LindormTable は、表形式モデルでセカンダリインデックスをサポートします。 クエリの条件でプライマリキー列が指定されていない場合、この機能により、アプリケーション開発の複雑さが軽減され、データ整合性が確保され、書き込み効率が向上します。 このトピックでは、Lindorm の表形式モデルにおけるセカンダリインデックスのコア機能について説明し、使用例を示します。
背景情報
Lindorm の表形式モデルでは、Lindorm のワイドテーブルは、列のデータ型が固定された事前定義のテーブルスキーマを持ちます。 Lindorm のネイティブセカンダリインデックス機能は、Alibaba Cloud で長年デプロイされており、そのパフォーマンスは「独身の日」のショッピングフェスティバルで繰り返し検証されてきました。 この機能は、大規模なデータセットにわたるグローバルなインデックス作成に最適です。
読み取りおよび書き込みの応答時間 (RT) の比較
製品
単一行書き込み RT
単一行読み取り RT
100 行バッチ書き込み RT
Lindorm
1.409
0.492
10.447
オープンソースの Phoenix
2.264
2.594
26.224
結果は、Lindorm がインデックス作成シナリオにおいて、オープンソースの Phoenix よりも低い RT を達成していることを示しています。 単一行書き込みの RT は、オープンソースの Phoenix の約 62% です。 単一行読み取りの RT は、オープンソースの Phoenix の約 19% です。 100 行バッチ書き込みの RT は、オープンソースの Phoenix の約 40% です。
書き込みスループットの比較
製品
BatchPut
Put
Get
Scan
Lindorm
198174
73214
156372
1283451
オープンソースの Phoenix
56923
33156
25910
224085
結果は、インデックス作成シナリオにおいて、Lindorm のスループットがオープンソースの Phoenix を上回っていることを示しています。BatchPut 操作では約 3.5 倍、Put 操作では約 2.2 倍、Get 操作では約 6 倍、Scan 操作では約 5.7 倍です。
特徴
Lindorm のセカンダリインデックスを使用すると、1 つのテーブルに対して複数のインデックスを作成できます。 各インデックスは、プライマリテーブルとは別の独立した物理データテーブルにマッピングされます。 インデックスは、異なる圧縮アルゴリズムを使用するストレージポリシーや、コールド・ホットデータ分離戦略など、さまざまなプロパティを持つことができます。 プライマリテーブルにデータを書き込むと、Lindorm は関連するすべてのインデックステーブルを自動的に更新し、それらの間のデータ整合性を維持します。 データをクエリする場合、プライマリテーブルをクエリするだけで済みます。 Lindorm は、WHERE 句とスキーマに基づいて、プライマリテーブルを含む最適なインデックスを自動的に選択します。 ヒントを使用してオプティマイザーの選択に影響を与えることもできます。 主な特徴は次のとおりです。
1 つのプライマリテーブルに対して複数のインデックスを作成できます。
1 つ以上の列で構成される結合インデックスをサポートします。
カバリングインデックスをサポートします。 完全カバリングインデックスは、プライマリテーブルに追加された新しい列を自動的に含みます。
クエリの最適化: WHERE 句に基づいてインデックスを自動的に選択します。 ヒントを使用してオプティマイザーの選択に影響を与えることをサポートします。
オンラインスキーマ変更: インデックスの変更は、プライマリテーブルでの通常の読み取りまたは書き込み操作に影響しません。 いつでもインデックスを追加、削除、または更新できます。
Time to Live (TTL) のサポート: インデックステーブルは、プライマリテーブルの TTL 設定を継承します。 プライマリテーブルとインデックステーブルの両方のデータは同時に有効期限が切れます。
動的カラム: 動的カラムおよび冗長な動的カラムへの書き込みをサポートします。
カスタムデータバージョンのサポート: ユーザー指定のタイムスタンプでデータを書き込みます。
前提条件
サーバー: Lindorm インスタンス。
クライアント: 詳細については、「Lindorm ワイドテーブル SQL で LindormTable エンジンを使用する」をご参照ください。
Lindorm-cli クライアント: 詳細については、「Lindorm-cli を使用して LindormTable に接続して使用する」をご参照ください。
用語
強力な整合性: プライマリテーブルとそのインデックステーブル間のデータ整合性を指します。 データ整合性の要件を満たしつつ、セカンダリインデックスのオーバーヘッドを最小限に抑え、書き込みスループットを最大化するために、Lindorm のセカンダリインデックスは、強力な整合性に対して以下の制約を課します。
スナップショット分離はサポートされていません。 データが書き込まれている間、クエリですぐに利用できない場合があります。 システムがクライアントに成功応答を返した後、データはプライマリテーブルとインデックステーブルの両方で表示されるようになります。
クライアントのタイムアウトまたは I/O エラーが発生した場合、データがプライマリテーブルまたはインデックステーブルに表示されないことがあります。 ただし、プライマリテーブルとインデックステーブル間の結果整合性は保証されます。
可変性オプション: プライマリテーブルに 1 つのインデックスがある場合、書き込み操作には、プライマリテーブルの読み取り、インデックスからの古いデータの削除、インデックスへの書き込み、プライマリテーブルへの書き込みの 4 つのステップが含まれます。 このプロセスにより、インデックスのメンテナンスコストが大幅に増加します。 ただし、書き込み増幅がすべてのシナリオで発生するわけではありません。 たとえば、ログ記録シナリオでは、データは挿入されるだけで更新されることはありません。 このような場合、インデックステーブルには古いデータが含まれていないため、システムはインデックスとプライマリテーブルにのみ書き込みます。 この問題に対処するために、Lindorm は可変性を導入しています。 可変性は、プライマリテーブルの書き込みパターンを分類し、それに応じてインデックスデータを整理して、さまざまなユースケースでのインデックス整理コストを最小限に抑えます。 次の表に、可変性の分類を示します。 テーブルを作成または変更するときに、Table_Options パラメーターを使用して可変性プロパティを設定します。 詳細については、「CREATE TABLE 構文」をご参照ください。
可変性の分類
制約
操作コスト
操作の説明
インデックスなし
なし。
1
インデックスが存在しない場合、システムはプライマリテーブルに直接書き込みます。 これは単一の操作です。
IMMUTABLE行単位でデータを書き込みます。 データは更新または削除できません。
2
プライマリテーブルとインデックステーブルに書き込みます。 これは、すべてのシナリオの中で最もコストが低く、最高のパフォーマンスを発揮します。
重要データが更新または削除されないようにしてください。 このタイプは推奨されません。
IMMUTABLE_ROWS行単位でデータを書き込み、削除します。 データは更新できません。
2~3
通常の書き込みの場合: プライマリテーブルとインデックステーブルに書き込みます。 削除シナリオでは、プライマリテーブルからの追加の読み取りが必要です。 パフォーマンスは IMMUTABLE に次いで 2 番目です。
重要データが更新されないようにしてください。 このタイプは推奨されません。
MUTABLE_LATEST(推奨)データは更新および削除できます。 カスタムタイムスタンプを使用してデータを書き込むことはできません。
4
プライマリテーブルから読み取り、インデックスから削除し、インデックスに書き込み、プライマリテーブルに書き込みます。
重要LindormTable 2.7.9 以降のインスタンスでは、これがデフォルトのプロパティです。 インスタンスのバージョンが 2.7.9 より前の場合は、プロパティを
MUTABLE_LATESTに設定してください。MUTABLE_ALL制限はありません。 カスタムタイムスタンプを使用してデータを書き込むことができます。
4
プライマリテーブルから読み取り、インデックスから削除し、インデックスに書き込み、プライマリテーブルに書き込みます。
MUTABLE_UDT制限はありません。 カスタムタイムスタンプを使用してデータを書き込むことができます。
4
プライマリテーブルから読み取り、インデックスから削除し、インデックスに書き込み、プライマリテーブルに書き込みます。
説明このプロパティは MUTABLE_ALL の最適化バージョンであり、より優れたパフォーマンスを提供します。 LindormTable 2.6.7 以降が必要です。
推奨設定は
MUTABLE_LATESTです。 パフォーマンスは向上しますが、指定されたタイムスタンプでのデータ書き込みはサポートされません。 LindormTable 2.7.9 以降を実行しているインスタンスの場合、MUTABLE_LATESTがデフォルトのプロパティです。 インスタンスが LindormTable 2.7.9 より前のバージョンを使用している場合は、プロパティをMUTABLE_LATESTに設定してください。カスタムタイムスタンプでデータを書き込むには、プロパティを
MUTABLE_UDTに変更します。 これには LindormTable 2.6.7 以降が必要です。 使用上の注意については、次の注記の 4 番目と 5 番目のポイントをご参照ください。説明IMMUTABLEおよびIMMUTABLE_ROWSタイプはデータ更新を伴わないため、書き込み増幅がなく、コストが最も低くなります。 これらは、ログ記録やモニタリングなどの高スループットの書き込みシナリオに適しています。 プロパティをIMMUTABLEまたはIMMUTABLE_ROWSに設定してもデータ更新を実行した場合、サーバー側ではエラーは報告されませんが、プライマリデータテーブルとインデックステーブルの間でデータ不整合が発生する可能性があります。IMMUTABLEタイプは削除を伴わないため、Lindorm のマルチ IDC デプロイメントを最大限に活用して、アクティブ/アクティブなデータアクセスを実現できます。いずれかの
IMMUTABLEプロパティを選択すると、書き込みレイテンシーが効果的に短縮され、インデックス作成シナリオでの全体的なスループットが向上します。 実際には、ワークロードがIMMUTABLEシナリオに一致しない場合でも、データ冗長性を使用してIMMUTABLEシナリオに適応させることができます。可変性プロパティは、インデックステーブルが作成されるときに有効になります。 LindormTable 2.7.9 以降の場合、デフォルト値は
MUTABLE_LATESTです。 このプロパティは、指定されたタイムスタンプでのデータ書き込みをサポートしません。 したがって、カスタムタイムスタンプを使用してデータを書き込む必要があるかどうかに基づいて、このプロパティを事前に計画してください。カスタムタイムスタンプを使用してデータを書き込む際に、インデックスを作成する前に可変性プロパティを
MUTABLE_UDTまたはMUTABLE_ALLに設定しないと、インデックス作成後にサービスがすぐに影響を受け、エラーが発生します。 解決策については、「よくある質問」をご参照ください。
カスタムタイムスタンプによるインデックスの更新: Lindorm は、カスタムタイムスタンプでのデータ書き込みをサポートしています。 任意のタイムスタンプでデータを更新でき、システムは最大のタイムスタンプを持つデータのみを適用します。 この機能は、データの TTL を制御し、順序不同またはべき等なシナリオを処理するために重要です。 HBase で広く使用されています。 Lindorm は列レベルのタイムスタンプをサポートし、プライマリテーブルはカスタムタイムスタンプでのデータ書き込みをサポートします。 ただし、セカンダリインデックスとタイムスタンプの両方をサポートする NoSQL システムの中で、カスタムタイムスタンプでのインデックス更新をサポートするものはほとんどありません。 これは、順序不同のタイムスタンプ書き込みにより、インデックスデータの更新と削除を確実に維持することが困難になるためです。 Lindorm のグローバルセカンダリインデックスはこの問題を解決し、列レベルのカスタムタイムスタンプ更新をサポートします。 以下は、カスタムタイムスタンプを使用する 2 つの実際の業務シナリオです。
同時インポートとリアルタイム更新: リアルタイム更新と履歴データのインポートの両方が必要なシナリオでは、リアルタイム更新には現在の時刻を使用し、履歴データのインポートには前日の 23:59:59 などの時刻を使用します。 これにより、当日にまだ更新されていないデータはインポートによって更新でき、すでに更新されたデータは影響を受けません。
メッセージのキャッチアップ: 業務システムはメッセージを使用して処理ロジックをトリガーします。 メッセージが蓄積されると、システムは蓄積されたメッセージをスキップして現在のメッセージを直接処理できます。 後で、以前に蓄積されたタスクをキャッチアップできます。 あるいは、バグが業務ロジックに影響を与える場合、システムは問題のあるメッセージをスキップし、修正後にそれらを再処理できます。 このような場合、業務は各メッセージ自身のタイムスタンプを使用してデータを書き込むことができます。 これにより、キャッチアップされたメッセージと通常のメッセージの間で正しい上書き動作が保証されます。
完全カバリングインデックス: インデックスをクエリした後にプライマリテーブルをルックアップするのを避けるために、通常、プライマリテーブルの一部の列をインデックステーブルに含めます。 これはカバリングインデックスとして知られています。 完全カバリングインデックスは一般的なデータ冗長性戦略です。 Lindorm は 3 つの冗長モードをサポートしており、プライマリテーブルのスキーマが変更されたり、動的カラムが含まれたりする場合でも、完全カバリングインデックスを簡単に実装できます。
冗長化のための列: (プライマリテーブルから) 複製する列を選択します。
プライマリテーブルスキーマのすべての列を含める: 完全カバリングインデックスが必要な場合、CREATE INDEX 文で各プライマリテーブルの列を明示的にリストする必要はありません。 代わりに、定数を使用してすべての列を含めることを示します。 新しい列がプライマリテーブルに追加されると、完全カバリングインデックステーブルはインデックスの再作成を必要とせずに自動的にそれを含みます。 また、新しい列をクエリするときにプライマリテーブルへのルックアップを心配する必要もありません。
動的カラムを含める: Lindorm は、固定スキーマと疎スキーマ (動的カラム) の両方をサポートします。 DYNAMIC 冗長モードを使用すると、インデックステーブルは、プライマリテーブルスキーマで定義されたすべての列とともに、プライマリテーブルのすべての動的カラムを自動的に含みます。
セカンダリインデックスの作成 (CREATE INDEX)
Lindorm のプライマリテーブルを作成した後、その列にセカンダリインデックスを作成できます。 次の例は、セカンダリインデックスを作成する方法を示しています。
-- プライマリテーブルの作成
CREATE TABLE test (
p1 VARCHAR NOT NULL,
p2 INTEGER NOT NULL,
c1 BIGINT,
c2 DOUBLE,
c3 VARCHAR,
c5 GEOMETRY(POINT),
PRIMARY KEY(p1, p2)
) WITH (CONSISTENCY = 'strong', MUTABILITY='MUTABLE_LATEST');
-- c3 列にセカンダリインデックスを作成し、すべての列を含める
CREATE INDEX idx1 ON test(c3 desc) WITH (INDEX_COVERED_TYPE ='COVERED_ALL_COLUMNS_IN_SCHEMA');
-- インデックステーブルに基づくクエリ。 c3 にインデックスが構築されているため、クエリで c3 を指定するとインデックステーブルにヒットします。
SELECT * FROM test WHERE c3 = 'data'; インデックスは同期または非同期で作成できます。 プライマリテーブルに大量の既存データが含まれていない場合は、同期メソッドを使用します。 それ以外の場合は、非同期メソッドを使用します。 構文の詳細については、「CREATE INDEX」をご参照ください。
すでにデータが含まれているテーブルに新しいインデックスを追加すると、CREATE INDEX コマンドは既存データをプライマリテーブルからインデックステーブルに同期します。 プライマリテーブルが大きい場合、CREATE INDEX コマンドの実行に時間がかかります。 データ同期タスクはサーバー側で実行されます。 Lindorm Shell プロセスを終了しても、データ同期タスクには影響しません。
インデックス構築にはデータルックアップが必要であり、これにより読み取り操作が生成されます。 ご利用のインスタンスでコールド・ホットデータ分離機能が有効になっている場合は、コールドストレージ (ストレージ最適化クラウドストレージ) のレート制限を監視してください。 コールドストレージの読み取りに対するレート制限は、インデックス構築の効率に直接影響し、書き込み操作にバックプレッシャーを引き起こす可能性があります。
セカンダリインデックスの表示 (SHOW INDEX)
Lindorm SQL を使用して、作成されたセカンダリインデックスのステータスを表示できます。 次の例は、セカンダリインデックスを表示する方法を示しています。
SHOW INDEX FROM test;この例では、test プライマリテーブルの下に作成されたインデックス名とインデックスタイプが表示されます。
セカンダリインデックスのステータスの変更 (ALTER INDEX)
セカンダリインデックスを作成した後、プライマリテーブルに既存データが含まれている場合は、インデックスに対して手動で再構築操作を実行する必要があります。 構文の詳細については、「BUILD INDEX」をご参照ください。 プライマリテーブルに既存データがない場合は、ALTER INDEX 構文を使用してインデックステーブルの状態を直接変更できます。 次の例は、セカンダリインデックスの状態を変更する方法を示しています。
ALTER INDEX IF EXISTS idx1 ON test ACTIVE;
ALTER INDEX idx1 ON test DISABLED;セカンダリインデックスの状態が DISABLED の場合、直接 ACTIVE に変更するとデータが失われます。 したがって、状態を変更する前に再構築操作を実行してください。
セカンダリインデックスの削除 (DROP INDEX)
次の例を使用して、対応するプライマリテーブルからセカンダリインデックスを削除します。
DROP INDEX IF EXISTS idx1 ON test;インデックスを削除するには、Trash 権限が必要です。
クエリの最適化
Lindorm は、ルールベース最適化 (RBO) を使用してセカンダリインデックスを選択します。 インデックステーブルのプレフィックスをクエリ条件と照合し、一致度が最も高いインデックステーブルを選択します。 次の例は、このプロセスを明確にするのに役立ちます。
-- プライマリテーブルとインデックステーブルは次のとおりです
CREATE TABLE dt (rowkey varchar, c1 varchar, c2 varchar, c3 varchar, c4 varchar, c5 varchar, PRIMARY KEY(rowkey));
CREATE INDEX idx1 ON dt (c1);
CREATE INDEX idx2 ON dt(c2,c3,c4);
CREATE INDEX idx3 ON dt(c3) INCLUDE(c1,c2,c4);
CREATE INDEX idx4 ON dt(c5 desc) WITH (INDEX_COVERED_TYPE ='COVERED_ALL_COLUMNS_IN_SCHEMA');
-- クエリの最適化は次のとおりです
SELECT rowkey FROM dt WHERE c1 = 'a';
SELECT rowkey FROM dt WHERE c2 = 'b' AND c4 = 'd';
SELECT * FROM dt WHERE c2 = 'b' AND c3 >= 'c' AND c3 < 'f';
SELECT * FROM dt WHERE c5 = 'c';文
SELECT rowkey FROM dt WHERE c1 = 'a';は、インデックステーブルidx1を選択します。文
SELECT rowkey FROM dt WHERE c2 = 'b' AND c4 = 'd';は、インデックステーブルidx2を選択します。c2=bに一致するすべての行を取得し、c4=dで行ごとにフィルター処理します。 c4 はインデックス列ですが、WHERE条件にc3列がないため、idx2のプレフィックスは一致しません。文
SELECT * FROM dt WHERE c2 = 'b' AND c3 >= 'c' AND c3 < 'f';は、インデックステーブル idx2 を選択します。 これは SELECT * クエリであり、インデックステーブルにはプライマリテーブルのすべての列が含まれていないため、インデックスをクエリした後にプライマリテーブルをルックアップする必要があります。 ルックアップ中、Rowkey はプライマリテーブル全体に分散している可能性があり、複数の RPC を消費する可能性があります。 結果セットが大きくなると RT が増加します。文
SELECT * FROM dt WHERE c5 = 'c';は、インデックステーブルidx4を選択します。idx4は完全カバリングインデックスであるため、SELECT *はプライマリテーブルへのルックアップを必要としません。
制限事項
異なるプライマリテーブルに対して同じ名前のインデックスを作成できます。 たとえば、dt テーブルと foo テーブルの両方に Idx1 という名前のインデックスを作成できます。 ただし、同じプライマリテーブルに対して作成される各インデックスは、一意の名前を持つ必要があります。
インデックスは、単一バージョンのデータを格納するテーブルに対してのみ作成できます。 多版テーブルはインデックスをサポートしていません。
セカンダリインデックスは、セルレベルの TTL をサポートしていません。
テーブルレベルの TTL を持つプライマリテーブルにインデックスを作成する場合、インデックステーブルに個別の TTL を設定することはできません。 インデックステーブルは、プライマリテーブルの TTL を自動的に継承します。
LindormTable 2.8.6 以降では、インデックスには最大 8 つのインデックス列を含めることができます。 それより前のバージョンでは、インデックスには最大 3 つのインデックス列を含めることができます。
インデックス列とプライマリテーブルのプライマリキーの合計長は 30 KB を超えてはなりません。 100 バイトを超える列をインデックス列として使用することは避けてください。
LindormTable 2.8.6 以降では、1 つのプライマリテーブルに対して最大 10 個のインデックスを作成できます。 それより前のバージョンでは、最大 5 個のインデックスを作成できます。 インデックスが多すぎると、ストレージコストが増加し、書き込み時間が長くなります。
クエリは最大で 1 つのインデックスしか使用できません。 インデックスマージクエリはサポートされていません。
インデックスを作成すると、プライマリテーブルのデータがインデックスに同期されます。 大量のデータを持つテーブルにインデックスを作成すると、CREATE INDEX コマンドの実行に時間がかかることがあります。
セカンダリインデックスは、バッチ増加機能をサポートしていません。
クエリで使用されるセカンダリインデックスのソート順は、プライマリテーブルのソート順とは異なります。
セカンダリインデックスは、SQL または API を使用して書き込まれたデータに対してのみ構築できます。 Bulkload を使用して Lindorm にインポートされたデータに対してセカンダリインデックスを構築することはできません。
よくある質問
「User-Defined-Timestamp(UDT) is not supported by table in MUTABLE_LATEST mode」エラーが発生するのはなぜですか?
このエラーは、カスタムタイムスタンプでデータを書き込んだにもかかわらず、インデックスを作成する前に可変性プロパティを MUTABLE_UDT (推奨) または MUTABLE_ALL に設定しなかったために発生します。
プライマリテーブルのインデックスを削除し、可変性プロパティを MUTABLE_UDT に変更してから、新しいインデックスを作成します。
ご利用の業務にこのインデックスにヒットする読み取りリクエストがある場合は、注意して削除してください。
インデックスに関連するその他の問題については、DingTalk の Lindorm グループに参加するか、チケットを送信してください。 詳細については、「テクニカルサポート」をご参照ください。