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

カスタム検索

2015-06-17

RDBにおけるキャッシュという考え方

RDBの専門家として日々活動している中で気づいたことのひとつに、「RDBはデータへのアクセスの実装をインデックスに頼っているが、インデックスは全ての問題を解決できるほど万能ではない」ということがある。インデックスというのはとても強力な部品であり、その点には全く異論はない。だが、世の中の全ての問題(クエリ)を解決できるほど、柔軟性に富んだものではないということだ。RDBは、どのインデックスを使ってデータへアクセスするかということを、オプティマイザを用いて判断する。大抵のRDB製品では、オプティマイザはよい仕事をするので、インデックスとオプティマイザの組み合わせによって、ほとんどの問題に対応できる。だが、100%ではないのであり、そのようなケースがシステムの性能問題を引き起こしたり、プログラマ(アプリケーションの設計者)に、NoSQLへ完全に移行したり、クエリ高速化のために非正規化をすると言った、誤った判断に走らせたりする。

こういった問題にとって、全ての元凶となっているのは、インデックスは便利で強力ではあるが万能ではないということに、プログラマが気づいていないことである。

性能が出ないクエリの例

RDBを使用していて困るケースの中に、複数のテーブルを結合したり複雑なサブクエリを用いた場合で、なおかつ多量の行をソートを要するような場合に、上手く性能が出ない場合があるというものがある。それもそのはず。そのようなクエリは上手くインデックスを使用できないからだ。

確かにインデックスは結合やサブクエリ、ソートに利用できる。だが、インデックスは一つのテーブル内のデータを対象にしたものであり、複数のテーブルを組み合わせた結果に対してソートしたい場合などには無力なのである。RDBは最適なインデックスがなくても、クエリの結果を出力してくれる。問題は、そのような場合にはクエリの実行速度が遅いということだ。RDBには、適合するインデックスが無い場合であってもクエリを高速に実行できるというような機能は、根本的に存在しない。

実装の足りない部分を補う

一般的にプログラマが仕事に取り組むとき、まずは自分が作成しようとしているアプリケーションにとって必要な部品が既に存在しないかということを考えるだろう。極端な話をすれば、アプリケーションプログラムを作成しようというときに、オペレーティングシステムやシステムライブラリを自分で一から作成しようという人は居ない。なぜならば、既にこの世には優秀なOSやシステムライブラリが存在しているからである。(30年前ならわからないが、今ならWindowsもLinuxもある。)そのような低級な部品だけでなく、もっと高級なライブラリも多数、様々な用途向けのものが存在する。例えばRDBもそうであるし、ウェブサーバーやアプリケーションフレームワーク、三次元描画ライブラリ、画像再生ライブラリなどである。従って、プログラマの最初の仕事は、自分がこれからつくろうとしているプログラムにとって、どのような部品が必要であるかを特定し、既存の部品の中からどれを使うべきかということを選択することである。

当然ながら、この世に既に存在するプログラムと同じ機能のものを作るということは滅多に無いので、部品を組み合わせるだけではプログラムは完成しない。それに部品だって完全ではない。なので、プログラマは足りない部分を自分でプログラムを書いて補う必要があるだろう。実装の足りない部分を補うという行為は、いつだってプログラマにとって大切な仕事だ。

データベースアプリケーション開発者の盲点

実装の足りない部分を補うということは、データベースを使ったアプリケーションにとっても同様である。データベース実践入門を読んで頂いた方であれば理解されているだろうが、RDBにとってインデックスというのは、クエリを高速に実行するための実装である。それは、プログラマが利用できる部品の一つに過ぎない。

ところが、データベースを使ったアプリケーションを開発する場合には、何故か人々はデータベースの機能、すなわちインデックスだけで何とかしようとしてしまっている・・・ように見えることが多い。まるで「インデックスは万能であり、インデックスによって解決できない問題は、人類の叡智をもってしても解決することはできない!!」とでも考えているかのようだ。

インデックスは強力ではあるが、構造はとても単純で、原始的な部品である。データがリーフノードに順番に並んで格納されていて、検索を高速化するための道標、すなわちノンリーフノードがあるに過ぎない。冒頭でも例を一つ挙げたが、このような単純な構造の部品で、様々な構造を持ったクエリを全て高速化できるわけがないのである。Bツリーに合ったクエリもあればそうでないクエリもある。この点が、見落とされがちなのだ。

ではプログラマはどうすれば良いのか。インデックス・・・つまりRDBの実装がアプリケーションにとって十分でないのなら、その足りない部分を補うべきなのだ。日々、他の仕事でそうしているように、データベースを使う場合にも同じようにするべきなのである。

高速化のための中間的なデータ構造

実装の足りない部分を補うと言っても、何もゼロから全てを作り出す必要はない。既に使える機能と、やりたいことの間に橋を架けるだけで良い。RDBで利用できる部品としては、やはりインデックスの存在は見過ごせない。工夫次第で活用できるなら、ぜひインデックスを利用すべきだ。

冒頭の例では、複数のテーブルにまたがった検索やソートではうまくインデックスが使えないケースがあるということが問題であった。単純にインデックスが使えないからと言って、インデックスが全く使えないというわけではない。データの構造がインデックスに向いていないことが問題なのだ。だったらインデックスに合うようなデータを用意してやれば良い。

だがここで多くの人が間違いを犯す。そう、高速化のための非正規化という間違いだ。非正規化をしてしまうと当然ながら更新異常が発生するようになり、RDB本来の旨味が失われてしまう。RDB上のデータは正規化してナンボである。そもそも実装のために設計を捻じ曲げるようなことはするべきではない。

であれば、元のデータ構造を残したまま、特定の検索のためのデータを中間的なデータという形で用意してやれば良い。論理データと中間データを同期すれば、どちらのデータからクエリを実行しても、期待した結果が得られるはずだ。つまり元の正規化したテーブルを捨て去って非正規化するのではなく、元の正規化したテーブルを残したまま、非正規化したデータを中間的なデータ、すなわちキャッシュとして新たに追加するというやり方である。この方法であればRDB本来の旨味を活かしつつ、正規化したデータでは高速に実行できないような検索を、高速化することができるというわけである。

というような話を、理論から学ぶデータベース実践入門の第12章に書いたので、興味のある方は是非読んでみて頂きたい。

0 コメント:

コメントを投稿