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

カスタム検索

2016-10-05

データベースについてのそもそも論

先月のはじめのほうで、「リレーショナルデータベースとの上手な付き合い方」というタイトルで、2回発表をした。ひとつは「まべ☆てっく Vol.1」であり、もうひとつは「Hacker Tackle(ハカタクル?)」である。

「リレーショナルデータベースの開発・運用に纏わるもろもろの話をして欲しい」というような内容の話をしてくれないかという同じような依頼を、ちょうど2日違いのイベントで頂いた。9/8のまべ☆てっくと、9/10のHacker Tackleである。そうなると必然的に話す内容も、同じようなものになってくる。同じ人物(=私)が話すのだから、テーマも同じで時期も同じであれば、内容が同じようなものになるのが自然である。もし違うものになってしまっているのであれば、片方はウソをついているということになるはずだ。今日は発表に使用したスライドを紹介しつつ、なぜデータベースを使うべきなのか(あるいは使うべきでないのか)ということについて、語ろうと思う。

まずはスライドの詳解から。

まべ☆てっく Vol.1で使用したスライド



Hacker Tackleで使用したスライド



なぜ後者のスライドがロングバージョンになっているかというと、与えられた時間が全然違うからだ。前者は20分、後者は50分。スライドの量に違いが出るのは当然である。結果的に、前者のスライドは要約された形になっているので、時間の無い人は前者のスライドを見て欲しい。内容をじっくり吟味したいという人は後者のスライドをどうぞ。

RDBMSを使わない開発や運用は大変

最近、NoSQLが流行したせいで、以前にも増してリレーショナルデータベースが持つ機能を軽視するような発言が増えたように思う。伝統的に未熟なリレーショナルデータベースのユーザーによって囁かれてきた「データベースなんてデータの入れ物だ。だからデータモデルなんて気にしなくて良い。」というものに加え、「トランザクションがなくても開発はできる。だからNoSQLでも問題ない。」という考えを持つ人が、とても増えてきたように思う。だが、そのような傾向は歓迎すべきものではない。なぜならば、既にある良好なテクノロジーの実装を使わないという選択は、開発やメンテナンスが非効率になってしまうからだ。

例えばトランザクションはデータの整合性を守るために必須の技術だ。トランザクションを実装したデータベースを使わずにアプリケーションを開発するということは、データの整合性を捨てるか、あるいはトランザクション相当のものを自分で実装するということを意味する。よく、「アトミックな処理ができればトランザクションは必要ない」とか「自分でロックを管理して排他処理をすればトランザクションは必要ない」というようなことをいう人がいるが、それは大きな間違いである。アトミックな処理やロックだけでは、当然ならが様々な点が欠けている。トランザクションと同じこと実現できるのは、トランザクション以外にはないからだ。

トランザクションと同等の機能を実現しようと思ったら、トランザクションそのものを実装するしかない。それはつまり、車輪の再発明をするということに他ならない。アプリケーションを開発するたびにトランザクションを実装するのは、壮大な無駄であると言えるし、もし出来上がったとしても性能や品質という点で、大きな不安が生じるだろう。できあがったアプリケーション内のトランザクション機能が、バグだらけで性能の出ないものでは独自に実装する意味がない。

品質や最適化は、一朝一夕で達成できる課題ではない。それらの課題を達成するには、開発者による膨大で、そして地道な努力が必要なのだ。餅は餅屋という言葉が示すように、トランザクションを実装し、長年メンテナンスしてきたRDBMSには一日の長があるのを忘れるべきではないだろう。そもそも商用のRDBMSであればベンダーがメンテナンスをしてくれるし、オープンソースプロジェクトとして開発されているRDBMSであれば、バグ登録をすればそのプロジェクトの開発者がバグ修正を行ってくれることが多いし、商用のサポートを提供している会社もある。そういった専門家によるメンテナンスに代わり、自分でトランザクション機能のメンテナンスを行っていくだけの技術とコストを、あなたはお持ちだろうか?

トランザクションが無いということ

トランザクションを使わずに開発するのは、ものすごく大変なことである。例えばトランザクションの性質として「原子性」が挙げられるが、もしこれがなかったらエラー処理が大変なことになってしまう。なぜなら、アプリケーションは処理がどこまで進んだかによって、エラー処理の内容を変えなければならないからである。10の更新から構成されるトランザクションであれば、10個のケースについてそれぞれ場合分けしてエラー処理を記述しなければならない。それに加え、そもそもどこまで処理が進んだかが分かるようなつくりにしておかなければならないだろう。一方、原子性があれば何も考えずに同じ処理を最初からやり直すだけでOK牧場なのだ。この差は実に大きい。

