コンテキスト
この質問は、SQLデータベースシステムとNoSQLデータベースシステムの両方におけるインデックスの低レベルの実装の詳細に関係しています。質問は特にこれらの実装の単一ノード内に格納されているkeysに関係するため、インデックスの実際の構造(B +ツリー、ハッシュ、SSTableなど)は無関係です。
背景
SQL(MySQLなど)およびNoSQL(CouchDB、MongoDBなど)データベースでは、データの列またはJSONドキュメントフィールドにインデックスを作成すると、実際にデータベースで実行されているのは本質的にこれらのすべての値のソートされたリストと、その値に関連するレコードが存在するメインデータファイルへのファイルオフセット。
(簡単にするために、特定の実装に関する他の難解な詳細を手を振っているかもしれません)
シンプルなクラシックSQLの例
インデックスを作成する単純な32ビットのintプライマリキーを持つ標準SQLテーブルを考えます。ソートされ、データファイルへの64ビットオフセットに関連付けられた整数キーのディスク上のインデックスになります。レコードは存続します。例:
id | offset
--------------
1 | 1375
2 | 1413
3 | 1786
インデックス内のキーのディスク上の表現は次のようになります。
[4-bytes][8-bytes] --> 12 bytes for each indexed value
ファイルシステムおよびデータベースシステムでのディスクI/Oの最適化に関する標準的な経験則に準拠して、キーをディスク上の4KBブロックに格納するとします。
4096 bytes / 12 bytes per key = 341 keys per block
インデックスの全体的な構造(B +ツリー、ハッシュ、ソートされたリストなど)を無視して、一度に341キーのブロックをメモリに読み書きし、必要に応じてディスクに書き戻します。
クエリ例
前のセクションの情報を使用して、クエリが "id = 2"で受信されたとしましょう。従来のDBインデックス検索は次のようになります。
質問の設定...
では、ここで質問をまとめます...
ステップ#2は、これらのクエリをO(logn)時間で実行できるようにする最も重要な部分です...情報をソートする必要があります[〜#〜] but [ 〜#〜]クイックソートの方法でリストをトラバースできる必要があります...より具体的には、明確に定義されたオフセットにジャンプして、インデックスキーの値を自由に読み取ることができる必要がありますその位置で。
ブロックを読み取った後、すぐに170番目の位置にジャンプし、キー値を読み取って、探しているものがGTまたはLTその位置であるかどうかを確認できます(以下同様)等々...)
このようにブロック内のデータをジャンプできる唯一の方法は、上記の例のようにキー値のサイズがすべて明確に定義されている場合です(4バイト、次にキーごとに8バイト)。
[〜#〜]質問[〜#〜]
さて、ここで私は効率的なインデックス設計で立ち往生しています... SQLデータベースのvarchar列、より具体的には、CouchDBやNoSQLなどのドキュメントデータベースの完全に自由な形式のフィールド。 length howインデックスを構築するインデックス構造のブロック内にあるキー値を実装しますか?
たとえば、CouchDBのIDにシーケンシャルカウンターを使用し、ツイートのインデックスを作成しているとします。数か月後に値が「1」から「100,000,000,000」に変わるとします。
1日目にデータベースにインデックスを作成するとします。データベースにツイートが4つしかない場合、CouchDBはインデックスブロック内のキー値に次の構成を使用したくなるかもしれません。
[1-byte][8-bytes] <-- 9 bytes
4096 / 9 = 455 keys per block
ある時点でこれが壊れ、キー値をインデックスに格納するために可変バイト数が必要になります。
「Tweet_message」などの本当に可変長のフィールドにインデックスを付ける場合は、さらに重要な点があります。
キー自体は完全に可変長であり、データベースには、インデックスが作成および更新されるときに「最大キーサイズ」をインテリジェントに推測する方法がないため、これらのキーはどのように実際にを表すブロック内に保存されますかこれらのデータベースのインデックスのセグメント?
明らかに、キーのサイズが可変で、キーのブロックを読み取る場合、ブロック内のキーの数実際にがわからないだけでなく、途中にジャンプする方法もわからないそれらのバイナリ検索を行うリストの。
これは私がすべてつまずくところです。
クラシックSQLデータベース(bool、int、charなど)の静的型付きフィールドを使用すると、インデックスがキーの長さを事前に定義してそれに固執できることを理解しています...しかし、このドキュメントデータストアの世界では、 O(logn)時間でスキャンできるように、ディスク上のこのデータを効率的にモデル化している方法に戸惑っています。
説明が必要な場合はお知らせください!
更新(グレッグの回答)
グレッグの回答に添付されている私のコメントをご覧ください。 1週間以上の調査の結果、実際に実装して使用するのは非常に簡単でありながら、気にしない重要な値の逆シリアル化を回避することでパフォーマンスを大幅に向上できるという、驚くほどシンプルでパフォーマンスの高い提案に彼は本当に遭遇したと思います。
私は3つの個別のDBMS実装(CouchDB、kivaloo、InnoDB)を調査しましたが、それらのallは、実行環境(erlang/C)。
これは、グレッグの提案について非常に素晴らしいと私が思うものです。 2048の通常のブロックサイズは、通常50以下のオフセットを持ち、その結果、非常に小さな数のブロックが読み込まれる必要があります。
更新(Gregの提案に対する潜在的な欠点)
この対話を自分自身でうまく続けるために、私はこれの次の欠点を認識しました...
すべての「ブロック」の先頭がオフセットデータである場合、ヘッダーで正しく開始されなかったデータまたはそのブロックを読み取ってしまう可能性があるため、後の設定でブロックサイズを調整することはできません。複数のヘッダーが含まれていました。
巨大なキー値をインデックス化している場合(たとえば、誰かがchar(8192)またはblob(8192)の列をインデックス化しようとしている)、キーが単一のブロックに収まらず、2つのブロックにまたがってオーバーフローする必要がある可能性があります。つまり、最初のブロックにはオフセットヘッダーがあり、2番目のブロックはすぐにキーデータから始まります。
これに対するすべての解決策は、固定データベースブロックサイズnotを調整し、その周りにヘッダーブロックデータ構造を開発することです...たとえば、すべてのブロックサイズを4KBに固定します(通常、最も最適です)とにかく)最初に「ブロックタイプ」を含む非常に小さなブロックヘッダーを記述します。通常のブロックの場合、ブロックヘッダーの直後にオフセットヘッダーを配置する必要があります。 「オーバーフロー」タイプの場合、ブロックヘッダーの直後は生のキーデータです。
更新(潜在的な素晴らしいアップサイド)
ブロックが一連のバイトとして読み込まれ、オフセットがデコードされた後。技術的には、検索するキーを生のバイトに単純にエンコードし、バイトストリームで直接比較できます。
探しているキーが見つかったら、ポインターをデコードして追跡できます。
グレッグのアイデアのもう一つの素晴らしい副作用!ここでのCPU時間の最適化の可能性は非常に大きいため、固定ブロックサイズを設定することは、これらすべてを得るだけの価値があるかもしれません。
インデックスを、固定サイズのオフセットのリストとして、キーデータを含むブロックに保存できます。例えば:
+--------------+
| 3 | number of entries
+--------------+
| 16 | offset of first key data
+--------------+
| 24 | offset of second key data
+--------------+
| 39 | offset of third key data
+--------------+
| key one |
+----------------+
| key number two |
+-----------------------+
| this is the third key |
+-----------------------+
(まあ、主要なデータは実際の例では並べ替えられますが、アイデアがわかります)。
これは必ずしも、どのデータベースでもインデックスブロックが(実際に)構成されている方法を反映しているわけではないことに注意してください。これは、キーデータが可変長であるインデックスデータのブロックをmightで構成する方法の単なる例です。