TiDB ベストプラクティス
このドキュメントでは、SQL の使用や、オンライン分析処理 (OLAP) およびオンライン トランザクション処理 (OLTP) シナリオの最適化のヒント、特に TiDB 固有の最適化オプションなど、TiDB の使用に関するベスト プラクティスをまとめています。
このドキュメントを読む前に、TiDB の技術的原理を紹介する次の 3 つのブログ投稿を読むことをお勧めします。
序文
データベースは汎用的なインフラストラクチャ システムです。開発プロセス中にさまざまなユーザー シナリオを考慮し、特定のビジネス シナリオの実際の状況に応じてデータ パラメータや使用方法を変更することが重要です。
TiDB は、MySQL プロトコルおよび構文と互換性のある分散データベースです。ただし、分散storageとトランザクションの内部実装およびサポートにより、TiDB の使用方法は MySQL とは異なります。
基本概念
ベスト プラクティスは、実装の原則と密接に関連しています。Raft コンセンサス アルゴリズム、分散トランザクション、データ シャーディング、負荷分散、SQL からキー値 (KV) へのマッピング ソリューション、セカンダリ インデックスの実装方法、分散実行エンジンなど、いくつかの基本的なメカニズムを学習することをお勧めします。
このセクションではこれらの概念を紹介します。詳細についてはPingCAPのブログ投稿を参照してください。
Raft
Raft は、強力な一貫性を備えたデータ複製を保証するコンセンサス アルゴリズムです。最レイヤーでは、TiDB はRaftを使用してデータを複製します。TiDB は、成功の結果を返す前に、レプリカの大部分にデータを書き込みます。このようにして、いくつかのレプリカが失われても、システムには最新のデータがあります。たとえば、レプリカが 3 つある場合、2 つのレプリカにデータが書き込まれるまで、システムは成功の結果を返しません。レプリカが 1 つ失われると、残りの 2 つのレプリカの少なくとも 1 つに最新のデータがあります。
3 つのレプリカを保存する場合、ソースレプリカのレプリケーションと比較して、 Raft の方が効率的です。Raft の書き込みレイテンシーは、最も遅いレプリカではなく、最も速い 2 つのレプリカに依存します。したがって、 Raftレプリケーションを使用すると、地理的に分散された複数のアクティブなデータセンターの実装が可能になります。2 つのサイトに分散された 3 つのデータセンターの一般的なシナリオでは、データの一貫性を保証するために、TiDB は 3 つのデータセンターすべてに書き込むのではなく、ローカル データセンターとより近いデータセンターにデータを正常に書き込むだけで済みます。ただし、これはクロスデータセンター展開がどのシナリオでも実装できることを意味するものではありません。書き込むデータの量が多い場合、データセンター間の帯域幅とレイテンシーが重要な要素になります。書き込み速度が帯域幅を超えたり、レイテンシーが長すぎると、 Raftレプリケーション メカニズムは依然として正常に機能しません。
分散トランザクション
TiDB は完全な分散トランザクションを提供し、モデルにはGoogle パーコレーターに基づいたいくつかの最適化が施されています。このドキュメントでは、次の機能を紹介します。
楽観的取引モデル
TiDB の楽観的トランザクション モデルでは、コミット フェーズまで競合を検出しません。競合がある場合、トランザクションは再試行する必要があります。ただし、競合が深刻な場合は、再試行前の操作が無効になり、繰り返す必要があるため、このモデルは非効率的です。
データベースをカウンターとして使用すると仮定します。アクセスの同時実行性が高いと深刻な競合が発生し、再試行が複数回行われたり、タイムアウトになったりする可能性があります。したがって、深刻な競合が発生するシナリオでは、悲観的トランザクション モードを使用するか、Redis にカウンターを配置するなど、システムアーキテクチャレベルで問題を解決することをお勧めします。ただし、アクセスの競合がそれほど深刻でない場合は、楽観的トランザクション モデルが効率的です。
悲観的なトランザクションモード
TiDB では、悲観的トランザクション モードの動作は MySQL とほぼ同じです。トランザクションは実行フェーズでロックを適用し、競合状況での再試行を回避して、より高い成功率を保証します。悲観的ロックを適用することで、
SELECT FOR UPDATE
使用して事前にデータをロックすることもできます。ただし、アプリケーション シナリオの競合が少ない場合は、楽観的トランザクション モデルの方がパフォーマンスが向上します。
トランザクションサイズの制限
分散トランザクションは 2 フェーズ コミットを実行する必要があり、最下層はRaftレプリケーションを実行するため、トランザクションが非常に大きい場合、コミット プロセスは非常に遅くなり、後続のRaftレプリケーション プロセスが停止します。この問題を回避するために、トランザクション サイズは制限されています。
- トランザクションは 5,000 個の SQL ステートメントに制限されます (デフォルト)
- 各キー値エントリは 6 MB 以下です (デフォルト)
- キー値エントリの合計サイズは 10 GB 以下です。
Google クラウド スパナにも同様の制限があります。
データシャーディング
TiKV は、キーの範囲に応じて最下層のデータを自動的に分割します。各リージョンはキーの範囲であり、左が閉じ、右が開いている間隔[StartKey, EndKey)
。リージョン内のキーと値のペアの量が特定の値を超えると、リージョンは自動的に 2 つに分割されます。
負荷分散
配置Driver(PD) は、TiKV クラスター全体の状態に応じてクラスターの負荷を分散します。スケジュールの単位はリージョンであり、ロジックは PD によって構成された戦略です。
KV 上の SQL
TiDB は SQL 構造をキー値構造に自動的にマッピングします。詳細についてはTiDB 内部 (II) - コンピューティングを参照してください。
簡単に言えば、TiDB は次の操作を実行します。
- データの行はキーと値のペアにマッピングされます。キーの先頭には
TableID
が付き、末尾には行 ID が付きます。 - インデックスはキーと値のペアとしてマッピングされます。キーの先頭には
TableID+IndexID
が付き、末尾にはインデックス値が付きます。
同じテーブル内のデータまたはインデックスには同じプレフィックスがあります。これらのキー値は、TiKV のキー空間内の隣接する位置にあります。そのため、書き込むデータの量が多く、すべてが 1 つのテーブルに書き込まれると、書き込みホットスポットが作成されます。連続して書き込まれるデータの一部のインデックス値も連続している場合 (たとえば、 update time
ように時間とともに増加するフィールド)、状況はさらに悪化し、いくつかの書き込みホットスポットが作成され、システム全体のボトルネックになります。
同様に、すべてのデータが集中した狭い範囲(たとえば、連続した数万行または数十万行のデータ)から読み取られる場合、データのアクセス ホットスポットが発生する可能性があります。
セカンダリインデックス
TiDB は、グローバル インデックスでもある完全なセカンダリ インデックスをサポートしています。多くのクエリはインデックスによって最適化できます。したがって、アプリケーションではセカンダリ インデックスを有効活用することが重要です。
多くの MySQL の経験は TiDB にも適用できます。TiDB には独自の機能があることが注目されます。以下は、TiDB でセカンダリ インデックスを使用する際の注意事項です。
セカンダリインデックスは多ければ多いほど良いのでしょうか?
セカンダリ インデックスはクエリを高速化できますが、インデックスを追加すると副作用があります。前のセクションでは、インデックスのstorageモデルについて説明しました。行を挿入するときに、インデックスを追加するごとに、キーと値が 1 つ増えます。したがって、インデックスの数が増えると、書き込み速度が遅くなり、占有するスペースが増えます。
さらに、インデックスが多すぎるとオプティマイザの実行時間に影響し、不適切なインデックスはオプティマイザを誤導します。したがって、セカンダリ インデックスの数が増えてもパフォーマンスが向上するわけではありません。
どの列にインデックスを作成する必要がありますか?
前述の通り、インデックスは重要ですが、インデックスの数は適切である必要があります。アプリケーションの特性に応じて適切なインデックスを作成する必要があります。原則として、パフォーマンスを向上させるには、クエリに関係する列にインデックスを作成する必要があります。インデックスを作成する必要がある状況は次のとおりです。
- 差別化の度合いが高い列の場合、インデックスによってフィルタリングされた行が大幅に削減されます。
- クエリ条件が複数ある場合は、複合インデックスを選択できます。複合インデックスの前に、同等の条件を持つ列を配置することに注意してください。
たとえば、よく使用されるクエリが
select * from t where c1 = 10 and c2 = 100 and c3 > 10
の場合、複合インデックスIndex cidx (c1, c2, c3)
を作成できます。このように、クエリ条件を使用してインデックスプレフィックスを作成し、スキャンすることができます。インデックスを介したクエリとテーブルを直接スキャンすることの違い
TiDB はグローバル インデックスを実装しているため、テーブルのインデックスとデータは必ずしも同じデータ シャーディング上にあるわけではありません。インデックスを介してクエリを実行する場合、最初にインデックスをスキャンして対応する行 ID を取得し、次に行 ID を使用してデータを取得する必要があります。したがって、この方法では 2 つのネットワーク要求が伴い、一定のパフォーマンス オーバーヘッドが発生します。
クエリに多数の行が含まれる場合、インデックスのスキャンは同時に進行します。最初のバッチの結果が返されると、テーブルのデータの取得を続行できます。したがって、これは並列 + パイプライン モデルです。2 つのアクセスによってオーバーヘッドが発生しますが、レイテンシーは高くありません。
次の 2 つの条件では、2 つのアクセスの問題は発生しません。
インデックスの列はすでにクエリ要件を満たしています。
t
テーブルのc
列にインデックスがあり、クエリがselect c from t where c > 10;
であると仮定します。 このとき、インデックスにアクセスすると必要なデータがすべて取得されます。 この状況をCovering Index
と呼びます。 ただし、クエリのパフォーマンスを重視する場合は、フィルターする必要はないがクエリ結果で返す必要がある列の一部をインデックスに入れて、複合インデックスを作成できます。select c1, c2 from t where c1 > 10;
例にとります。 複合インデックスIndex c12 (c1, c2)
を作成することで、このクエリを最適化できます。テーブルの主キーは整数です。この場合、TiDB は主キーの値を行 ID として使用します。したがって、クエリ条件が主キーにある場合は、行 ID の範囲を直接構築し、テーブル データをスキャンして結果を取得できます。
クエリの同時実行
データは多くのリージョンに分散されるため、クエリは TiDB で同時に実行されます。ただし、大量のシステム リソースを消費する場合、デフォルトでは同時実行性は高くありません。また、OLTP クエリでは通常、大量のデータが処理されないため、同時実行性は低くても十分です。ただし、OLAP クエリの場合、同時実行性は高く、TiDB は次のシステム変数を使用してクエリの同時実行性を変更します。
tidb_distsql_scan_concurrency
:テーブルとインデックス データのスキャンを含む、データ スキャンの同時実行性。
テーブル データにアクセスする前に行 ID を取得するためにインデックスにアクセスする必要がある場合、行 ID のバッチを単一の要求として使用してテーブル データにアクセスします。このパラメータはバッチのサイズを設定します。バッチが大きいほどレイテンシーが増加し、小さいほどクエリが増える可能性があります。このパラメータの適切なサイズは、クエリに含まれるデータの量に関係します。通常、変更は必要ありません。
tidb_index_lookup_concurrency
:テーブル データにアクセスする前に行 ID を取得するためにインデックスにアクセスする必要がある場合、このパラメータによって、毎回行 ID を介してデータを取得する同時実行性が変更されます。
インデックスを通じて結果の順序を確保する
インデックスを使用して、データをフィルタリングまたは並べ替えることができます。まず、インデックスの順序に従って行 ID を取得します。次に、行 ID の戻り順序に従って行の内容を返します。このようにして、返される結果はインデックス列に従って順序付けられます。インデックスをスキャンして行を取得するモデルは並列 + パイプラインであることは前に説明しました。行がインデックスの順序に従って返される場合、2 つのクエリ間の同時実行性が高くても、レイテンシーは短縮されません。したがって、同時実行性はデフォルトで低くなっていますが、
tidb_index_serial_scan_concurrency
変数を通じて変更できます。逆インデックススキャン
TiDB は、昇順インデックスの逆順スキャンをサポートしていますが、速度は通常のスキャンより 20% 遅くなります。データが頻繁に変更され、バージョンが多すぎる場合は、パフォーマンスのオーバーヘッドが高くなる可能性があります。逆インデックススキャンはできる限り避けることをお勧めします。
シナリオと実践
前のセクションでは、TiDB の基本的な実装メカニズムと、それらが使用に与える影響について説明しました。このセクションでは、デプロイメントからアプリケーションの使用まで、具体的な使用シナリオと運用方法を紹介します。
展開
展開する前に、 ソフトウェアおよびハードウェアの要件お読みください。
TiUPを使用して TiDB クラスターをデプロイすることをお勧めします。このツールを使用すると、クラスター全体をデプロイ、停止、破棄、アップグレードできるため、非常に便利です。TiDB クラスターを手動でデプロイすることはお勧めしません。後でメンテナンスやアップグレードが面倒になる可能性があります。
データのインポート
インポート プロセス中の書き込みパフォーマンスを向上させるには、 TiKV メモリ パラメータのパフォーマンスを調整するで説明したように TiKV のパラメータを調整できます。
書く
前述のように、TiDB はキー値レイヤーで単一のトランザクションのサイズを制限します。SQLレイヤーでは、データの行がキー値エントリにマップされます。追加のインデックスごとに、キー値エントリが 1 つ追加されます。
注記:
トランザクションのサイズ制限を設定する場合、TiDB エンコーディングのオーバーヘッドと追加のトランザクション キーを考慮する必要があります。各トランザクションの行数は 200 未満、1 行のデータ サイズは 100 KB 未満にすることをお勧めします。そうでない場合、パフォーマンスが低下します。
ステートメントをバッチに分割するか、ステートメントの数UPDATE
INSERT
、またはDELETE
に制限を追加することをお勧めします。
大量のデータを削除する場合は、 Delete from t where xx limit 5000;
使用することをお勧めします。ループを介して削除し、 Affected Rows == 0
ループの終了条件として使用します。
一度に削除する必要があるデータの量が多い場合、このループ メソッドは、削除ごとに逆方向に移動するため、どんどん遅くなります。前のデータを削除した後、多くの削除済みフラグが短期間残り (その後、ガベージ コレクションによってすべてがクリアされます)、次のDELETE
ステートメントに影響します。可能であれば、 WHERE
条件を絞り込むことをお勧めします。5 2017-05-26
すべてのデータを削除する必要があると仮定すると、次のステートメントを使用できます。
for i from 0 to 23:
while affected_rows > 0:
delete from t where insert_time >= i:00:00 and insert_time < (i+1):00:00 limit 5000;
affected_rows = select affected_rows()
この疑似コードは、前のDelete
ステートメントが後のステートメントに影響を与えないように、大きなデータのチャンクを小さなチャンクに分割して削除することを意味します。
クエリ
クエリの要件と具体的なステートメントについては、 システム変数を参照してください。
SET
ステートメントによる SQL 実行の同時実行と、ヒントによるJoin
演算子の選択を制御できます。
さらに、MySQL の標準のインデックス選択、ヒント構文を使用したり、オプティマイザを制御してUse Index
Ignore Index hint
インデックスを選択したりすることもできます。
アプリケーション シナリオに OLTP ワークロードと OLAP ワークロードの両方がある場合は、OLTP 要求と OLAP 要求を別の TiDB サーバーに送信して、OLAP が OLTP に与える影響を軽減できます。OLAP ワークロードを処理する TiDBサーバーには、高性能ハードウェア (たとえば、プロセッサ コア数が多く、メモリが大きい) を搭載したマシンを使用することをお勧めします。
OLTP と OLAP のワークロードを完全に分離するには、 TiFlashで OLAP アプリケーションを実行することをお勧めします。TiFlashは、OLAP ワークロードで優れたパフォーマンスを発揮する列指向のstorageエンジンです。TiFlashは、storageレイヤーで物理的な分離を実現し、一貫した読み取りを保証します。
監視とログ
監視メトリックは、システムの状態を確認するための最適な方法です。TiDB クラスターとともに監視システムを導入することをお勧めします。
TiDB はグラファナ + プロメテウス使用してシステムの状態を監視します。TiUP を使用してTiUPをデプロイすると、監視システムが自動的にデプロイされ、構成されます。
監視システムには多くの項目があり、そのほとんどは TiDB 開発者向けです。ソース コードの詳細な知識がなくても、これらの項目を理解する必要はありません。アプリケーションやシステム キー コンポーネントの状態に関連する一部の項目が選択され、ユーザー用の別のoverview
つのパネルに配置されます。
監視だけでなく、システムログも閲覧できます。TiDB の 3 つのコンポーネント、tidb-server、tikv-server、pd-server には、それぞれ--log-file
パラメータがあります。クラスタ起動時にこのパラメータが設定されている場合は、パラメータで設定されたファイルにログが保存され、ログファイルは毎日自動的にアーカイブされます。3 --log-file
パラメータが設定されていない場合は、ログはstderr
に出力されます。
TiDB 4.0 以降、TiDB は使いやすさを向上させるためにTiDBダッシュボード UI を提供します。ブラウザでhttp://{PD_PORT}/ダッシュボードにアクセスすると、TiDB ダッシュボードにアクセスできます。TiDB ダッシュボードには、クラスター ステータスの表示、パフォーマンス分析、トラフィックの視覚化、クラスター診断、ログ検索などの機能があります。
ドキュメンテーション
システムについて学んだり問題を解決したりする最良の方法は、そのドキュメントを読んで実装の原則を理解することです。
TiDB には中国語と英語の両方で多数の公式ドキュメントがあります。問題に遭遇した場合は、 FAQとTiDBクラスタシューティング ガイドから始めることができます。また、問題リストを検索したり、 GitHub の TiDB リポジトリで問題を作成したりすることもできます。
TiDB には便利な移行ツールも多数あります。詳細については移行ツールの概要参照してください。
TiDB の技術的な詳細に関する記事については、 PingCAP公式ブログサイト参照してください。
TiDBに最適なシナリオ
TiDB は次のシナリオに適しています。
- データ量がスタンドアロンデータベースには大きすぎる
- シャーディングはしたくない
- アクセスモードには明らかなホットスポットがない
- トランザクション、強力な一貫性、災害復旧が必須
- リアルタイムのハイブリッドトランザクション/分析処理(HTAP)分析を実現し、storageリンクを削減したい