foursquareの11時間にも及ぶサービスダウンの原因を詳細に調査してみた。<a href="http://b.hatena.ne.jp/entry/http://d.hatena.ne.jp/doryokujin/20101014/1287000278" class="bookmark-count"><img src="http://b.hatena.ne.jp/entry/image/http://d.hatena.ne.jp/doryokujin/20101014/1287000278" tit

こんにちは、@です。前回に引き続き、MongoDBに関するエントリーです。今回は10月4日にMongoDBが原因で起きた、foursquareのサービスダウンに関して、その原因や復旧に至る経緯を詳細に調査しました。TechCrunchJapanの記事、Foursquare:「対策を講じたはずなのですが、また6時間もダウンしてしまいました」にも紹介されていたのでご存知の方も多いと思います。MongoDBが原因で引き起こったとするならば、企業で実際に運用している僕にとっては放っておけない問題になります。実は5月にも長いサービスダウンがあったのですが、それはAmazonEC2の停電によるものでした。
本日のアジェンダです:

  • foursquareにおけるMongoDB
  • サービスダウン時の状況
  • 本当にMongoDB自体の問題だったのだろうか
  • サービスダウンの引き金となった出来事
  • 本質的な原因はどこにあったのか
  • 復旧後の状況
  • foursquareの行なった対策について

foursquareにおけるMongoDB

foursquareのサービスにはScalaの言語が用いられ、WebフレームワークであるLiftを使用して作られています。現在130万人のユーザーを抱え、1日に 61.5万回のチェックインが行われています。これまでに5000万回のチェックインが行われました(2010年5月時点)。なお、8月末の時点で会員数は300万人を超えたとロサンゼルス・タイムズのインタビューの中で報じられています。
そして、チェックインの情報を保存しておくデータベースとして現在はMongoDBが使われています。元は1台のPostgreSQLインスタンスをデータストアとして使用していましたが、急速のサービス拡大に伴ってMongoDBに切り替えた経緯があります。切り替えた理由は主にスケールアップ(レプリケーションとシャーディング)の観点で、MongoDBの方がSQLデータベースよりもはるかに容易に導入・管理できるからと考えたためです。この機能が使えるMongoDBのバージョンは1.6からなので、foursquareはバージョン1.6(最新)を使用しています。チェックイン・Tips、そして現在地の施設などにつける、Venueと呼ばれるマーカ情報(とそれに関するデータ)がMongoDBに書き込まれていきます。

MongoDBはロケーション(+/- 180 degrees in each of 2 axes)に関するデータに対して特殊なインデックスを作成することができます。インデックスとして「"2d" index type」というタイプを使用することができ、例えば latlng: [40, -72]というロケーションを持ったvenuesというコレクション(SQLでいうテーブルに当たる用語です)に対して、

db.venues.ensureIndex({latlng: "2d"})

と、インデックスを作成することで以下のようなクエリーでの検索が可能になります。

  • 与えられたロケーション[40,-72]に近いデータ20件を取得する:

