ちょっと硬派なコンピュータフリークのBlogです。

カスタム検索

2013-12-01

その選択、ちょっと待った!NoSQLデータベースへ乗り換える前に検討すべき3つのポイント

最近、どうも安易に「NoSQLにすれば厄介なDB設計から開放される」と考えている人が多いように思えて仕方がない。だが待って欲しい。本当にNoSQLと呼ばれるデータベースを使えばアプリケーションの開発・運用の苦しみから逃れられるのだろうか。もちろん「そんなことは無い!!絶対にだ!!」と私は考える。今日はその理由について語ろうと思う。

トランザクション

先日、リレーショナルデータベースにおけるDB設計についてセミナーで解説したばかりだが、リレーショナルデータベースにおけるデータの整合性は何もDB設計だけが担保しているわけではない。リレーショナルモデルと同じかそれ以上に欠かせないのがトランザクションだ。

トランザクションがあるおかげで、トランザクション終了後のステータスは「成功」か「失敗」の2つしかないということが保証される。すなわちオール・オア・ナッシングだ。もしトランザクションの途中で何らかの問題、例えばアプリケーションサーバーがクラッシュしたり、データベースサーバーがクラッシュすると言ったトラブルが発生した場合、アプリケーションは「成功」か「失敗」の2つの状態しか考える必要がない。つまり処理が中途半端なところで止まっている可能性については、一切考える必要がないということになる。

トランザクションでは、データの整合性の取れた(いわゆる正しいデータを持った)データベースに対して、一連の更新処理をすべて実行して初めて、データベースが「次の」整合性のある状態へ遷移すると考える。中途半端な状態で終了したということは、つまり何らかの不整合が生じているわけだ。更新が中途半端な状態で終わってしまったとき、トランザクションが実装されているデータベースではそれを「前の」整合性のある状態へと自動的にロールバックしてくれる。だが、そのような機能がなければ、アプリケーションが同等のロジックを実装するか、何らかの代替の方法で不整合を解決するしかない。これは非常に大きな手間である。

ちなみに、MongoDBで実装されているような「アトミックな操作」があればトランザクションは要らないと考える人が居るようだが、これも誤りである。一部の更新処理だけアトミックで実行できたとしても意味はない。一連の操作全てがアトミックで出来ること、すなわち中途半端な状態にならないことが保証されて初めて、データベースを整合性のとれた(正しいデータを持った)状態に保つことができるのである。

少々の矛盾なら目を瞑ってしまおうと考える人も居るだろう。だが、ひとたびデータベースに矛盾が生じれば、矛盾したデータを元に新たな更新処理を行うことで、矛盾がデータベース内にどんどん波及してしまうことになる。そして、いずれ取り返しがつかなくなってしまうだろう。当然のことだが、データベースでは「データが正しい」ということが非常に重要なのである。

データモデル

先日公開したスライドをちゃんと理解してくれた方であれば、リレーショナルモデルにおけるデータベース設計理論、即ち「正規化理論」と「直交性」を実践することで、重複を排除し、更新によって矛盾が生じないことを担保することができるという意見に同意してくれるのではないかと思う。重複が排除されたデータベースでは、あるデータを変更するには一箇所だけ(あるテーブルの1行だけ)を更新すれば良い。

ところが、例えばドキュメント型データベースはデータを「非正規化」された状態で持つというコンセプトに基づいて作られている。非正規化されたデータというのは言わば重複の宝庫であり、同じ意味を持ったデータがデータベース内に多数出現する可能性がある。同じ意味のデータは同じ値でなければならないが、同じ意味のデータを格納した箇所が複数あると、一部だけ別の値に書き換えてしまうことで「矛盾」が生じてしまうことになる。

矛盾が生じてしまうと非常に厄介だ。どちらの値が「正しいか」ということは、データを見ただけでは分からないからだ。当然、データに矛盾があればクエリの結果は信用できないものとなる。そして、そのような信用できない結果は使い物にならない。先程も述べたが、ひとたびデータベースに矛盾が生じれば、矛盾したデータを元に新たな更新処理を行うことで、矛盾はどんどん波及してしまう。データベースが矛盾に蝕まれてしまうのである。

