詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

ISUCON5 本選に参加して7位に終わりました #isucon

表題のとおり、ハロウィンの夜ゾンビ化した渋谷の街でヒカリエに取り残された俺たちは、ISUCON5本戦に参加してきました。

予選はこちらをご覧ください:

motemen.hatenablog.com

チームは id:wtatsuru (たつる先生)と id:ichirin2501 (いちりんちゃん)とで組んだ「2nd Party Cookies」。

結果は上記のように7位。8万点台に乗らなければ勝負に絡んだと言えず、一時は7万台まで伸ばしていただけに悔しい結果となりました。以下、当日やったことをふり返ります。

初動

開始時刻になってサーバにアクセスできるようになると、wtatsuru が諸々のセットアップをしているあいだにアプリケーションの挙動をチェック。マイクロサービスを使ったウェブサービスで、ユーザの登録情報から複数の HTTP API を叩いて画面に表示する、というものでした。

データベースが Postgres だったのには戸惑いましたが、Perl 実装に切り換えて各エンドポイントごとの処理時間を見ると直列に外部リクエストを発行している GET /data が支配的だったのでまずはこちらをやるのが先決だろう、という結論に達しました。

最初の1時間が経ったところで作戦会議と決めていて、この時点でまだ時間があったので /data の処理をクライアント(ベンチマーカー)に分散してみたらどうだろう、というアイデアを検証するため JavaScript を書き換えたところ、fail。あくまでサーバサイドのアプリケーションをチューニングせよという問題だと分かりました。

まずはキャッシュ

外部 API の性質を把握するのが第一だろう、ということでエンドポイント定義を見るとすべて GET だったので、これはキャッシュしてよかろうと memcached に無期限キャッシュするように。するとスコアが10倍くらいに跳ね上がりました。fail がなかったので方針は間違ってないのだろうと思ってしまいましたが、後にキャッシュが古すぎるという理由の fail が頻発し、苦しむことになります……。

ken および ken2 API は見るからに郵便番号から住所を返すものだったので、どこかに元データがあるのだろうと思って探したところ、日本郵便の提供する CSV ……を加工した CSV が 郵便番号データのダウンロード - zipcloud で提供されているのを知り、Postgres に COPY 文でインポート、住所を非 HTTP API 化したところ、またスコアアップ。その後3台構成にすると3万点台に乗って暫定1位となりました。

ダメゾーン

ここでひと息ついて、リクエストの並列化やアプリの CPU 率の低減が考えられる次の課題だろうという話をし、motemen ichirin2501 それぞれが自分のブランチで作業することにしました。検証にはどれか1台にブランチをデプロイしてベンチを回し、スコアが上がればマージする、ということをすでに行っていて、そのフローを踏襲したのですが、3台構成にしたままこれを行ってしまったのがよくなかった。他にも混入していた不具合等があり、この辺りから fail が連発して、有用な変更を何一つ入れられませんでした。

持ち直し

みんなが同時に喋りだしたところで wtatsuru 先生が落ち着いて、まずは master 通る状態に戻しましょう、と言ってくれたので、元に戻して通ることを確認。このひと言には助かった。これで一同、気を取り直せました。

motemen は AnyEvent による並列化を断念。初期化処理をミスっていて郵便番号データがベンチのたびに増大し、メモリを圧迫していたことが分かったので修正する(これは initialize が30秒を越えたのを調査していて気づけた)、静的ファイルの配信をnginx 化したつもりができていなかったので修正する、など、凡ミスを修正して7万点台に。暫定3位。

またキャッシュ

……と喜んでいたら、アクセス数が増えた結果か、また fail するようになってしまいました。レスポンスの内容が古い、というメッセージ。これは memcached の初期化を忘れたときに一度見たものだったので、キャッシュを適切に破棄すればよかろう、と、キャッシュ秒数を調整することに。ここでようやく外部 API のレスポンスを curl で直接覗いたのだけど、この時点で残り2時間、遅かった。If-Modified-Since ヘッダも付与してみたがレスポンスに変化がなかったので、使えないと判断してしまいました(終了後じつは対応していたと明かされたので、リクエストの組み立てがおかしかった模様)。

tenki API が3秒ごとにレスポンス内容を変化させることを観察し、キャッシュ時間を3秒、1秒と変化させてみましたが fail。ここは実装をミスっていたのだとしか思えない。そうする間にデッドラインが近づいてきたので、成功していた頃の5万点台に戻すため、静的ファイル配信の最適化をやめるという苦渋の決断をしました。これで提出、あとは前出の通りの結果となりました。

感想

本戦には最近の2回を参加していて、その時と較べると確実に進歩しているけれど、まだまだ。今回はけっこう準備していったつもりだったし、方針も間違ってはいなかったはず。ただ、チームワークとか冷静さの部分で他のチームに遅れを取ってしまったように思えて、短い時間で結果を出すには、各自のレベルや経験はもちろん、やはり司令塔が必要かなと感じました。あと /data を Go で並列化する、とかは思いついていたかった……。

さて ISUCON 勝利への近道は出題者サイドに立つこと……だというもっぱらの噂ですので、まずは社内 ISUCON でも開いてみたいな、と思うところです。と少し考えてみるだけで、問題を作るのもベンチマーカーを準備するのも大変そう。今回は予選本選ともベンチマーカーが最高に快適で、問題も楽しかったです。主催者側のみなさん、お疲れさまでした&ありがとうございました!

