effective-goではない何か

去年からGolangを書き散らかしてみて、だいぶセンス感じとれるようになってきたので、Golangを書くときのイディオムのようなものをまとめてみる。

Golangをコーディングするディレクトリ

簡単な、10行20行くらいで済むコードであればどこで書いてもいいのだけれども、 ライブラリだったりモジュールに分けるレベルの規模のコードだと、コーディングするディレクトリは、$GOPATH直下、または$GOPATHへのsymlinkにしたほうが良い。

例えば、ライブラリを作成しようとしてホスティング場所はGitHubだとする。 その作成しているライブラリを使ってサンプルコードを書こうとすると、 import宣言は、

import (
  "github.com/yoppi/a-library"
)

となる。 ただ、$GOPATHにないとビルドできないので、まだGitHubにpushしていない場合はもちろん、「少し修正して書き直す」という繰り返しの中でGitHubにpushしてgo getしないと手元でコード書けない、という状況は辛い。 そこで、書いているライブラリは、$GOPATH直下 -- たとえば$GOPATHが $HOME/.go/ だとすれば、$HOME/.go/src/github.com/yoppi/a-library -- に作成すると、問題なくビルドできる。 ちなみに、$GOPATHに直接作成するか、$GOPATHへsymlink張るかの流派で分かれている、模様。 僕は、symlink派。

ライブラリ兼実行コマンドありのディレクトリ構成

ライブラリとしても使うのだけど、実行コマンド(たとえばa-execという名前だとする)もあるライブラリを作るときは、

a-library
  `
  + a-exec
  |   `
  |   + main.go
  + a-library.go
  ...

と、実行コマンド名をディレクトリ名にして、直下にmain.goとするディレクトリ構成だとわかりやすいと思っている。 こうしておくことで、go get github.com/yoppi/a-library/a-exec で追加でインストールも可能になる。

ちなみに、実行コマンドだけのものを作るときでそこそこ規模が大きくなる場合、

a-exec
  `
  + bin
  |   `
  |   + a-exec.go
  + module.go
  ...

みたいな構成にしている人が多そう。

構造体を生成するコンストラクタ

構造体を生成するコンストラクタ関数名には、型名に接頭語Newを付けることにしている。 むしろ、それ以外の接頭語が付いている名前だと、他の人が書いたものを読んだ時に戸惑ってしまう。 Golangで組み込み関数であるnew()もあることですし。 そして、この関数は型宣言した、直下に宣言しておくとさらに読む人が把握しやすいので便利。

type A struct {
  // ... member
}

func NewA() *A {
  a := &A{}
  // ...
  return a
}

型T

Golangで、任意の型を表現するのはinterface{}。reflectを頻繁に使って問題を解決しようとするプログラミングだと、intarface{}型を駆使することになる。 これを毎回毎回タイプしてたらFPの消費量激しくなって、辛くなってくるので、aliasとして型Tを宣言しておくと便利だと思う。

type T interface {}

init関数でコマンドライン引数をパースする

Golangでは、init()関数は特別扱いされる。 使い道としては、Javaでいうところのstatic initializerに相当するもの。 たとえば、ライブラリ内コレクションを貯める、みたいなインタフェースがあったとして

package some-library

var hogeCollection map[string]Hoge

func init() {
  hogeCollection = map[string]Hoge{}
}

のように書いておくと安全に初期化できる。 これを利用してコマンドライン引数の初期化などを書いておくとコードがすっきりしそう。 コマンドライン引数の扱いは、flagパッケージを駆使すると便利にパースできる。

import (
  "flag"
)

var (
  a int
  b string
)

func init() {
  flag.IntVar(&a, "a", 0, "args a")
  flag.StringVar(&b, "b", "", "args b")
  flag.Parse()
}

func main() {
  println(a)
  println(b)
}

グローバルに変数を宣言することになるけど、mainに書くよりかは読みやすいかなぁと。

ISUCON3 - 予選から本戦を終えて

TwitterでISUCONに参加したいとぼやいていたら、@f440 さんが一緒にどうかと誘ってくれたので、ISUCON3に参加してきました。

オシャレ怪盗スワロウテイルとして参加し、本戦2位という結果で幕を閉じました。 参加した動機として、自分がWebエンジニアとしてどれくらい通用するのかという腕試しでもあり、また優勝を狙っていたので、あとからじわじわ悔しさが溢れだしながらこのまとめを書いています。