db.venues.find({latlng: {$near: [40.72, -73.99]}).limit(20)

  • 与えられたロケーションから各軸で1度だけ異なる範囲にあるデータを20件取得する:

db.venues.find({latlng: {$near: [40.72, -73.99, 1]}).limit(20)

さらに多くの情報は公式ドキュメント日本語訳の地理空間のインデックスを参照してください。

参考:Foursquare & MongoDB

サービスダウン時の状況

  • 10/04 11:00am EST - 片方のシャードにチェックインが集中していたために、パフォーマンスが著しく低下していることに気づく
  • 10/04 12:30pm EST - この1時間半の間に偏りをなくすためのバランシングを試みたがうまく機能しなかった。そして新しいシャードを追加することでこの偏りを軽減させようとした
  • 10/04 6:30pm EST - シャードの追加によっても1台のシャードのパフォーマンス低下が解決されなかったために、インデックスの再構築を行ない、メモリの断片化を修復することを決めた。この再構築にはかなりの時間と、データの損失や破壊の可能性があるためにバックアップ作業が必要になる
  • 10/04 11:30pm EST - 5時間以上におよぶ再構築とバックアップ作業の後、サービスが復旧。幸いにもデータの損失などの障害は避けられた。

この約11時間の間、foursquareのすべてのサービスは完全にダウンしてしまっていました。

参考:So, that was a bummer.

本当にMongoDB自体の問題だったのだろうか

foursquareのダウン時はまだデータベースの記録容量はまだ120GB程度でしかありませんでした。また直近のレポートでは1日約100万チェックインが行われ、これは1秒当たり60チェックイン(=書き込み数)が行われたことになります。この数値は大きいのかというとそれ程でもなく、例えばBasho on NoSQL performanceによれば、RiakとよばれるNoSQLで8ノード(16,000 TPS with 14 nodes)の構成で秒間10,000トランザクションをさばけたという報告があります。(Riakについてはこちらのスライドが参考になります。)なので、今回の問題はMongoDBの本質的な性能不足によるもの以上に、foursquareのMongoDBのデザインやトラブル対応に少し不備があったのではと考えるほうが自然になります。

参考:MongoDB, FourSquare and the Sharding problem

サービスダウンの引き金となった出来事

foursqareではサービスダウン当時、AmazonEC2上で66GBのメモリを積んだ2台のマシンでシャーディング(データベースを分割することで負荷やディスク・メモリ容量を分散させる方法)を行なっていました。シャーディングはユーザーIDを元に行われていました。それまでの2ヶ月間の運用では、データは2台のサーバーに均等にうまく振り分けられていてどちらのシャードも約33GBのメモリを消費する程度で安定していました。しかし今回はこのバランスが崩れてしまったようで、サービスダウンの原因は

  • 1. 片方のシャード(shard0とする)のメモリが物理限界の66GBをオーバーし、著しい性能低下を引き起こした

であったと報告されています。さらにその対策もうまくいかなかったわけで、

  • 2. 第3のシャードを追加することによってshard0から5%のチャンク(データの塊)を移行しようとしたが、その後もshard0のメモリ容量が減らず、状況が解決されなかった

ために、復旧に11時間もかかってしまいました。しかし、幸いにも一部のデータが失われるといった状況にはなりませんでした。

参考:Foursquare outage post mortem

本質的な原因はどこにあったのか

それではこれに関する本質的な原因をMongoDBの特徴と絡めて詳しく見ていくことにします。まず前者に関しては、

ユーザーIDをキーにした分割方法が適切でなかった

が考えられます。ちなみにもう片方のサーバーのメモリ消費量は50GBでした。ユーザーIDを分割のキーとしていたために、片方のシャードに何度もチェックインを繰り返すヘビーユーザーが偏ってしまう振り分けになってしまっていたとしたら、このような状況になってしまう可能性があります。また、後者の原因にはMongoDBが密接に絡んでおり、

インデックスに使用したメモリの断片化によってshard0の負荷を低減させることに手間取った

ことが考えられます。foursquareが記録していたチェックインに関するレコードは1レコード当たり約300バイトでしかありませんでした。実はMongoDBでは4KB以下のレコードの集合に関しては、それが別のシャードに移動したり削除されたとしてもインデックス作成に使用された一部のメモリは開放ずに残ってしまう(断片化)状況が起こってしまうのです。foursquareの場合は、データが連続しているチャンクから効率よくデータを移行することができずにこの状況が起こってしまいました。user-groupのDetails on FourSquare bug?において、新しいシャードの追加が素早く・効果的に機能するための条件として、Dwight Merriman (10gen)は以下のように述べています:

  1. シャードの条件に設定されたキーの振り分けが挿入順序で行われる場合
  2. ソースとなるシャード(ここではshard0)に高負荷がまだかかっていない状況時にシャードを追加した場合
  3. 1つ1つのレコードが4kBより少し上回る程度の容量を持っている場合
  • もしこれらの条件のすべてを満たしていない場合は、速やかにデフラグを行い、断片化の進行を防ぐ必要がある

同サイトに、4kBより遥かに小さい大量のレコードで構成されたコレクションを作成して検証が行われています。このコレクションはインデックスに約300MBの容量を消費していましたが、このコレクションを削除したあとも2.3MBの容量を消費していることがわかります。さらにこのコレクションを作成しては削除するといった動作を繰り返すたびに、どんどん消費量が増えていっています。今回のケースでもこの断片化が引き起こされていたことが考えられます。さらに、他の2条件も満たしていなかったので、なかなか追加シャードへのデータの移行が進みませんでした。

参考:Details on FourSquare bug?

復旧後の状況

その後shard0の復旧が進み、また3番目のシャードに一部のデータの移行が完了したために、現在はデータが均等に振り分けられ安定に動作していると報告されています。MongoDBには全てのインデックスを再構築するためのrepairDatabase()コマンドがあり、これを実行したようです。このコマンドは、すべてのデータが壊れていなかチェックし、壊れているものがあった場合取り除きます。またデータファイルを少し縮小します。しかし、容量の小さなチェックインレコードを大量に持ったコレクションによって引き起こされた断片化の問題に関しては本質的な解決には至らず、スレーブサーバーに repairDatabase() というコマンド(すべてのインデックスの再生成)を実行して最適化した後、このスレーブをマスターに昇格させることで約20GBの消費に抑えたと報告されています。また、今回明確となったMongoDBのボトルネックは、現在10genの技術チームが解決に向けて取り組んでいるとのことです。

参考(MongoDBのrepairコマンドについて):耐障害性と修復(公式ドキュメント邦訳)
参考(MongoDBのインデックスについて):インデックス(公式ドキュメント邦訳)

foursquareの行なった対策について

今回11時間にも及ぶサービスダウンはユーザーに対しての信頼を損ねてしまう結果になってしまいましたが、やはり新しい技術を使用する以上はこのようなリスクは避けられないと思います。しかし、foursqareはこのダウンの間、

  • サーバーダウン中やバックアップ中である旨のメッセージをTwitterアカウント、 @4sqsupport と @foursquare が伝えた
  • また、 @4sqsupport は毎時間ごとに定期的に状況報告を続けた

ときちんと状況報告に努め、かつこの事例から学んだことを、

  • サービスの稼働状況を表示するサイト、 status.foursquare.comを新たに作成した
  • より意味のあるエラーページを作成することにした。現在どのような状況でそれが起きているのかを詳細に記述するようにした

としてすぐにサービスに反映しました。僕はこの一連のきちんとした対応には好感を持っています。また、この大きな出来事を元にMongoDBがより良いものに、そして他の企業この事例を学んで同じ状況が起こるのを回避できるようになっていくことを願っています。

終わりに

今回、様々な海外サイトを参考にさせていただきましたが、英語面・技術面ともにまだまだ未熟ですので訳や解釈の違いが多々あると思います、その場合はご指摘していただけると幸いです。今回はMongoDBの少しネガティブな部分を書きましたが、他にも色々とネガティブな部分があることがわかっています。MongoDBのメリット/デメリットという軸でそのうちエントリーが書けたらなと思っています。

追記(10/21更新)

後日InfoQより詳細な報告があげられていましたので追記してお伝えします:

InfoQ Foursquare's MongoDB Outage

また、この日本語訳も公開されていますので参考にしてください。

FoursquareのMongoDBが機能停止