そのような状況にならないようにするには、ロジックをしっかり実装すれば良いではないかと考えてしまうかも知れないが、それは実質的に不可能であると思う。なぜならば、その目論見はバグによって即座に崩れ去ってしまうことになるからだ。整合性を担保するのがロジックということであれば、ロジックの欠陥、つまりバグは致命傷となる。バグのないプログラムというものなどはそうそう書けるものではない。ソフトウェアの規模が大きくなればなるほど、バグが入り込む危険性も高くなる。NoSQLを用いた場合にデータの整合性を担保するようなロジックは膨大なものになるので、当然バグが入り込む危険性も高くなる。

具体的には、非正規化されたデータベースにおいて、矛盾が生じないように更新を行うには、同じ意味のデータを、同時に、かつ同じ別の値へと書き換える必要がある。一つでも漏れが生じるとアウトだ。ところが、ドキュメント型データベースは、厄介なことにスキーマレスとなっているため、同じ意味のデータを漏れ無く探しだすというのは極めて難しい作業となる。オブジェクトが入れ子構造になっているときどの階層にデータがあるのか、配列の要素になっているのか、あるいは配列の中のオブジェクトのどの位置にあるかなど、考慮すべきことが山のようにあるからだ。データの整合性について考慮するならば、実際には予想外のところにデータを配置するわけには行かないだろう。だが、スキーマレスであるが故に、データがどこに配置されているかをデータベース側で制限することはできない。そのため、矛盾が入り込むことをその仕組み上防げないのである。

一方で、きっちりと正規化されたリレーショナルデータベースであれば、その仕組みによって矛盾が入り込むことを防ぐことができる。どちらが手がかからないやり方なのかは一目瞭然である。また、複数出現するデータを毎回書き換えるというのは性能面でも不利になる。正規化されていれば1行の更新で済むところを、何度も繰り返し更新を行わなければならないからだ。性能面(特に更新において)でも非正規化されたデータというのは不利なのである。

ところで、別のエントリでも嘆いたように、世の中にはリレーショナルデータベースを使いながらも、データベース設計を蔑ろにしている現場が多く見受けられる。そのような現場では、リレーショナルモデルの恩恵は受けられないので、開発は修羅場と化すだろう。また、「どこまでリレーショナルモデルの範疇としてモデリングするか」というのは極めて重要なポイントとなるが、今までそのような視点で行われた解説はほとんど内容に思う。ぜひこの切り口からリレーショナルモデルについて復習してみて欲しい。先の勉強会で使用したスライドがお役に立てば幸いである。

同時実行性能

先に挙げた2点ほど重要ではないが、「期待したほど性能が出ない」といったケースに陥らないために重要な点を指摘しておく。具体的な例を載せると角が立つので敢えて紹介はしないが、「NoSQLは速い!!」みたいな記事にあるベンチマークを見ると、どうも同時実行性能に関する考慮が抜けていることが多い。

データベースでは、単一の処理が速い、つまりレスポンスが良いということと、並列操作にリクエストを送信してどれだけ処理をたくさん実行できるか、即ちスループットが良いかということは別の話である。リレーショナルデータベースはトランザクションの開始・終了のオーバーヘッドが大きくなる傾向にある。そのため、一つのスレッドで連続して同じ操作を行うというのは苦手になりがちである。例えば1行ずつデータを挿入するといった操作は極めて苦手な種類の処理だ。実際のシステムでは、リクエストは並列して送られてくることになるので、直列化した処理がどれだけ速いかを競うベンチマークはあまり意味がない。スループットが重要なのだ。従ってデータベースを評価する際には、現実に近い負荷でベンチマークを実施することをおすすめする。

また、シャーディングによるスケールアウトにも注意が必要だ。よくシャーディングすることによって性能の問題が全て解決するかのような謳い文句を見かけるが、どのような負荷においてもその効果を最大限に発揮できるものではない。例えば、シャードキーを指定しないような範囲検索を解決するには、全てのシャードを調べなければならないため、そのような検索はスケールしない。そのような検索は、フェッチする(条件に該当する)行数が少なくなればなるほどオーバーヘッドばかりが大きくなり、最終的にひとつのシャードにつき単位時間あたり何回の検索ができるかがボトルネックになってしまう。そのため、シャードを増やしてもスケールしない。

また、特定のシャードにアクセスが集中した場合も当然ながらスケールしない。その場合も、そのシャードで単位時間あたり何回の検索ができるかがボトルネックになってしまう。

スケールアウトによって全ての性能問題が解決するわけではないという点は非常に重要である。スケールアウトを売りにしているNoSQLデータベースを使う場合にはこの点に注意して欲しい。

まとめと考察

