NoSQLは、「Not Only SQL」の略(という事にしよう、とされている)。
「RDB以外のDB」を意味する。(主にキーバリューストアを指す)
以下、KVSのDBをちょっと試用しただけの半端な知識による考察です。
実運用に基づく共通知が早く出来て欲しいなぁ。
自分の視点は、開発者。
つまりテーブルの設計方法・使用方法やプログラミング方法(トランザクションの扱いとか)に一番興味がある。
運用(耐障害性とか(単一障害点がどうとか))についてはひとまず考慮外^^;
データベースの主流はリレーショナルデータベース(関係型データベース・RDB)だが、GoogleやAmazonがRDBでないDBを使って成功してから(そしてその論文が発表されてから)、RDB以外のDBが注目されるようになった。
そこで「RDB以外のDB」を指すために「NoSQL」という言葉が使われ始めた。
SQLというのはRDB用の言語であり、RDBの象徴的存在・象徴的な言葉として扱われている。
つまり「NoSQL」という言葉は「SQLでないもの」=「RDBでないもの」という発想で作られたのだと思う。
一方で(英語の詳しいことは知らないが)「NoSQL」と言うと「SQL(RDB)は不要」といった意味で捉えることも出来るらしい。
が、それじゃ否定的過ぎるってもんで、後から「Not Only SQLの略語ということにしよう」という事になったらしい。
「Not Only SQL」を意訳すると、「DBはRDBだけじゃない」「RDBだけがDBじゃない」といったところか。
今では大抵の用語解説サイトで「NoSQLはNot Only SQLの略」と書かれているようなので、定着したと言っていいと思う。
(一過性の流行語として、忘れ去られる可能性もあると思うが(爆))
実際のところ、データベースには色々な種類があって、RDB以前にも有名なところでネットワーク型DBとか階層型DBとかがあった。
単純に「RDB以外のDB」と言うとそれらも含まれてしまうと思うが、NoSQLでターゲットにしているのはRDBより後、主に分散Key/Valueストア(KVS)と呼ばれるDBを指している。
DBMS名 | 備考 |
---|---|
Google BigTable | 論文が発表されているだけで、一般人が直接使うことは出来ない |
Amazon Dynamo | |
Apache HBase | BigTableのJavaによる実装(オープンソース) |
Hypertable | BigTableのC++による実装(オープンソース) |
Voldemort | DynamoのJavaによる実装 |
Apache Cassandra | BigTableとDynamoの合体。Javaによる実装(オープンソース) |
SQL Azure | マイクロソフトのWindows AzureのDB |
ちなみに、MySQL・PostgreSQLはRDBの名前、PL/SQLはOralceの独自言語、psqlはPostgreSQLの対話型ツール(SQL*PlusはOracleの対話型ツール)。
キーバリューストア(key/value store・KVS)は、キー(key)を指定して値(value)を保持・取得できるデータベース。
って言うと、どのDBもそうじゃん!(爆)
KVSもRDBも“どちらもDBだから”という理由で比較されるけれども、構造や出来る事を考える上では、KVSはマップ(連想配列)と対比して考える方が分かりやすいと思う。
乱暴に言うと、KVSは、ファイルシステム上に置かれているマップである。
(古くからこの業界にいる方は、「KVSはISAM・VSAMだ」とおっしゃっている模様。確かIBMの汎用機(メインフレーム)で使われていた用語だったかな)
特に複数のコンピューター(ノード)にまたがってデータを保持するKVSは、分散KVSと呼ばれることもある。
純粋な(原始的な)KVSは本当にキーと値を入れられるだけなのかもしれないが、BigTable等は、カラムファミリー(ColumnFamily)という層があり、その中にプロパティー名と値をペアで登録できたりして、もう少し高機能になっている。
自分はこのカラムファミリーがあるDBのことをKVSと呼んでいる。
RDBの場合、主キーは大抵のテーブルに設定するけれども、インデックス(索引)を張ることにより、インデックス内のデータを指定して値を取得することが出来る。(主キーはインデックスが自動的に作られる)
インデックスの数が増えたりデータの件数が増えると、データ挿入時のインデックス作成やデータ更新時のインデックス変更に負荷がかかるようになってくる。
KVSの場合、インデックスは無い。主キーに当たる項目のデータでソートされて格納される。
ソートされているから、値を取得する際には二分探索の類でデータを探す。
(こうすることにより、インデックスを扱う必要が無くなるので、インデックス爆発という問題が解決される)
したがって、KVSにおいては、キー以外を条件としてデータを取得する事は基本的に出来ない。
SQLでいう結合(join)もサポートされないのが普通。
(KVSはマップだから、値を条件にしようと思ったら全データを走査するしかない)
データはキー順で並んでいるだけなので、分割は容易である。
例えばあるノードにキー1000番から2999番のデータが入っていたとして、データ量が増えたから2000〜2999番だけ別のノードに移す、なんて事も簡単に行える。
(インデックスが無いから、指している場所の変更といった作業は(ほとんど)発生しない)
もちろん欲しいデータがどこのノードにあるかを探す手段は必要になるが、ノード毎のキー値の範囲だけ保持していれば済む。
(インデックスなら全件数分を保持することになる)
具体的にどうやって範囲を保持したり更新したりするかは、KVSそれぞれのDBMSの腕の見せ所のひとつ。
データを保持しているノードが壊れるとデータが失われるので、DBにおいては複製・バックアップおよびリストアの機能は必須となる。
分散KVSでは、同じデータを複数のコンピューター(ノード)に書き込む。つまり複製を作成する。
大抵の場合は3箇所にデータを保持するようだが、どれがプライマリーでどれがバックアップ、といった取り決め(優劣・差異)は無い。
複製先は、データ登録時に接続されていたノードやそこに近いノード、それから遠いノードが選ばれるのが理想的。
遠い・近いというのはネットワーク的な意味で、近ければ転送速度が速い。一番近いのは同一LAN、次いで同一ラックとか。一番遠いのは別のデータセンター(DC)。
データセンターが停電になったり飛行機が落ちてきて壊れたりする障害のことを考えると、別のデータセンターにもデータを置いておく方が安全。
複製先を決める方法も、KVSのDBMSの腕の見せ所のひとつ。
障害が起きた際に生きているノードに複製を作り直したりするのも、DMBSの腕の見せ所。
データが読まれる際は、複製されたノードの内のどれかからデータが取得される。
理屈から言えば、書き込まれたデータの複製なので、どのノードから読んでも同じデータになる。
しかし現実には、ネットワーク的に離れているので、全ノードに書き込まれる前にそのデータを別のクライアントが読み出す事もあり得る。すると、古いデータが読めてしまう可能性がある。
これに関して引き合いに出される例が、DNS。ホスト名からIPアドレスを取得するサービス。
DNSサーバーは世界中にあるが、それらに対する更新は、一箇所から全体へ徐々に伝播してゆく。
最終的には全体が最新の状態になるが、途中の時点では、新しい情報が取れたり古い情報が取れたりする。
RDBではそういった場合はロックをかけてデータを更新するので、コミットしたら、以降は絶対に新しいデータが読まれることが保証される。
(このロックをかける排他制御方式は悲観的並行性制御(pessimistic concurrency control:PCC)と呼ばれる)
しかし分散環境(特に大量のノード)でそういった事を保証するのは難しいらしく、KVSではロックをかけない楽観的並行性制御(optimistic
concurrency control:OCC)が採用されている。
これは分散KVSの宿命として受け入れ、そうなってもよいように設計・プログラミングする必要がある。
とは言っても、分散KVSでもロックをかける方式を提供しているDBMSもある。
KVSにおいては、入れられるデータの型に決まりが無い。
つまりバイナリデータであれば何でも入れられてしまう。
RDBのテーブルの項目名に当たる部分も、実行時に自由に決められる。テーブルの列(項目)と言うよりは、マップのキー値に過ぎないので。
RDB | HBase | Cassandra | 備考 |
---|---|---|---|
スキーマ (ユーザー) |
なし | キースペース | 使用前に定義しておく必要がある。 |
文字列 | 文字列 | ||
テーブル | テーブル | カラムファミリー | 使用前に定義しておく必要がある。 |
文字列 | バイト列 | 文字列 | |
列(項目) | カラムファミリー | なし | 使用前に定義しておく必要がある。 |
文字列 | バイト列 | ||
なし | Qualifier | スーパーカラム | データ登録時に動的に名前を決められる。 |
バイト列 | バイト列 | ||
プライマリキー | ROW | キー | データ登録時に動的に値を決められる。 |
データ型 | バイト列 | 文字列 | |
データ | セル | カラム | HBaseではセルにデータが入っている。 Cassandraではカラムにカラム名とデータが入っていて、異なるカラム名なら複数保持できる。 |
データ型 | バイト列 | バイト列 |
KVSではテーブルの結合(join)が出来ない為、テーブル設計としては、正規化をくずす(非正規化する)ことがあるらしい。
顧客情報・商品情報・購買履歴を管理するテーブルを考えてみる。
RDB |
|
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
HBase |
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Cassandra |
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Cassandra (その2) |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
マップ |
Map<顧客番号, Map<"顧客情報", Map<カラム名, 顧客名or性別>>> |
Map<商品コード, Map<"商品情報", |
RDBでの購買履歴テーブルは、たぶん正規化とかプライマリキーの持ち方とかでもっと良い設計があると思う。
KVSはテーブルの結合(join)は出来ない為、正規化されたRDBのテーブルをそのままKVSに移すのはやめた方が良さそう。
(というか、複数項目にまたがるプライマリキーは、KVSでは実現できない。キーは必ず一項目のみ)
私見だが、KVSで顧客に属する購買履歴のような情報を持つ場合、顧客データとして同一のテーブルに持ってしまうのがいいと思う。
たぶん更新時にキー毎の原子性は担保されるから、別々のテーブルに持った場合の障害時の整合性とかは気にしなくて済むんじゃないか。
RDBでプライマリキーを構成していた複数項目は、KVSでは1つにつないでカラム名として使用する。(KVSはカラム名を動的に追加できるから)
KVSでは、カラム名を範囲指定して取得する方法が用意されてたりする。それはこういう使い方を想定している(んじゃないかなーと期待)。
それを使えば、日付(日時の範囲)を指定して、ある顧客のその日の購買履歴だけ取ってくるのは簡単。
さらにKVSの場合、保持するデータの形式はバイナリデータであれば何でもいいので、クラスや構造体・マップ・リストをシリアライズして持たせてもいいはず。
この形なら、商品コード以外に商品名や金額を持たせることも可能。つまり、照会時に商品情報を取得する為に商品データにアクセスする必要が無い。
(クライアントツールから参照するのは困難になるが(苦笑))
商品コード毎に集計したいような場合は、カラム名の一部が商品コードになっていたり、データの内部でしか商品コードを保持していなかったりして、直接条件にすることは出来ない。
そういう場合はMap/Reduce(分散KVSに対するバッチ処理)の出番。
全データを処理対象とし、Map処理で商品コードを抽出、Reduce処理で商品コード毎に集計を行う。
全データを対象にしたらすごい時間がかかりそうでとんでもない気がするが、そこは分散処理なので。全ノードを並行で処理すれば、全体をシーケンシャル(順番)に処理するより短く済む。
10台あれば約1/10の時間で済む、というように性能が台数に比例するのが分散KVSのスケールアウトの設計思想。
Reduce(集計)処理も商品コード毎に別ノードで並列で処理すれば、最大で一番遅い商品コードの処理時間しかかからない。
ノードを分散させることのメリットのひとつに、処理・負荷を分散させられる、という事がある。
例えば顧客毎の処理を行うバッチ処理を考えてみる。
よくある構成では、バッチのプログラムはバッチサーバーに置き、DBサーバーに対してデータの取得・更新を行う。
|
←→ |
|
しかしこれだと処理がシーケンシャル(順番)になるので、顧客数に比例して時間がかかってしまう。
そこで、バッチ処理を並列化することを考える。
|
← |
|
|||||
… | ←→ | ||||||
|
← |
これなら10台で処理しているから処理時間は約10分の1になる!と思っても、現実にはそうはならない。
大抵はDBサーバーに負荷がかかり、ボトルネックになってしまう。
ならば、DBサーバーも分割してしまえばいい。
RDBの場合はテーブルのパーティション分割機能を使うのかな。
|
← |
|
|||||||||||
… | ←→ | ||||||||||||
|
← |
しかしRDBのパーティション分割は、顧客数が増えた場合に柔軟に対応できない。(例:Oracle10gのパーティション分割)
分散KVSの場合は、本当にDBサーバー毎に別のコンピューター(ノード)にしてしまう。
|
←→ |
|
||||
… | … | … | ||||
|
←→ |
|
※実際には、同じテーブルの中の別の範囲を同一のノードで保持する事はあるかもしれない。
理屈から言えば、RDBでもDBサーバーを複数用意してスキーマ(テーブル定義)は同一にして、入れる番号帯だけ分けてやれば、いける!
しかしどの値がどのサーバーにいるかを管理する必要があるから面倒そうだ。(プライマリキーの値からサーバー番号が算出できれば、それほど難しくはない?その処理をフレームワークに入れちゃうとか)
全件SELECTとか件数カウントとかも全サーバーに対して行う必要があるからちょっと手間がかかりそうだ。
単純なjoinも出来ないな。結合データのあるサーバーがバラバラだろうから。(結合対象になりそうな小さなテーブルだけは1つのDBサーバーに置いとけばいいのか)
複数の顧客にまたがるトランザクションにはグローバルトランザクション(2フェーズコミット)を使えばいい?
テーブルに項目を追加したかったら、全DBサーバーに対して更新する必要があるなー。
(なお、こういったRDBを複数のマシン上で動かす仕組みは既にあって、Shardingと呼ばれているらしい)って、これはほとんどKVSで出来ない(直接サポートされていない)事ばかりだ。
つまりRDBかKVSかに関わらず、サーバーが分散すると協調処理は難しくなるわけだ。
KVSは、そういう難しい事を仕様から外すことで分散化を果たしている(分散を優先している)ということかな。
(テーブルへの項目追加に関しては、KVSは明確な「項目」は持たず、データ登録時に名前を決めて入れられる)
Hadoop(分散処理フレームワーク)の場合、バッチプログラムそのものもDBサーバー(分散ノード)側で動かしてしまう!
|
→ |
|
|||||
→ |
|
||||||
… | |||||||
→ |
|
こうすれば、バッチプログラムとDB間の通信はネットワークを経由しない。(同一のコンピューター上で実行されるから)
(データを複製する分はネットワーク経由の通信が発生するが、それは分散KVSを使う限り、どういう手段でデータ更新しても発生する)
最初にHadoopのクライアントからバッチプログラムを転送する手間はかかるが、全体の処理時間から考えれば、そちらの方が短く済む。
(というか、そういう時間のかかる処理でない限り、Hadoopを使う意味は無い)
“どのノードにどの範囲のデータを置くか”というのは、KVSが勝手に制御してくれる。
“どのノードでどの範囲を処理対象にするか”というのは、DBに入っているデータの範囲やノードの負荷状況を元に、Hadoopが勝手に考えてくれる。
各ノードへのバッチプログラムのコピーもHadoopがやってくれる。
いずれかのノードでバッチ処理中に障害が起きたら、(別ノードで)自動的にリトライしてくれる。
しかしその為に、HadoopはMap/Reduceという非常に限られた形式でしか処理できないわけだが…^^;
あ、逆に、DBサーバーからのデータを全てバッチサーバー上のファイルに書き出し、バッチはそのファイルを読み込んで新しい結果ファイルへ出力、その後に結果ファイルを読み込んでDBサーバーに反映させるって手も考えられるな。
そうすればDBアクセスは最小限、演算中はバッチサーバーのみの負荷で済むから、DBサーバーの負荷は軽減されるw
KVSの実装にもよるが、なんといっても最大の欠点はトランザクションが無いこと。
口座処理のような業務で使おうと思ったら、さすがにトランザクションが必須だろう。
口座Aの現在金額を取得し、そこに加算(あるいは減算)して、口座Aを更新する。変更履歴も登録する。
この間に別の処理で口座Aが更新されないこと(ロックをかける)、あるいは
別の処理で更新されたから自分の処理が失敗する(そして全更新がロールバックされる)こと
は保証されないと困る。
テーブルに入れるデータがバイナリデータであれば何でもいいというのも実はちょっと不便。
そこに入っているデータが数値なのか文字列なのかは、アプリケーション側で決めて記憶しておく必要がある。
また、そのせいで、アプリケーションデータに依存しない汎用的なクライアントツールは作りづらいのではないかと思う。
で、KVSには統一的なクライアントツール、特にSQLに当たるものが無い。
SQLはRDBの言語として、統一された規格が存在している。(実際はDBMS毎に方言が存在しているわけだが…)
KVSの欠点として各DBMSに統一のDB操作言語が無いことを挙げている人もいるようだが、自分は特に要らないと思う。
そりゃ統一されるなら覚える事が減るのでありがたいが、DBMSによってテーブル構造すら違うのに、どうやって統一しようというのか?
前述の通り、KVSはRDBに比べて制限事項が多いので、今までのように「何も考えず何でもRDB」という訳にはいかず、使用に向いていない分野がある。
まず、処理件数が多くないなら、特に分散させる必要が無いからKVSを使う必要は無い。
バッチ処理に時間がかかっていて、分散させれば高速化するというなら、分散KVSが使いたくなってくる。
基本的に、集計・抽出処理は分散させられるのではないかと思う。
(店毎・顧客毎の集計といった処理は、Map/Reduceが得意とするところだ)
しかしKVSはトランザクションがRDBほどはっきりしていないので、厳密なトランザクション(口座間のお金の移動とか)が必要だと、KVSは向かない。
逆に以下のように厳密なトランザクションが不要な業務・データであれば、KVSでも特に問題ない。
例えばGoogleの検索処理では、検索して表示されるページが最新でなくても構わない。
新しいページを追加する処理とユーザーが検索する処理は並列で動いているだろうが、新ページ追加の情報が全ノードに複製されている最中に、ユーザーによって新しい情報が見られたり古い情報が見えたりするのは許容範囲だ。
例えばAmazonでカートに商品を追加する処理は、ユーザー間で競合することは無い(商品の在庫の確認は、きっと購入確定処理時に行うだろう)し、同一ユーザーが複数の更新を同時に行うことも(ほぼ)無いだろうから、厳密なトランザクションは必要ない。
一方、実際に商品を確定して購入する処理ではトランザクション(排他制御)が必須だと思う。
例えばログの出力先をDBにしたいなら、KVSは向いている。
ログは基本的に一度書き込んだら更新することは無いので、後からログを検索・分析したりするには問題ない。
「NoSQL」という言葉が意味している通り、DBはRDBだけではない。また、KVSだけでもない。
RDBが一掃されてKVSだけになるなんてことは無いだろう。
KVSの弱点はトランザクション、RDBの弱点は大量処理。
ということは、両者を組み合わせて使えばいいんじゃないかなぁと思い付く。
例えば口座処理のような業務で履歴データの集計処理を行いたい場合。
基本的に、入出金業務では今まで通りRDBを使用する(全体的に、RDBを正とする)。
集計処理を行うときには、RDBから一旦KVSへ必要な履歴データをコピーする。
そしてKVSで集計処理を行えばOK!
まぁ問題は、コピーにかかる時間がどれくらいか、ということだけど。
(コピーの方法もいくつか考えられる。RDBからSELECTしてKVSへ直接書き出す。RDBのダンプ機能でファイルへ書き出してKVSへ取り込む。あるいはそれらとHadoopの組み合わせ)
RDBから一旦ファイルなりへ出力して、別の基盤で取り込んで処理を行う、というのは特に珍しいことではない。
データウェアハウス・データマイニングツール(大量データの分析ツール)では、データを全部メモリーに入れて(インメモリーで)処理するものもあるようだ。
まぁそれで処理できるなら、わざわざKVSへコピーしたりせずに、そのツールを使えばいいだけだが(爆)
インメモリー系の場合はそれ相応の高性能なハードウェアが必要そうだし、スケールアウトは出来なさそうだし。
それとたぶん、MySQL+Memchached(分散メモリキャッシュ)とかいうのは、これに似た考え方だよね。
MySQLもMemchachedも使ったことが無いから、なぜこれらを使っていた企業がKVSへ流れたのか、具体的なところはよく分からないけど。
→PublickeyのMySQL+Memcachedの時代は過ぎ、これからはNoSQLなのか、についての議論
RDBの更新と同時にKVSへ履歴データを登録しちゃう方法ももちろん考えられる。
この場合、RDBの更新が成功したらKVSへの登録を行うのがいいかな。RDBへの更新が失敗したらロールバックし、KVSへは登録しない。
RDBへの更新が成功してKVSへの登録が失敗した場合、失敗したキー情報をRDBに保持(あるいはファイルへ出力)しておいて、後からKVSへコピーする処理を設けるかなぁ。
失敗情報の保持自体が失敗したらどうするんだろ(苦笑) KVSとRDBを比較して、KVSに存在しないデータだけコピーするような処理を作るのかな。
KVSから一旦消して、全データをコピーする方が楽かな。
このコピー処理はほぼ前者の方法と同じだが、まぁリカバリー用としてはしょうがないか。
トランザクションをサポートしているKVSであれば、「RDBでないと出来ない事」は無さそうだから、KVSに移行してもいいかなぁ。
あ、あと帳票ツールなんかはRDBから直接SELECTしてくるものがあるので、そういうツールを使わなきゃいけない場合は、KVSへは移行できないなぁ。
(帳票ツールもそのうちKVSには対応してくるだろうけど。where条件やgroup by・order by等、SQLの高機能さに頼ってきたツールは、直すの大変だろうなぁ^^;)