僕も考えていた全体的な心構えとか、方針とかは相方の @f440 さんのエントリ ISUCON3 の参加記録 に読みやすくまとめらているので、合わせて読んでもらえると僕達がどう考えてISUCON3に取り組んだのかが深まると思います。

予選前

今回がISUCON初参加、ということもあり勝手があまりわかっていない状態だったので、@f440 さんにざっと解説してもらい過去の問題(ISUCON2KAYACさんの社内ISUCONRubyに書き直して)をチューニングする、みたいなことをしていました。

予選でやったこと

予選の問題は、メモスニペットアプリ(投稿するテキストがMarkdownで編集できる)でした。 セッションを伴うWebアプリは出題されそうだよね(KAYACさんの社内ISUCONがまさに)という話は事前にしていて、出題されたときに予想どおりでテンションが上がったのを覚えています。 また、公開されたamiからAWSに自前で用意して挑戦するというのも、AWSに慣れた身としては気持ちも楽でした。

やったこととしてそんなに多くなく、

  • クエリのチューニング
  • DBのチューニング
  • Varnishによるページキャッシュ
  • テーブルのインメモリ化

という感じでした。 クエリのチューニング、DBのチューニングは早々に潰せ、 さらに、Varnishをページキャッシュとして導入したことにより一気にスコアを上げることができたまではよかった。 のだけれども、アプリが軽くなってくると、Markdownで変換しているところが重たくなってくる、のは目に見えてわかってきていたのに、どうでもよいチューニングを繰り返して、結局1位を取れず、2日目2位、総合5位で予選通過という結果になりました。

予選が終わってからの反省会で、「なんで巨大なテキスト取ってきてsplitしてるんだよ」「普通Redcarpet使うよね」「カウントしてるところとかRedisでincrするでよいのでは」と、冷静になると効果的なチューニングができることに気づいて少し凹んだりもしました。

予選を終えて

予選の結果があまりにも不甲斐ない終わり方だったので、 メンバーとチャットで予選の反省会しつつ、本戦に向けての準備を各自する、ということを続けていました。

  • 「今までずっと、コンテンツはテキストベースだったよね」
  • Flickrみたいな画像のアップローダーとか来そう?」
  • 「INSERTばかりでなくて、UPDATEやDELETEが大量にあるのでは」
  • SNS系だよなぁ。タイムラインは複雑になると破綻するし」
  • Twitterもどきはそろそろ出題されそう」
  • 「本戦でも多くてもテーブルは3つか4つくらいだよね」

などなどお互い予想しつつ、僕はもっぱらこういったアプリのデータ構造をRDBで持つアプリにしてチューニングする、ということを繰り返していました。 たとえばRDBじゃなくてKVSにする場合はどういう持ち方にするだろうか、キャッシュとの親和性を高めるにはどうすればいいか、等を考えていました。 具体的には、MySQLからRedisへデータ構造に落としこんだり、キャッシュとアプリの親和性を高めるために、ワーカーでアプリの断片を生成しつつ、nginxのSSIで組み立てるみたいなことも特訓していました。

また、KAYACさんが予選で使用したGo製のベンチマークツールも公開してくれていたので、手を加えて予選の問題を改造して、削除や更新も追加して負荷をかけたりして、ベンチマーク作る側からすればどういう問題が出そうか、みたいなことも考えたりしました。

どうでもいいですが、Go、大変良いですね。最近Rubyより書いています。

本戦でやったこと

本戦のお題は、公開レベルを設定できる画像のみ投稿可能なTwitterでした

構成がシングルページアプリケーションと今となっては珍しくないですが、しっかりした作りでチーム内で「シンプルに作られてて良い」みたいな話もしつつ、兎に角プロファイルです。 予選のときに、いろいろ手当たり次第な感じにチューニングを進めてしまったことを反省して、本戦では、アプリを読み込む、プロファイルをとることに注力しようとう話を事前にしていました。

まずは、コードを読みつつ画面を見ながらアプリを操作しつつ静的解析してからデータ構造を把握したところで、 素の状態で、以下のプロファイルを取ります。

  • Apacheのアクセスログ
  • MySQLのクエリログ(long_query_tme=0)
  • Sinatraのアクションログ(enable :logging)

