信じられないDB文化「Join禁止」に「固定長DB」、、でも、合うんです。大規模コンシューマ向けサービスのRDB設計
僕らが最近手がけているのは、とても大規模なコンシューマ向けサービスだ。
100万人の契約ユーザが使い、1テーブルに1億レコード以上のデータを貯め、24時間止めることが許されず、
要求から応答までのターンアラウンドタイムが1秒以内という厳しいSLAのサービスである。
僕がこの現場に来て、驚愕した文化が2つある
それは「Join禁止」と「固定長DB」だ。
ありえない。
とはいえ、正直に言えば「またか、、、」という感想でもある。
RDBを知らないレガシーな人たちが設計したDBではよくありがちな設計だからだ。
と僕は早々にこの文化と戦って、絶対に覆してやろうと考えてた。
過去の経験上それはたやすいハズだった。
しかし、この文化と戦うこと3ヶ月間。
屈した。初めて屈した。いや、屈したというよりは理解した。
大規模コンシューマ向けサービスのRDBというものは、通り一辺倒の教科書的なやり方ではいけない
むしろ「Join禁止」も「固定長DB」も、この現場ではとても理にかなった最先端のやり方なのだと理解したのだ。
Join禁止の理
Join禁止のポリシーの理屈は一言でいうと
RDBはスケールアウト(サーバ増設)しにくいから、DBには考えさせない。
ということだ。
JavaEEサーバなどのAPに負荷を負わせるようにする。
APサーバのスケールアウトは比較的設計しやすい。
しかしRDBのスケールともなればORACLE RACが登場してきて一気にコストが跳ね上がる。
だから、RDBには何も考えさせない。
データはシンプルな条件文で取得してきて、Javaで上手くjoin相当のことをする。
ここはコンシューマ向けサービスの特性が効いてくる。
コンシューマ向けサービスは、エンタープライズ向けシステムと違って、
蓄積されるデータも、操作するデータも基本的に1ユーザに紐づく少量のデータである。
少量データと少量データならJavaでかき集めてきて整形しても知れた作業なのだ。
エンタープライズ向けで帳票なんかを出すのに、このやり方ではどうにもならない。
JOINして、GROUP BYして、サブクエリーで上手いこと1発取得するほうが高速であることが多い。
コンシューマ向けサービスのほうに話を戻す。
JOINそのものが問題なのではなく、
どちらかといえば不適切な実行計画が問題の本質なのだ。
実行計画までは、あらかじめ設計段階の思想を端々の実装まで浸透させるのは難しいため、
まずい実行計画でSQLを流すのを阻止するという考え方が、JOIN禁止を生み出したのだ。
JOINを使わなければインデックスの設計が非常にシンプルになり、統計情報の随時の更新が必要なくなる。
このため、我々のサービスでは統計情報の自動的な採取はオフにしている。
1テーブルが1億レコードや、将来的には10億レコードにもなるようなシステムでは、
統計情報をいくらサンプリングで算出したとしてもDBサーバのcpuのうち少なくとも1つは使い切ってしまう。
もちろんORACLなどでは、統計情報の算出に1cpuだけに限定させるやり方もあるが、
DBに考えさせないという基本方針からは外れてしまうので、統計情報は取らないことにした。
統計情報が随時で変更されることの意味は、cpuの問題だけではない。
統計情報が変わると、実行計画が変わるのだ。
動的に実行計画が変化して、パフォーマンスがあがったとしても
それは僕らの対面する厳しいお客さんにとっては「不安定」とみなされる。
ブラックボックスを作らず、すべてを設計の範疇でコントロールすることは
性能設計の中では意外と重要だ。
性能問題が発生したときには、必ず説明を求められるからだ。
ハイパフォーマンスで不安定よりも、
中パフォーマンスで安定しているほうがお客さんには喜ばれるし、
ハードウェアスペックで底上げするほうが、近年は安くつく。
統計情報を自動取得しない変わりに、
あらかじめ手動で計算し、負荷試験をクリアした統計情報をセットすることにした。
データは増加したとしても、すべてのテーブルで統計情報をセットしていれば、
テーブル間のデータ量の関係性が逆転しない限り実行計画に大きな変化はない。
安定した実行計画というのが大事なのだ。
この設計方針を守っていたため、厳しいSLAながら性能面ではかなり要求を満たせている。
だた、唯一僕らのシステムで性能問題を引き起こしているのは、パッケージ製品の部分。
これらについてはAPIの内部まで踏み込んでSQLを改造するわけには行かなかった。
このパッケージ製品の内部は「普通」に作られているため、JOINを多用している。
やや、インデックスの設計が当初からおかしいところがあったが、
100万ユーザ、TAT 1秒という性能要件でなければそれなりに動いたはずだ。
しかし、結果的に僕らのサービスではJOINは悪といわれてもおかしくないような結果となった。
コンシューマ向けサービスには、MemcachedやBigTable、Oracle Coherenceなどのインメモリ型の分散ストレージ、
KVS(キー・ヴァリュー・ストレージ)が良いとされる。
スケーラビリティーは、KVSのほうが圧倒的に高い。
僕らが作ったサービスも、RDBである限りスケールには限界がある。
しかしながら、分散ストレージではRDBで必須の考え方であるACID属性がやや弱い。
(このあたりの説明はわが後輩のid:jyukutyoのクラウドでの新しいACID、そしてBASEトランザクションとCAP定理 - Fight the Futureに詳しい。)
それで問題無しとするサービスは、KVSをオススメする。僕らはそうではなかった。
固定長DBの理については書く時間が無くなったので後日にする。