プログラムを書いていると、間抜けなバグをいくらでも仕込むことができる。たとえば一文字だけのTypoだったり、ifの括弧のつけわすれであったり、エラー処理をひとつ忘れていたり、もしくはその言語特有の間抜けなバグというものもあるだろう。そういったバグは往々にしてコードレビューはテストを掻い潜ってしまう。製品や本番のコードに残ったままリリースされ、とてもヘンなデータが入ったり障害が起きたりしたときにだけ発露する。今回のはなしもそのエラー処理をひとつ忘れてリリースしてしまったことがきっかけではあるのだが、むしろバグの見つけ方が間抜けであったことをここに書いて今後の反省の材料としたい。
riak_cs#985 はそんなバグのひとつで、エラー処理をひとつ忘れて Riak CS のコネクションプールが漏れていたという話だ。再現条件としては、ユーザに関するAPI(一覧GETなど)を叩いたり、 Authorization ヘッダなしで存在しないバケットを GET すると、コネクションがプールからひとつ漏れていく。コネクションプールが枯れてしまうと全てのAPIリクエストが503だか何だかのエラーしか返らなくなってしまって、まあ要はサービスが落ちるわけだ。これの原因が分からなくて、2日ほどうんうんと唸っていた *1 。
僕が非常に間抜けな見つけかたをしたのは後者の再現条件だ。 Riak には /ping という簡単な動作確認用のAPIがある。これをGETでたたくと、 "OK" という2文字がBodyに入ったレスポンスが返ってくるので、RiakのプロセスがすくなくともHTTPのレベルで生きていることはわかる。同様に、 Riak CS には /riak-cs/ping というAPIがあって、これを叩くと生きているかどうかがわかる。 #985 が問題になっているときに焦ってしまって、思いつく限りのAPIを適当に叩きまくっていたのだけど、そのなかに /ping というAPIがあると仕様を間違って理解していて、その実装は riak_cs_wm_ping.erlだと思っていた。これがコネクションを使っているし、あまりテストもないし、これが怪しいんじゃないかと疑っていた。これだと認証ヘッダがつかないので、ふつうのAPIアクセスとは違った挙動になる…ことを期待していたわけではなく、八方塞がって s3curl ではなく curl を使って叩いていた。ほんとうは /riak-cs/* のAPIも認証ヘッダが必要なのだけど、頭が混乱していたわけだ。
それで、
$ curl http://localhost:8080/ping
というコマンドを叩くとコネクションが漏れることを発見した。これがきっかけで問題箇所を特定できた(特定したのは僕ではない)のだけど、これは riak_cs_wm_ping.erl のコードを使っていなくて、 riak_cs_wm_objects.erl という全く別のAPIを叩いていたのである。どれくらい的外れかというと、この手の死活監視ならふつうは200が返ってくるのが相場だが、ステータスコードは404が返ってきてそういうXMLが表示されるくらい的外れだ。そして404が返っていても、リークを見つけて興奮していたのでそれにしばらく気付かなかったくらいボケていた。
ぼくの仮説の設定は全くの検討外れで、今回は運良くたまたま原因となるところにリーチできたわけだ。
ぼくはわりと性急な性格なので、勤務時間中は給料をもらっていると考えると効率を上げるために焦る傾向があるようで、デスクトップは片付けないし、かなりの数のメールをタイトルだけ読んでArchiveする。同様にドキュメントやコードを読むときは要点だけを知りたいので、細かい所はわりと飛ばしてよむ癖がある。それが今回は幸いしたわけだが、お前は本当にそれでよかったのかと問いたい。
じゃあバグの体系的な倒しかた、とかバグを仕込みにくい開発やプログラミング言語とは、という話になるのだけど、再発予防とか水平展開とか一般論に陥りがちで難しい。まあ QuickCheck でテストを書けという話ではある。
*1:まあ2日で見つかるんだからいいじゃないかという気もする