これらのログから、「画像配信」がボトルネックだということが、すぐにわかりました。 DBにはほぼ負荷がないことに若干戸惑いもしました(mysqldumpslowすると一撃でわかります)。

そこで、画像配信をどうすれば速くできるか。午前中はそればかりを話し合っていました。アクションログや、アプリのコードを読みつつ、

  • 初期配置された画像はすべて事前にすべてのサイズで変換しておく
  • アプリで変換済みのものがあれば変換しない
  • 変換したら、保存して2度変換しない
  • アイコンはすべてpublicだよね? これはすべて変換してしまって静的ファイルとして返す
  • 画像も公開範囲は途中で変更できないので、publicなものはアイコンと同様に静的ファイルとして返す
  • 投稿されたときに非同期でワーカーで画像を変換する

という一連の案を素早く閃けたのは、なかなか良い感じでした。 あとは、作業分担して淡々とチューニングをすすめるだけなのですが、

  • 画像のバックアップ先がMacBook Airだった
  • そのままMacBookAirで、画像変換スクリプトを走らせてしまった(MacBook Proあるのに!)
  • ワーカーを作成したけど、思ったほど効果が出ない
    • 最初Watchrで書いたのですが思う通り動かず、Guardにしたのですが、ファイル検知が遅すぎて使い物になりませんでした。あとから、フロントからResqueなりSidekiqに投げればよかったとか思いつき悶絶したり
  • 構成として、1台のみアプリを動かして、4台にnginxを配置してフロントにしたのだけど全台をフロントにしてそれぞれでアプリを稼働させればスケールしてた

と、予選と同じくやはり本番中ではなぜか気づけず、タイムアップ後やチャット上で冷静になった頭で考えるとあれこれアイデアが浮かんでくるというなんともやり切れなかった感が残りました。 ただ、アグレッシブな方法を取らず、基本を忠実に、Failせずに完走できたのは良かったと思います。

まとめ

とにかくISUCONでは、

  • 熱くなっても冷静になる
  • ふつうのWeb技術力
  • アプリケーションを短時間で把握する力
  • Web技術の引き出しを増やして、問題解決のために使えるものを選択できる力
    • 知ってるとかちょっと使えるだけではだめで、これをこう使うと問題をこう解決できる、というレベルにシフトしておく

が、大切だなーと実感しました。 仕事と同じで、問題解決にはまずどこが問題なのかを把握するところから始めます。 ISUCONでは、それを7時間という短い時間内に的確に見極めて、手を入れていくのはとても面白いものがありました。

調度良い難易度の問題を作成してくださったKAYACさん、また、インフラを提供してくださった、データホテルさん、イベントを開催していただいたLINEさん、本当にありがとうございました。 オシャレ怪盗スワロウテイルはここで解散となりますが、また来年リベンジしたいと思っています。

SO_REUSEPORT ソケットオプション

Linux Kernel3.9(BSDでは割と以前から実装されていたけど)で取り込まれたSO_REUSEPORTソケットオプションは、異なるプロセスで同一のポートにbindできるようになるので、いろいろおもしろいことができそう。

https://lwn.net/Articles/542629/

これがmerge commit

https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/commit/?id=c617f398edd4db2b8567a28e899a88f8f574798d

試しにRubySO_REUSEPORTを使うサーバを書いてみた。 もちろんKernelを3.9以降にアップデートしないと動かない。 Ubuntuだと、script書いてくれてる人がいるので楽にUbuntu Kernelをアップデートできる。

http://dl.dropboxusercontent.com/u/47950494/upubuntu/kernel-3.10.9

require 'socket'

SO_REUSEPORT = 15

s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
s.setsockopt(Socket::SOL_SOCKET, SO_REUSEPORT, 1)
s.bind(Addrinfo.tcp("127.0.0.1", 10080))
s.listen(5)

while true do
  conn, addr = s.accept
  puts "Connected to #{Process.pid}"
  data = conn.recv(1024)
  conn.send("HTTP/1.1 200 OK", 0)
  conn.close
end

で、これを10プロセスほど起動して、apache benchでつないでみる。

$ for i in `seq 10`
do
  ruby so_reuseport_opt.rb &
