MyFleetGirlsがSQLのミスで死んだ

はじめに

MyFleetGirlsにDBのTableが増えるような新機能を入れたら、SQLにwhere句を1つ付け忘れただけで、1時間でDBを埋め尽くし、load averageを通常時の10倍以上に増やした事件の話。

新機能

新機能とはシンプルなもので、艦娘の経験値グラフを追加するものである。現行では艦娘の最新情報のみ保持しているので、履歴を別途取っておく必要がある。これが新しいTableに保存される。

このTableは簡単にRecord数の爆発が予想される。従って、最終更新からの差分を計算し、必要であればinsertするような仕組みを考えた。この部分にSQLのミスを埋め込んだ。

問題のTableはship_historyで、以下のようなものである。

https://github.com/ponkotuy/MyFleetGirls/blob/26d741efb21c2879856c33b8cf797995718627fb/server/conf/db/migration/default/V99__create_ship_history.sql

バグの内容

バグの修正commitは以下。

https://github.com/ponkotuy/MyFleetGirls/commit/26d741efb21c2879856c33b8cf797995718627fb

member_idの条件を入れ忘れている。findAllLastest関数はこんな感じ

https://github.com/ponkotuy/MyFleetGirls/blob/26d741efb21c2879856c33b8cf797995718627fb/server/app/models/db/ShipHistory.scala#L73

findAllLastestではwhere条件における最後に生成されたship_historyを返す。このときwhere条件としてcreatedを指定している。追加で引数のwhere条件を付け足している。

member_idの条件を入れ忘れた結果、2つの事象が発生する

  • 全く関係ないデータがヒットすることで常に差分が大きくなるので、全くfilterできてない
  • UNIQUE KEYの先頭のmember_idがwhere条件に入らないことで、Indexが効かなくなる

こうしてTableは肥大化し、非効率的なクエリによって、1時間でサーバリソースを食い尽くすまでに至る、というわけである。