ここまでNoSQLデータベースを使う際の問題点について述べてきたが、NoSQLデータベースの利便性を全て否定しているわけではない。私が主張したい点は、NoSQLデータベースはリレーショナルデータベースの置き換えにはならないということだ。いくら複雑なスキーマを表現できるとはいえ、ドキュメント型データベースであっても置き換えにはならない。(理由は先に述べた通りだ。)

大規模なアプリケーションを構築しようとすると、どうしてもリレーショナルモデルに頼らざるを得ない部分、つまり正規化によってデータの整合性を保証したほうが都合が良い部分と、そうでない部分が出てくることになる。きっちりと正規化を行うべきところを、それが面倒だからと作業を放棄してしまっては、結局はより大きな苦労を背負い込むことになるだろうということを言いたいのである。

NoSQLデータベースは、リレーショナルデータベースの置き換えにはならないが、そのデータモデルに合った使い方をする限りはとても便利なツールである。特に、リレーショナルモデルでは上手に表現できない、あるいはNoSQLのほうが高速に実行できるような処理であれば、必要に応じてNoSQLを採用するべきであると思う。

最も理想的な使い方は、リレーショナルデータベースと併用するということだ。余程特殊な用途でない限り、リレーショナルモデルが不要となるようなケースは少ないだろう。リレーショナルデータベースを軸として使いつつ、NoSQLデータベースを高速化のためのキャッシュとして利用するような使い方であれば、データの整合性が犠牲になることはない。なおかつ、NoSQLが得意とする処理をNoSQLに任せることで、処理の高速性を同時に享受することができるだろう。

例えばNoSQLデータベースを使用する部分はすべて「キャッシュ」ととして扱うような使い方が考えられる。NoSQLデータベースが得意な検索を、NoSQLに任せるというパターンだ。その場合、リレーショナルデータベース上のデータから全て再構築できるような構造にするべきということになる。他にも、単にデータの種類によって格納先を分けるというような使い方も考えられる。

リレーショナルデータベースは、リレーショナルモデルにフィットするデータだけでなく、それ以外のデータについても扱えるようになっているため、基本的には何でも卒なくこなすことができる。(SQL≒リレーショナルモデルだからだ。)だが、餅は餅屋に任せたほうが実装が容易になったり、処理を高速化することができたりするケースは多い。だからNoSQLを使うべきシーンはたくさんある。だが一方で、NoSQLでリレーショナルモデルを実践することは実質的には不可能である。従ってNoSQLデータベースを使う場合には、リレーショナルデータベースも併用するべきなのである。

世の中に銀の弾丸はない。データベースにおいてもまた然りである。「リレーショナルデータベースからスキーマレスなドキュメント型データベースへ移行したほうが良い」などという意見は、根本的に間違っている。ドキュメント型データベースはリレーショナルモデルの代替物ではないし、絶対的に他より優れたデータベースなどというものは存在しないのである。リレーショナルデータベースには確かに苦手な処理があるのかも知れないが、それはNoSQLとて同じことだ。NoSQLを使ってリレーショナルモデルのようなデータの整合性を担保しようとするのは苦行と言える。自ら苦しみを求める嗜好のある人は止めないが、そうでなければ(単にプロジェクトを成功させたいのであれば)全面的にNoSQLデータベースだけを使うような行為に走らないのが賢明だ。

重要なのは、処理に応じて適切なツールを選ぶということである。処理に応じてどのデータベースにするかを選択する、あるいは複数のデータベースを併用するといった判断とそのノウハウが、これからはますます重要になっていくことだろう。

少しでも「NoSQLを使えばリレーショナルデータベースを使うより幸せになれる」という誤った考えを持つ人が少なくなることを願うばかりである。

2 コメント:

Ryousuke Wayama さんのコメント...

handlersocket使えばNoSQLの性能でinnodbに突っ込んでから通常のSQLでも検索でき、トランザクションが必要な場合は通常のSQLプロトコルでやって、データが失ってもいいようなストリーム的なデータやキャッシュであればhandlersocketで突っ込めばいいので使い勝手がハイブリッドで重宝しています。

乳牛 さんのコメント...

私も何でもNoSQLに置き換えて俺カッコイイみたいなのはどうかと思います。
例えばデータ整合性の重要度はモノによりけりで、細かい整合性には目をつぶるからそれよりも分散による拡張性が欲しい、とかそういうトレードオフがあるはずのものですね。

コメントを投稿