done
$ ab -n 10000 -c 10 localhost:10080/
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)...Connected to 3476
Connected to 3334
Connected to 4875
Connected to 3531
Connected to 3531
Connected to 4879
Connected to 4869
Connected to 2891
Connected to 4870
Connected to 4868
Connected to 4866
...

カーネル側でバランシングしてくれていることがわかる。 使いどころとして、

  • 単純なpreforkモデルでパフォーマンスを出したいアプリケーション
  • システム管理系のアプリケーションで、プロセスを落とせないけど、新しいversionを使いたくて、同時に新しいものも起動させてから、古いversionのプロセスを落とす(hotswapしたい)

などが作れそう。

guard文のあれこれ

あるメソッドで、渡されたパラメータによっては処理をまったくしないということがある。 その場合guard文を書くことでネストが深くならなくて、読みやすくなることはイディオムとしてよく知られている。

しかし、guard文を書くと逆に読みにくくなってしまう場合があるんじゃないかと最近仕事のコードを書いていて思った。とくに、必ず特定の値を返すメソッドの場合。 「必ず特定の値を返すメソッド」はどんな時に書くのかと考えると、例えば、Interceptorパターンを実装していて、パラメータは降ってきて特定の条件下では処理はするけど、必ず後続のInterceptorに処理を継続させたくてtrueを返す場合。

public boolean before(AObject o, AMap param) {
  if (!cond(param)) {
    return true;
  }

  // brabrabra

  return true;
}

二回もreturn文が出てきて気持ち悪いのでここは、guard文を書かないで素直に書くほうが読みやすいんじゃないかと。

public boolean before(AObject o, AMap param) {
  if (cond(param)) {
    // brabrabra
  }
  return true;
}

Mac OS -> Ubuntu

教授がMac OSをメインで使っていた影響で、学生時代からメインマシンにMacを使ってきたけれども、転職を期にUbuntuへ移行しました。

  • 職場の開発マシンがUbuntuなので、普段からプログラミングスタイルや開発環境のノウハウを貯める
  • xmonadの完成度が高くてプログラミング環境として集中できる(職場にxmonad使い多くて吹いた)
  • 別にMac OSが必要な仕事をするわけでもないし、プライベートでも興味がない。結局Xcodeもcommand line toolのインストール時等、数える程しか起動しなかった

など、よくよく考えるとMacでなければならない理由はなくて、手軽にUnixライクな環境が使えればよかった。 とはいえ、純粋なUnixではなくUnixの皮をかぶったDarwinなので、たまに不都合(ビルドがすんなりできないとか)が出たりしたときはつらいなーとか思って諦めてそのまま使ったり、頑張って解決したりしてきたけど、そんな悩みもなくなった(はず)。 切り替えてから1ヶ月ほど経過してるけどいまのところ問題もなく、むしろ快適に感じている。

nodeでRedisを使ってセッション管理

フレームワークにexpressを使う場合、connectが提供するsessionモジュールを使うと楽に実装できる。 さらに、Redisにセッションデータを保存する場合、connect-redisを使うと楽だ。

https://github.com/visionmedia/connect-redis

他にも、express-session-redisというモジュールがあるけれども、コードを読んでセンスがあると思ったのは、connect-redisの方。 connectがセッションストア用インタフェースを作ってくれているので、それを継承して作ったほうがスマート。

Twitterでログインするやつをconnect-redisを使って書いてみた。

https://gist.github.com/yoppi/5604149

nodeでWebアプリを開発するときに考えること

僕は、全体を俯瞰して作り出すので遅いのだけれども、トータルとして同じくらいのスピードになるかなと思って、新しい言語やフレームワークを使うときは、まずは全体で必要になりそうな技術であったり、問題点を潰すことにしています。

フレームワーク

nodeのフレームワークは、たくさんありすぎてどれを使っていいのやら、という状況だけれども express 使っておけば良いというのが僕の中での結論。 ルーティング、テンプレート、セッション管理といった基本的な機能を簡単に使える(connectが素晴らしいというのもあるけど)ので、複雑なことをしたければあとは自分で頑張るほうがいいと思う。

それに、全部読みきれる量であるということ。 使うライブラリのコードを読みきれるというのはすごい重要。フレームワークでサポートしていないことをしたくなったときにどこに手を入れて良いのかがすぐに判断できるから。 また、expressのテストコードはmochaを使っているので、本体と合わせて読むと、どうやってテストコード書けばいいかも勉強できて良い。

