Scaleするかどうか、それが問題だった


私は過去にデータ量に起因するパフォーマンス劣化問題を経験してからはスケーラビリティを第一の命題としてきました。 Scaleするかどうか、それが問題だ

クラウドは、どんなにアクセス数やデータ量が増えても未来永劫サクサク動くスケーラビリティが魅力です。
私はこのクラウドのスケーラビリティの特長をエンタープライズにも適用することはできないかとずっと考えてきました。
しかし、あまりうまくいっていません。

今日は、このあたりについて述べたいと思います。

スケーラビリティを望むならRDBはいらない

実はエンタープライズの現場で重視されるのはスケーラビリティより可用性です。
そして、可用性よりも重要なのは、一貫性、つまりデータの整合性やデータロストがないことです。
CAPの定理でいえば、以下のような順番です。分断耐性≒スケーラビリティと思ってください。

一貫性 (Consistency)>可用性 (Availability)>分断耐性 (Partition-tolerance)

なので、システムにおける第一選択は当然ながらRDBになります。
とりあえずRDBにしておけば、安心、安全にデータを管理できるからです。

しかし、私はスケーラビリティを阻害する元凶はRDBだと思っていて、RDBの代りにKVSをシャーディングして使うことを推奨しています。
【BPStudy#64】RDBじゃだめな理由を2つほど

とにかく、RDBに依存するのが嫌で嫌でしょうがないというのが私のスタンスです。
オブジェクトをそのまま登録できるデータストアさえあればいいのです。
RDBは必要ありません。そうすれば、O/Rマッパーも不要になります。
ただし、これはオンラインのバックエンドに限った話であって、データ分析や統計ではRDBを使うべきだと考えています。

(qiitaでO/Rマッパー不要とかいう記事が炎上していたので同意見だと思って読んでみたらSQL至上主義の方でした(笑))

KVSは単純なログファイルに書かれるだけなので、スケールアウトして複数ノードになったとしても運用は楽です。

KVSは一貫性が課題といわれますが、vte.cxが標準で採用しているBDB(BerkleyDB Java Edition)であれば、一貫性の問題が解決できることがわかっています。これは、DBMSと同等のACIDがあり、トランザクションログからのリカバリもDBMSと同じようにできます。

つまり、私の提案では以下の順番になります。

一貫性 (Consistency)>分断耐性 (Partition-tolerance)>可用性 (Availability)

CAP定理については誤解が多く、CAP定理を見直す。“CAPの3つから2つを選ぶ”という説明はミスリーディングだったにあるように、単純にCAPの3つから2つを選ぶのではなく、状況に応じた選択肢やさまざまな度合いがあることには注意する必要があります。
しかし、提案では各ノードのBDBがシングル構成になっており、可用性を下げることでスケーラビリティを優先させています。

そして、あるお客様において数年間、この構成で運用してきたわけですが、急激なアクセス増加にも単純なノード追加で対応できたのは大変よかったと思っています。特に、アクセス増加を予測できないB2Cのシステムでは、このようなスケールアウト可能な仕組みが重要だと痛感している次第です。

やっぱり可用性が大事というのでRedis+RDBを提案

しかし、この構成は純粋にスケールアウトできる反面、前述したようにBDBがシングル構成になっている点で可用性に課題があります。数年間、これまで一度もダウンしたことはありませんでしたが、可用性の問題はずっと指摘されつづけてきました。

じゃあということで提案したのが、RedisとRDBの組み合わせです。私は敗北感に苛まれつつ、ありきたりの構成を示しました。
当然、Redisも2重化してRDBも複数のレプリカをもつ構成になります。

しかし、Redisには無限の容量があるわけでなく、スループットにも限界があります。
また、永続化すればI/Oが詰まるので、どう溜めるかをよく考えないといけなくなります。(つまり、ACIDのDをどうするか)
それに、障害対策についても頑張って作る必要があるわけです。結局、ログに全部吐きましょうかということになります。
つまり、RedisだけではBDBの代わりになることができないのです。

最終的にRDBに書き込むとしても今度はRDBがボトルネックになります。
RDBの参照系は複数のレプリカで分散可能ですが更新系は1台しかありません。1局集中を避ける方法を見つけるのは至難の業です。

それでも、Redisを採用する方が素のRDBだけよりは100倍ましです。
いずれにしても、単純なスケールアウト戦略を取れなくなったのは非常に痛い。まるで大阪城の外堀を埋められた真田幸村の気分です。

パフォーマンスで孤軍奮闘

こうなってしまった以上、RDBがボトルネックにならないように、いかにRedisを使って更新系の負荷を軽減できるかが重要になるのですが、ひとたびRDBが導入されると堰を切ったように使われるようになります。
「RDBにお任せでいいじゃん」圧力とでもいいましょうか、RDBの魔力、引力はすさまじく、開発者は何でも解決できると思いこんで安易なSQLをぶちこんできます。

いくら、私がパフォーマンスが大事、スケールアウトが大事とガミガミいってもまったく響きません。
例えば、フレームワークで炎上を防ぐで示したようなルールで縛ることでなんとか防波堤を築いているのですが、制約があると不便でいやだとか、JOINは自由にやらせてくれとか、採番ぐらいRDBでやってもいいでしょと、こんな感じです。特に、RDBに書き込む処理についてあまりに無頓着なのです。

「君たちは、普通に作るだけでは遅くて使えないシステムになるという事実を知ってますか?」
一度、高負荷で動かなくなったシステムを運用してみて、どうしようもない苦しみを味わないとわからないのでしょうけど、そうなってからでは遅いのです。設計の段階から遅くなる要素をすべて敵とみなして戦わないといけないのです。

このように、パフォーマンスという私だけが見えている世界で孤軍奮闘しているわけですが、無垢な開発者のやっつけ根性は本当に疲れます。手に負えない大阪城の浪人衆のようです。後で痛い目にあうことも知らずに、というか運用までは関知しないよということでしょう、たぶん。

スケールアウトなんてできなくていいさ、どうせ5年間動けばいいんでしょ?という無責任な発言にはさすがにキレましたが、エンタープライズの多くの開発者の意見はだいたいこんなものなんだろうなとは思いました。

とりあえず結論

実は、BDBからRedis+RDBに変更しても、フレームワークが違いを吸収するので、アプリケーションはほとんど変更する必要はありません。
テーブル定義は簡単には変更できないため、ソフトスキーマだけで管理していたのときよりも自由度はなくなりますが、それでもフロントエンド開発者やサービスの開発者はvte.cx/ReflexWorksのアーキテクチャーで、これまでと同じように開発していけます。
それは、フレームワークが下位のレイヤーをアプリケーションから隠蔽しており、APIの互換性が担保されているからです。

Redis+RDBになるとスケーラビリティは損なわれますが、開発には影響しないので、まあ、ミッションクリティカルなエンタープライズシステム向けとしてはこの形もアリかなとは思っています。
ただ、更新系RDBがボトルネックになって、どうしようもない状態になった場合に、救済する方法を考えないといけません。

その方法については、Redisと更新系RDBについてはシャーディングで対応するのが一番現実的だと考えています。
もちろん、フレームワークでシャーディングするようにして業務アプリには影響しないようにしなければいけないでしょう。

ちなみに、BaaSのvte.cxについては、これまでどおりBDBによるスケールアウト構成で運用していきます。

それでは、また明日。