また、当然ながら排他制御も自分で行わなければならない。万が一、排他制御のコードにバグがあれば、待ち受けるのはデータ破壊だ。トランザクションを使えば、排他制御はデータベース側で勝手にやってくれる。しかもMVCCによって参照系の処理能力も相応に高いものが得られるのである。

万が一データベースサーバーがクラッシュしてしまった場合は大変だ。データの整合性を保つには、中途半端な状態の更新処理を取り消すか、あるいは確定するまで先に進めて、整合性の取れた状態にしなければならない。そのような処理を、トランザクション無しに成し遂げるのは至難の業である。

リレーショナルモデルが無いということ

RDBMSを使う上で、トランザクション以外に忘れてはならない必須の理論が、リレーショナルモデルである。データベースなんてただの入れ物だと考えてRDBMSを使ってる人にとっては、その恩恵は分からないところがあるかも知れないが、リレーショナルモデルが適用可能なケースにおいてそれを活用すると、開発が極めて楽になる。

リレーショナルモデルは、集合に根ざしたデータモデルである。データを集合として表現し、集合から別の集合を得る演算(射影や制限など)や、集合同士の演算(結合や直積、集合和など)が定義されている。RDBMSでは、SQLを通じてそういった演算を行うようになっている。よくある SELECT select_list FROM table_reference WHERE condition という書式のクエリでは、select_listが射影、table_referenceが直積(結合と見ることもできる)、conditionが制限に相当する。データの単純なR/Wに比べると、こういった演算は遥かに複雑だと言える。

リレーショナルモデルの素晴らしいところは、データの取得や更新を、宣言的に記述できるというところである。これにより、開発者は「何が欲しいか」に集中することができ、「どのように取得するか」はほとんど関知する必要がない。オプティマイザがごく手頃なコストで、勝手に実行計画を立ててくれるからだ。とはいえ、オプティマイザには限界があるので、思うように性能が得られない場合には開発者や管理者がチューニングを行う必要がある。それにしたって、自分で一からデータの取得に関するコードをすべて記述することに比べれば、効率は天と地ほどの差があるといえよう。KVSを使ってJOIN相当の処理を記述することを想像してもらいたい。コードを記述するのがそもそも大変であるが、デバッグも大変だし、チューニングだって大変だ。データへのアクセスが遅いと感じたら、コードを書き直さないといけないが、それはSQLをいじったりインデックスを追加するより、ずっと大変だろう。

リレーショナルモデルの上に立脚した正規化理論を活用することで、開発はさらに楽になる。データベース設計によって、データに矛盾が起きるのを防ぐことができるからだ。設計によって矛盾を防ぐということの意義はとても大きい。設計によって矛盾が起きないことが担保されていれば、データが壊れていないかどうかをチェックするコードを書く必要が無いからだ。ただし、正規化によって防ぐことができるデータ破壊は、ひとつのテーブルからデータの重複を排除することで、そのテーブル内に矛盾したデータが生じないようにするという類のものだけである点には注意したい。データが壊れる可能性は他にももっとあるので、トランザクションや制約を組み合わせて、その可能性を最小化するよう、努力しなければならない。とはいえ、正規化によって防ぐことができるものを、むざむざ放置するのは愚の骨頂であり、余計な手間(コードの記述やデバッグ、テストなど)が増える結果となるだけである。その手間を減らすことができるかどうかは、やはり大きな違いとなって開発やメンテナンスのコストに跳ね返って来ることになるだろう。

既存の技術が何故あるのかを考えてみる

トランザクションにしろ、リレーショナルモデルにしろ、それらは何かしらの課題を解決するために考案された理論であり、RDBMSにはその理論に基づいた機能が実装されている。そういった背景を無視して、既存の機能を活用せずにアプリケーションを開発することが、本当に正しいことなのかどうかは、よくよく考えたほうが良いだろう。

RDBMSが使えないケース

この世にパーフェクト超人が存在しないように、トランザクションやリレーショナルモデルもまた、万能ではない。リレーショナルモデルは集合に基づいたデータしか扱うことができないので、それ以外のデータ、例えばグラフやツリーを扱うのは苦手である。SQLをこねくり回してグラフを操作することは可能だ。SQLそのものにはグラフ操作のコマンドは存在しない。そのため、リレーションの演算と比べると、グラフの演算は格段に遅くなってしまう。例えば経路探索のような用途にはリレーションの演算は役に立たないのである。