O/Rマッパー

RDBMSを使ってそこそこの規模のWebアプリを書こうと思うと、O/Rマッパーがあるとすっきりする。が、探してみるとActiveRecordのような決定的なものがない。 ぱっと見でよさそうなのが、node-orm2

https://github.com/dresende/node-orm2

基本的なクエリに加えて集計関数も投げられるし、has-oneとかhas-manyといった関連も定義でき、フックも充実、さらにexpressとも相性がいい。 Rails界に住んでる人は、モデル毎にファイルを分けて定義したくなるので、手動でロードしないといけないのでちょっと面倒。

var orm = require('orm'),
    fs = require('fs');

var dbConfig = JSON.parse(fs.readFileSync(__dirname + '/database.json'));
var db = orm.connect(dbConfig.dev);
fs.readdir(__dirname + "/models", function(err, models) {
  if (err) {
    console.log(err);
    return;
  }
  models.forEach(function(model, i) {
    db.load(model, function(err) {
      console.log(err);
    });
  });
});

一般的にnodeでRDBMSを操作することはやはり少ないのかなぁ。スロークエリ頻発するともはや非同期IOは意味をなさなくなるというところから、mongoやCouch、Redisを積極的に使う方針にしたほうがよいのだろうか。

migration

RDBMS使うなら、migrationの機構がないと死ねる。node-db-migrateが使いやすい。Rails流。up/downで操作、データベースにmigrationsテーブル作って管理するところなど。

https://github.com/nearinfinity/node-db-migrate

上記のnode-orm2と相性はまぁまぁ。唯一ぐんにょり来るのが、設定ファイルのプロパティ名が違っていること。 node-orm2では、protocolだがnode-db-migrateはdriverになっている。database.jsonで両方記述しておくとうまくいくが、ぐんにょり来る。 あと、Rails界の人は、

  • updated_atcreated_atは必ず自分で定義しないといけない
  • サロゲートキーも自分で定義(idも自動付与されず自分でPK、AUTO_INCREMENTを指定する)

に注意。

auto loader

hotnodeが今のところ快適に使えている。 コヒスク使う人は、hotcoffeeコマンドで。 シングルページアプリケーション程度の規模なら問題なし。

https://github.com/saschagehlich/hotnode

デバッガ

とりあえず、node-inspector使っておけ、らしい。

https://github.com/dannycoates/node-inspector

Webkitブラウザ上でサーバサイドのコードをデバッグ実行できるので死ぬほど便利。一度使うと便利すぎてないと生きていけなくなる。 上記のhotnodeとも組み合わせて使える。

$ hotnode --debug app.js # アプリ起動
$ node-inspector # 別ターミナルで実行してChromeなどにアクセスする

ホスティング

普通のWebアプリなら迷わずHerokuだけれども、たとえばWebSocketを使いたい場合Herokuはまだサポートしていない。

https://devcenter.heroku.com/articles/using-socket-io-with-node-js-on-heroku

Herokuは1Dynoなら無料で公開できる(ただし750時間までなので注意)ので、これほどまでに使いやすいPaaSはないので諦めるのは辛いところ。 WebSocketの代わりにロングポーリングでも良いのだけれども、iPhoneiPadだと通信中のぐるぐるが回ったままで、気にする人は気にするので気持ち悪さを与えて結果として使わないということがあるので、なるべくWebSocket使いたい。

とりあえずは、Herokuで作り問題が出て来て(お金に余裕のある人は)、nodejitsuやAWS、VPSあたりを借りるがいいのでは。

デプロイ

nodeアプリを動かすときに、Heroku や nodejitsu といったPaaS使わない場合、手動(capコマンド実行等)/自動(git pushトリガー, jenkinsトリガー)問わずproduction環境にデプロイする必要がある。

Stack Overflowの解答を眺めていると、Capistranoを使っている人が割と多い。

プロセス管理

nodeは、内部でエラーが発生すると、落ちる。イコールサービスが止まる。なので、基本的に自動で上げるためのプロセス管理が必要になる。 まぁこれはいくらでもあるので、monit、god、supervisor等慣れたものを使うのが良いと思う。

あとは、node製のものとして、foreverがある。 nodejitsuはforever使っているとのこと。 Stack Overflowでの解答も「とりあえずforever」という意見が多い。