LindormTable は、表形式モデルでセカンダリインデックスをサポートしています。この機能は、条件にプライマリキー列を指定しないクエリに対して、アプリケーション開発の複雑さを軽減し、データ整合性を確保し、書き込み効率を向上させます。このトピックでは、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 は読み取りおよび書き込み操作のレイテンシが低いことがわかります。単一行書き込みの 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 のスループットは BatchPut 操作でオープンソース Phoenix の約 3.5 倍、Put 操作で 2.2 倍、Get 操作で 6 倍、Scan 操作で 5.7 倍であることがわかります。
特徴
Lindorm のセカンダリインデックスでは、1 つのテーブルに対して複数のインデックスを作成できます。各インデックスは、プライマリテーブルから独立したデータテーブルに物理的にマッピングされます。各インデックスは、異なる圧縮アルゴリズムやコールド・ホットデータ分離ポリシーなど、異なるストレージポリシーを持つことができます。プライマリテーブルに書き込むと、Lindorm はすべてのインデックステーブルを自動的に更新し、プライマリテーブルとインデックステーブル間のデータ整合性を保証します。データを読み取る際は、プライマリテーブルをクエリするだけで済みます。Lindorm は、WHERE 条件とスキーマに基づいて、プライマリテーブル自体を含む適切なインデックスを自動的に選択します。オプティマイザーの動作に影響を与えるためのヒントワードもサポートされています。Lindorm セカンダリインデックスの基本的な特徴は以下の通りです。
単一のプライマリテーブルに対する複数のインデックスのサポート。
1 つ以上の列を使用した結合インデックスのサポート。
冗長インデックスをサポート。完全冗長インデックスは、プライマリテーブルからの新しい列を自動的に複製します。
クエリ最適化:WHERE 句に基づいてインデックスを自動的に選択します。オプティマイザーの選択に影響を与えるためのヒントワードをサポートします。
オンラインスキーマ変更:インデックスの変更は、プライマリテーブルの通常の読み取りおよび書き込み操作に影響しません。いつでもインデックスを追加、削除、または更新できます。
Time to Live (TTL) のサポート:インデックステーブルは、プライマリテーブルの TTL 設定を自動的に継承します。プライマリテーブルとインデックステーブルのデータは同時に有効期限切れになります。
動的カラムのサポート:動的カラムに書き込み、インデックスに含めることができます。
カスタムデータバージョンのサポート:指定されたカスタムタイムスタンプでデータが自動的に書き込まれます。
前提条件
サーバー:Lindorm インスタンス。
クライアント:詳細については、「Lindorm SQL で LindormTable を使用する」をご参照ください。
Lindorm-cli:詳細については、「Lindorm-cli で LindormTable に接続して使用する」をご参照ください。
用語
強力な整合性:これは、プライマリテーブルとそのインデックステーブル間のデータ整合性を指します。データ整合性の要件を満たし、セカンダリインデックスのオーバーヘッドを最小限に抑え、書き込みスループットを向上させるために、Lindorm のセカンダリインデックスには強力な整合性に関して以下の制約があります。
スナップショット分離はサポートされていません。データ書き込み中、可視性は保証されません。ただし、クライアントが成功の応答を受け取った後、データはプライマリテーブルとインデックステーブルの両方で可視になります。
クライアントのタイムアウトまたは I/O エラーが発生した場合、データがプライマリテーブルとインデックステーブルで可視であることは保証されません。ただし、プライマリテーブルとインデックステーブルのデータは最終的に整合性が取れます。
可変性オプション (Mutability options):プライマリテーブルに 1 つのインデックスがある場合、書き込み操作には、プライマリテーブルからのデータ読み取り、インデックスからの古いデータの削除、インデックスへのデータ書き込み、プライマリテーブルへのデータ書き込みの 4 つのステップが含まれます。このプロセスは、インデックスのメンテナンスコストを大幅に増加させます。しかし、書き込み増幅がすべてのシナリオで発生するわけではありません。たとえば、ログ記録シナリオでは、データは挿入されるだけで更新されません。この場合、インデックステーブルに古いデータは存在しないため、システムはインデックスとプライマリテーブルに書き込むだけで済みます。このため、Lindorm は可変性 (Mutability) の概念を導入しました。可変性は、プライマリテーブルの書き込みパターンを分類し、それに応じてインデックスデータを編成します。これにより、さまざまな要件に対するインデックス編成コストが最小限に抑えられます。次の表は、可変性のタイプを説明しています。テーブルを作成または変更する際に、`Table_Options` パラメーターを使用して Mutability 属性を設定する必要があります。詳細については、「CREATE TABLE 構文」をご参照ください。
可変性の分類
制約
コスト
説明
インデックスなし
なし。
1
インデックスが存在しない場合、システムはプライマリテーブルに直接書き込みます。これは単一の操作です。
IMMUTABLEデータは行単位で書き込まれます。データは更新または削除できません。
2
プライマリテーブルとインデックステーブルに書き込みます。このタイプは、すべてのシナリオで最もコストが低く、最高のパフォーマンスを発揮します。
重要データが更新または削除されないことを保証する必要があります。このタイプは推奨されません。
IMMUTABLE_ROWSデータは行単位で書き込みおよび削除されます。データは更新できません。
2~3
通常の書き込みの場合:プライマリテーブルとインデックステーブルに書き込みます。削除シナリオでは、プライマリテーブルからの追加の読み取りが必要です。パフォーマンスは IMMUTABLE に次いで優れています。
重要データが更新されないことを保証する必要があります。このタイプは推奨されません。
MUTABLE_LATEST(推奨)データは更新および削除できます。カスタムタイムスタンプでデータを書き込むことはできません。
4
プライマリテーブルから読み取り、インデックスから削除し、インデックスに書き込み、プライマリテーブルに書き込みます。
重要データベースエンジンバージョンが 2.7.9 以降のインスタンスでは、この属性はデフォルト属性です。バージョンが 2.7.9 より前の場合は、手動で属性を
MUTABLE_LATESTに設定することを推奨します。MUTABLE_ALL制限はありません。カスタムタイムスタンプを使用してデータを書き込むことができます。
4
プライマリテーブルから読み取り、インデックスから削除し、インデックスに書き込み、プライマリテーブルに書き込みます。
MUTABLE_UDT制限はありません。カスタムタイムスタンプを使用してデータを書き込むことができます。
4
プライマリテーブルから読み取り、インデックスから削除し、インデックスに書き込み、プライマリテーブルに書き込みます。
説明この属性は MUTABLE_ALL の最適化バージョンであり、より良いパフォーマンスを提供します。データベースエンジンのバージョンは 2.6.7 以降である必要があります。
推奨される属性は
MUTABLE_LATESTです。これはより良いパフォーマンスを提供しますが、指定されたタイムスタンプでのデータ書き込みは許可しません。データベースエンジンバージョンが 2.7.9 以降のインスタンスでは、MUTABLE_LATESTはデフォルト属性です。ご利用のインスタンスが 2.7.9 より前のデータベースエンジンバージョンを使用している場合は、手動で属性をMUTABLE_LATESTに設定する必要があります。カスタムタイムスタンプでデータを書き込むには、属性を
MUTABLE_UDTに変更することを推奨します。これには、データベースエンジンのバージョンが 2.6.7 以降である必要があります。使用上の注意については、以下の注記の 4 番目と 5 番目のポイントをご参照ください。説明IMMUTABLEおよびIMMUTABLE_ROWSタイプはデータ更新を伴いません。これは書き込み増幅がなく、コストが最小限に抑えられることを意味します。これらのタイプは、ログ記録やモニタリングなどの高スループットの書き込みシナリオに適しています。タイプをIMMUTABLEまたはIMMUTABLE_ROWSに設定してもデータ更新を実行した場合、サーバーはエラーを報告しません。ただし、これによりプライマリテーブルとインデックステーブル間でデータ不整合が発生する可能性があります。IMMUTABLEタイプは削除を伴いません。これにより、Lindorm のマルチ IDC デプロイメントを最大限に活用して、アクティブ/アクティブデータアクセスを実現できます。2 つの
IMMUTABLEタイプのいずれかを選択すると、インデックス作成シナリオでの書き込みレイテンシを効果的に削減し、全体的なスループットを向上させることができます。ビジネスシナリオがIMMUTABLEの要件を満たさない場合は、データ冗長性を使用してIMMUTABLEシナリオに適応させることができます。Mutability 属性は、インデックステーブルを作成するときに有効になります。データベースエンジンバージョン 2.7.9 以降では、デフォルト値は
MUTABLE_LATESTです。この属性は指定されたタイムスタンプでのデータ書き込みをサポートしません。したがって、カスタムタイムスタンプでデータを書き込む必要があるかどうかに基づいて、この属性を事前に計画する必要があります。カスタムタイムスタンプを使用してデータを書き込むが、インデックスを作成する前に Mutability 属性を
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 はインデックスキー列の 1 つですが、WHERE句にc3列が欠落しているため、クエリはidx2のプレフィックスに一致しません。文
SELECT * FROM dt WHERE c2 = 'b' AND c3 >= 'c' AND c3 < 'f';は `idx2` インデックステーブルを選択します。クエリは `SELECT *` を使用しており、インデックステーブルにはプライマリテーブルのすべての列が含まれていないため、インデックスのクエリ後にプライマリテーブルのスキャンが必要です。プライマリテーブルがスキャンされるとき、ルックアップする行キーがテーブル全体に散在している可能性があり、複数のリモートプロシージャコール (RPC) が発生する可能性があります。スキャンされるデータ量が多いほど、応答時間 (RT) は長くなります。文
SELECT * FROM dt WHERE c5 = 'c';はインデックステーブルidx4を選択します。idx4は完全冗長インデックスであるため、select *クエリはプライマリテーブルにアクセスする必要はありません。
制限事項
異なるプライマリテーブルに対して同じ名前のインデックスを作成できます。たとえば、dt テーブルと foo テーブルの両方に Idx1 という名前のインデックスを作成できます。ただし、同じプライマリテーブルに対して同じ名前のインデックスを作成することはできません。
インデックスは、単一バージョンのデータを格納するテーブルに対してのみ作成できます。複数バージョンのテーブルに対してインデックスを作成することはできません。
セカンダリインデックスは、セルレベルの TTL をサポートしていません。
テーブルレベルの TTL を持つプライマリテーブルに対してインデックスを作成する場合、インデックステーブルに個別の TTL を設定することはできません。インデックステーブルは、プライマリテーブルの TTL を自動的に継承します。
インデックスには最大 3 つのインデックスキー列を含めることができます。
インデックスキー列とプライマリテーブルのプライマリキーの合計長は 30 KB を超えることはできません。100 バイトを超える列をインデックスキー列として使用しないことを推奨します。
単一のプライマリテーブルに対して最大 5 つのインデックステーブルを作成できます。インデックスが多すぎると、ストレージコストが高くなり、書き込みレイテンシが長くなる可能性があります。
クエリは最大 1 つのインデックスにヒットできます。インデックスマージクエリはサポートされていません。
インデックスを作成すると、プライマリテーブルのデータがインデックスに同期されます。大量のデータを持つテーブルに対してインデックスを作成すると、CREATE INDEX コマンドに時間がかかることがあります。
セカンダリインデックスは、バッチ増分機能をサポートしていません。
クエリによってヒットしたセカンダリインデックスのソート順は、プライマリテーブルのソート順とは異なります。
セカンダリインデックスは、SQL または API を使用して書き込まれたデータに対してのみ作成できます。BulkLoad を使用して Lindorm にインポートされたデータに対してセカンダリインデックスを作成することはできません。
よくある質問
なぜ User-Defined-Timestamp(UDT) is not supported by table in MUTABLE_LATEST mode エラーが発生するのですか?
このエラーは、カスタムタイムスタンプを使用してデータを書き込んでいるにもかかわらず、インデックスを作成する前に Mutability 属性を MUTABLE_UDT (推奨) または MUTABLE_ALL に設定していないために発生します。
プライマリテーブルのインデックスを削除し、Mutability 属性の値を MUTABLE_UDT に変更してから、新しいインデックスを作成することを推奨します。
このインデックスにヒットする読み取りリクエストがビジネスにある場合は、慎重に進めてください。
インデックスに関するその他の問題については、DingTalk で Lindorm のテクニカルサポートに連絡するか、チケットを送信してください。詳細については、「テクニカルサポート」をご参照ください。