トランザクションだって、複数のクライアントから同時に更新が入るOLTP以外のケースでは、ただのオーバーヘッドになってしまう可能性が高い。例えばログの解析や集計処理などではトランザクションは返って足かせになってしまう。

使えないケースにおいて、無理にRDBMSを使おうとすると、性能が出ないか、あるいはかなりコストが高くついてしまう。何事も適材適所であり、データベースもその例外ではないのである。データベース、特にRDBMSを使うべきかどうかという判断を下すには、RDBMSの機能がどのようなものかを、きちんと理解するべきである。そうすれば、自ずと判断はつくだろう。

ノーガード戦法が許されるのは矢吹ジョーだけ!!

本来、リレーショナルモデルやトランザクションが必要な場面で、それを使わないという判断をしてしまうと、データ破壊からデータを守るため、開発コストの増大に繋がってしまう。それなのに、開発コストの増大を嫌って、特に対策を講じなかったとしよう。いわゆるノーガード戦法というヤツである。するとどうなるか。たまたま上手く動いているうちはいい。だが、コンピュータシステムにはエラーはつきものであり、CPUやディスク、マザーボードは壊れる運命にあるし、OSがクラッシュすることもあれば、ネットワーク機器の問題が生じる可能性もある。もしそういったトラブルに直面しなかったとしても、それは単に運が良いだけである。たまたま運が良いからと言って、何の備えもしていなかったらどうなるだろう。その顛末が明るくないことは、想像に難くないだろう。

必要な対策をしないということは、リスクを抱えるということに他ならない。矢吹ジョーは、カウンターを狙うという目的があるから、ノーガード戦法が活きていくる。しかしコンピュータシステムにはカウンターなどというものは存在しない。隙があればシステムに重大なトラブルが起こるだけである。ノーガード戦法は本質的に無意味なのだ。重大なトラブルは、必ずしもデータの復旧が可能なわけではない。データが失われればユーザーからの信用を失うことになるし、復旧できたとしても、その対応には膨大な労力が必要になるだろう。

そのような悲惨な状況にならないようにするためには、本当に必要な技術を必要な場面で使うべきなのである。

まとめ

世間では、NoSQLデータベースを使ってアプリケーションを開発した、あるいはしているという事例を見かけることがある。そして、如何にNoSQLが素晴らしいかというようなことが付け加えられていたりする。しかし、そのような事例は極めて特殊な例なのであって、RDBMSを使わないアプリケーションの開発は大変であり、誰もが簡単に飛びついて良いものではない。RDBMSを使わず、NoSQLを使って開発を行うためには、越えなければならないハードルがたくさん存在するのである。リスクを無視して突き進むのは勇敢な行為ではなく、ただのバカである。幸運にもたまたま上手く運用できているうちは良いかも知れないが、トラブルが起きたときのダーメージは計り知れない。よほど特殊なケースでない限り、通常のアプリケーション開発(特に小規模なもの)では、まず初めにRDBMSの利用を検討することを、私は強くオススメする次第である。どうか世間のNoSQLの事例に踊らされないようにして欲しい。そして快適なデータベース生活をエンジョイして欲しいと思う。

4 コメント:

Choo さんのコメント...

もしかして「RDBMSが使えないケース」の「SQLをこねくり回してグラフを操作することは可能だた」は「SQLをこねくり回してグラフを操作することは可能だが」でしょうか…?

Mikiya Okuno さんのコメント...

そうです。ご指摘ありがとうございました。修正しました。

Shigeru HANADA さんのコメント...

> select_listが射影、table_referenceが結合、conditionが直積(結合と見ることもできる)に相当する。

の部分(SELECT構文要素と集合演算との対応の説明)ですが、table_referenceが「直積(結合と見ることもできる)」で、conditionは「選択」ではないでしょうか?

一般的に「WHERE句で結合する」という言い方もありますが、
SELECT * FROM table_a, table_b WHERE table_a.id = table_b.id;
といったパターンは、FROM句の直積結果にWHERE句の選択が働き、結果として内部結合と同じ結果が得られている、という解釈なのかなと思っております。

Mikiya Okuno さんのコメント...

ご指摘ありがとうございます。修正しました。選択ではなくリレーショナルモデルでは、「制限」と言います。

これは編集するときに間違ってしまったようです。当初

よくある SELECT select_list FROM table_reference WHERE condition という書式のクエリでは、select_listが射影、table_referenceが結合、conditionが制限に相当する。

と書いていて、結合の部分を「直積(結合と見ることもできる)」に直すつもりだったんですが、誤って制限を修正してしまったようで・・・。

コメントを投稿