Qiita 記事中のコード片をダウンロードするツール

Qiita に書かれた記事、とくにシェルスクリプトに関するものはローカルに保存して実行できる便利な「おみやげ」が含まれているものも少なくない。そして多くの場合そのソースは当該記事そのものであって、Gist にアップロードしています、というようなものはあまり見ないように思う。そういったものをじゃあ利用しようか、と思ったときにコピペしてエディタを開いて保存して……などとするのは癪なので、そのためのツールを書いた。

https://github.com/motemen/qiita-dl

インストール

% go get github.com/motemen/qiita-dl

使い方

たとえば git commit --fixup で fixup する対象を peco/fzf で選べるスクリプト書いた - Qiita 中の "git-fixup" を保存したい場合、

% qiita-dl -x -d ~/bin http://qiita.com/uasi/items/57da2e4268d348b371fb

とする。 -x は保存されたファイルに実行ビットを付与するオプションで、これを使えばダウンロードしたコード片が即座に利用可能となる。-d <dir> は保存先のディレクトリ。指定しなければカレントディレクトリになる。保存されるファイル名はウェブページ上ではコード片のタイトル的な表示である何か(クラス名的には言語を指していそうにも見えるけど)。これは -o <filename> で上書きもできる。

記事中にタイトルのついたコード片が複数存在する場合、以上のような使い方だと失敗する。そのときは -n <pos> に 1 からはじまるインデックスを指定して対象を絞り込む。以下のようにコード片がインデックスつきでリストされるはず。

ちなみにこのオプションを指定すると、ファイル名のないコード片もダウンロードできる(-o と組みあわせて使う)。

% qiita-dl http://qiita.com/uasi/items/c4288dd835a65eb9d709                                                             
Title: Zsh 入門者のための超速設定ガイド - Qiita
error: Too many snippets are there:
[1] ".zshrc"    # Emacs ライクな操作を有効にする(文字入力\xe4...
[2] ".zshenv"   # PATH の設定(お好みで)\nexport PATH=\"/usr/local/b...
[3] ""  % bindkey -L\nbindkey \"^@\" set-mark-command\nbindkey \"^A\" begi...
Specify one with -n

git commit --fixup とは何か

git commit --fixup というオプションの存在を最近知って調べた。

ヘルプとリリースノートより

"git commit" learned the --fixup and --squash options to help later invocation of interactive rebase. Git v1.7.4 Release Notes

       --fixup=<commit>
           Construct a commit message for use with rebase --autosquash. The commit message will be the subject line from the specified commit with a prefix of "fixup! ".
           See git-rebase(1) for details.

1.7.4 から入ってるらしい。

使い方

ふつうにコミットするときに、別の既存のコミットを指定する。

git commit --fixup=HEAD~1

こうする場合コミットメッセージは不要で、代わりに指定されたコミットのコミットメッセージの先頭に "fixup! " が付与されたコミットメッセージが使用される。

% git log --oneline
e609512 fixup! some commit
9961eb5 meow
05aed73 some commit
147d264 init

なんとも色気のない機能で、これだけだと何に使えるのかと思うが、ここで git-rebase が登場する。

git rebase と組み合わせる

git rebase について詳述はしないが、これは過去のコミット群の親をつけ替えるなどして歴史を改変できる高度なコマンドで、とくに -i オプションによるインタラクティブモードだとエディタを利用して詳細な編集がおこなえる。そこでは "fixup" という指示が使えて、指定したコミットの変更をまるっとその親に含めてしまうことができる。あるコミットをした後に、そのコミットに関するちょっとした修正を後知恵でコミットしたあと、git push 前に git rebase -i、という流れだ。ちなみに git commit --amend でも同じようなことはできてよく使っているが、これは最新のコミットを書き換えることしかできない。

git commit --fixup はこの git rebase -i とうまく働く(というか、そのためのものである)。git rebase -i--autosquash オプションを与えると、先ほどの "fixup! " で始まるコミットを自動的に検出して、そのコミットを修正先のコミットと合体させてくれるように再配置してくれる。

先ほどの例で git rebase -i --autosquash HEAD~4 してみると、エディタには以下のような並びを提示される:

pick 05aed73 some commit
fixup e609512 fixup! some commit
pick 9961eb5 meow

コミットの順番が並び替わっていて、このままエディタを閉じれば "fixup! some commit" の変更は "some commit" に合体して歴史がきれいに改変される。

おまけ

Git のコミットは :/<コミットメッセージの部分文字列> という指定の仕方もできるので、先ほどの例だと

git commit --fixup HEAD~1

の代わりに

git commit --fixup ':/some commit'

のような指定の仕方もできる。

2015-10-20 追記

git commit --fixup とは何か - 詩と創作・思索のひろば

commit --fixup は便利そうなんだけど、最近はコマンドラインから commit しないからなぁ…。magit で commit する時に手で「squash!」書いてる。magit でもできるんかな。autosquash は .gitconfig に書いておくと便利。

2015/10/20 08:29

なるほど! rebase.autosquash を true にしておくと、git rebase -i 時に自動で --autosquash されるんですね。

git config --global rebase.autosquash true

はてなで一緒に働きませんか?