昨日未明から本日11時にかけて自作Webサービスtwistoireに障害が発生し、サービスを提供できませんでした。ユーザの皆様にはご迷惑をおかけしました。
簡単ではありますが、以下に障害内容を報告いたします。
障害内容
フォロワーが5,000を超えるアカウントに対応できないことに気づかず運用していたため、一部ユーザのフォロワーが5,000を超えたタイミングでサービス提供が不可能になりました。具体的な障害内容としては以下2つが挙げられます。
- 1人に数百回/分のDM送信を行うことによるTwitterAPIの枯渇
- リソース消費による他ユーザへのサービス提供遅延
発生原因
今回の障害は、私がDatastoreに関する制限を正しく理解していなかったために発生しました。以下にこの制限についてまとめます。
現在Datastoreでは、要素が5,000個*1を超えるListを永続化しようとするとIllegalArgumentExceptionが生じます。これはbuilt-in index*2に起因する制限のようです。
- http://groups.google.com/group/google-appengine/browse_thread/thread/7443cbd122935e71
- http://code.google.com/intl/en/appengine/docs/java/datastore/queries.html
twistoireではフォロワーをList
今回使用した検証コードをGistにアップしていますので、興味をお持ちの方はご参照ください。なおローカル環境ではこの制限がかからないため、サーブレットをApp Engineにデプロイして確認する必要があります。
対応内容
対応方法には大きく分けて2パターンが考えられました。今回はデータ永続化にJPAを使用しているという前提から、後者を選択しています。
- Entity#setUnindexedPropertyメソッド を使用し、インデックスの自動作成を回避する
- built-in indexが作成されないBlobプロパティかTextプロパティによってフォロワーを記録する
具体的には、intをMessagePackでシリアライズしてからBase64でエンコードし、Textプロパティに記録しています。この方法でフォロワー数14万までは耐えられる計算です。[http://twittercounter.com/pages/country&time_zone=Tokyo:title=フォロワー数が14万を超える日本のアカウントは70程度]しかありませんので、ほぼすべてのユーザに使っていただけることになります。
文字列にエンコードせずbyteをBlobプロパティに突っ込む方がシンプルですし、フォロワー数20万程度まで耐えられるのですが、データをCSVにバックアップしているという現状の運用を考えるとまずはTextのほうが扱いやすいのではと判断しました。著名な方々にも使っていただけるサービスを目指すならBlobstoreや圧縮の採用が必要になるでしょう。
また、この他にも
- TaskQueueのオプションで「失敗したら1時間以上待ってから再実行する」ように変更
- 従来はすぐに再実行していたためダイレクトメッセージ大量送信が起きやすかった
- 単体テストの充実
- テストパターンを増やす
- 実環境でのテスト実行も順次増強
- ログの整理
といった、障害を起きにくし対応を容易にするための工夫も施しています。どれも従来から行われていて当然のものでありお恥ずかしい限りですが、今からでも遅すぎではないですし少しでもユーザの皆さんにプラスになればいいと考えています。
今回の障害はあってはならない問題であると受けとめています。サービスを停止したということは期待してくださっている機能提供に応えられなかったわけですし、大量のダイレクトメッセージを送りつけてしまったユーザの皆さんには実質的な被害も与えてしまいました。
ありがたいことにtwistoireは、ここ4ヶ月ほどで1,344人のユーザを抱えるサービスになりました。appengine研究と趣味を兼ねて気軽に作ってきましたが、もう1段高い意識と責任感をもって楽しむべきだと改めて認識した次第です。今後ともよろしくお願い致します。