TiDB ベストプラクティス
このドキュメントでは、SQL の使用や、オンライン分析処理 (OLAP) およびオンライン トランザクション処理 (OLTP) シナリオの最適化のヒント、特に TiDB 固有の最適化オプションなど、TiDB の使用に関するベスト プラクティスをまとめています。
このドキュメントを読む前に、TiDB の技術的原理を紹介する次の 3 つのブログ投稿を読むことをお勧めします。
序文
データベースは汎用的なインフラストラクチャシステムです。開発プロセスにおいては、様々なユーザーシナリオを考慮し、具体的なビジネスシナリオにおける実際の状況に応じて、データのパラメータや利用方法を調整することが重要です。
TiDBは、MySQLのプロトコルと構文と互換性のある分散データベースです。ただし、内部実装と分散storageおよびトランザクションのサポートにより、TiDBの使用方法はMySQLとは異なります。
基本概念
ベストプラクティスは実装の原則と密接に関連しています。Raftコンセンサスアルゴリズム、分散トランザクション、データシャーディング、負荷分散、SQLからキーバリュー(KV)へのマッピングソリューション、セカンダリインデックスの実装方法、分散実行エンジンなど、Raftかの基本的なメカニズムを習得することをお勧めします。
このセクションではこれらの概念について紹介します。詳細についてはPingCAPのブログ投稿を参照してください。
Raft
Raftは、強力な一貫性を備えたデータ複製を保証するコンセンサスアルゴリズムです。最レイヤーでは、TiDBはRaftを使用してデータを複製します。TiDBは、成功の結果を返す前に、大多数のレプリカにデータを書き込みます。これにより、いくつかのレプリカが失われても、システムは最新のデータを保持します。例えば、レプリカが3つある場合、2つのレプリカにデータが書き込まれるまで、システムは成功の結果を返します。レプリカが1つ失われても、残りの2つのレプリカのうち少なくとも1つには最新のデータが含まれています。
3 つのレプリカを保存する場合、ソースレプリカのレプリケーションと比較して、 Raft の方が効率的です。Raftの書き込みレイテンシーは、最も遅いレプリカではなく、最も速い 2 つのレプリカに依存します。そのため、 Raftレプリケーションを使用すると、地理的に分散された複数のアクティブなデータセンターの実装が可能になります。 3 つのデータセンターが 2 つのサイトに分散されている一般的なシナリオでは、データの一貫性を保証するために、TiDB は 3 つのデータセンターすべてに書き込むのではなく、ローカル データセンターとより近いデータセンターにデータを正常に書き込むだけで済みます。 ただし、これはクロスデータセンター展開がどのようなシナリオでも実装できることを意味するものではありません。書き込むデータの量が多い場合、データセンター間の帯域幅とレイテンシーが重要な要素になります。書き込み速度が帯域幅を超えるか、レイテンシーが大きすぎる場合、 Raftレプリケーション メカニズムは依然として正常に機能しません。
分散トランザクション
TiDBは完全な分散トランザクションを提供し、そのモデルはGoogle パーコレーターに基づいていくつかの最適化が施されています。このドキュメントでは、以下の機能を紹介します。
楽観的取引モデル
TiDBの楽観的トランザクションモデルは、コミットフェーズまで競合を検出しません。競合が発生した場合、トランザクションは再試行する必要があります。しかし、競合が深刻な場合、再試行前の操作は無効となり、再度実行する必要があるため、このモデルは非効率的です。
データベースをカウンターとして使用する場合を考えてみましょう。同時アクセス数が多いと深刻な競合が発生し、複数回の再試行やタイムアウトが発生する可能性があります。したがって、深刻な競合が発生するシナリオでは、悲観的トランザクションモードを使用するか、Redisにカウンターを配置するなど、システムアーキテクチャレベルで問題を解決することをお勧めします。ただし、アクセス競合がそれほど深刻でない場合は、楽観的トランザクションモデルが効率的です。
悲観的なトランザクションモード
TiDBでは、悲観的トランザクションモードはMySQLとほぼ同じ動作をします。トランザクションは実行フェーズでロックを適用し、競合状況での再試行を回避し、高い成功率を保証します。悲観的ロックを適用することで、
SELECT FOR UPDATE使用して事前にデータをロックすることもできます。ただし、アプリケーション シナリオの競合が少ない場合は、楽観的トランザクション モデルの方がパフォーマンスが向上します。
トランザクションサイズの制限
分散トランザクションは2相コミットを実行する必要があり、最レイヤーではRaftレプリケーションを実行するため、トランザクションが非常に大きい場合、コミットプロセスが非常に遅くなり、後続のRaftレプリケーションプロセスが停止する可能性があります。この問題を回避するために、トランザクションサイズは制限されています。
- トランザクションは 5,000 個の SQL 文に制限されます (デフォルト)
- 各キー値エントリは6 MB以下です(デフォルト)
- キー値エントリの合計サイズは 10 GB 以下です。
Google Cloud Spannerにも同様の制限があります。
データシャーディング
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モデルについて説明しました。追加したインデックスごとに、行を挿入する際にKey-Valueが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の基本的な実装メカニズムとそれらが利用に与える影響について説明しました。このセクションでは、導入からアプリケーションでの利用に至るまで、具体的な利用シナリオと運用方法を紹介します。
展開
展開する前に、 TiDB のソフトウェアおよびハードウェア要件お読みください。
TiDBクラスタはTiUP使用してデプロイすることをお勧めします。このツールはクラスタ全体のデプロイ、停止、破棄、アップグレードを実行できるため、非常に便利です。TiDBクラスタを手動でデプロイすることは推奨されません。後々のメンテナンスやアップグレードが面倒になる可能性があります。
データのインポート
インポート プロセス中の書き込みパフォーマンスを向上させるには、 TiKVメモリパラメータのパフォーマンスを調整するで説明したように TiKV のパラメータを調整できます。
書く
前述の通り、TiDBはキーバリューレイヤーにおける単一トランザクションのサイズを制限しています。SQLレイヤーでは、データ行がキーバリューエントリにマッピングされます。インデックスを追加するたびに、キーバリューエントリが1つずつ追加されます。
注記:
トランザクションのサイズ制限を設定する際には、TiDBエンコードと追加のトランザクションキーのオーバーヘッドを考慮する必要があります。各トランザクションの行数は200未満、1行のデータサイズは100KB未満に抑えることが推奨されます。これを超えると、パフォーマンスが低下します。
ステートメントをバッチに分割するか、ステートメントの数UPDATE INSERTまたはDELETEステートメント) に制限を追加することをお勧めします。
大量のデータを削除する場合は、 Delete from t where xx limit 5000;使うことをお勧めします。1 はループを通して削除し、 Affected Rows == 0ループの終了条件として使用します。
一度に削除する必要があるデータの量が多い場合、このループ方式は、削除処理が後方に移動するにつれて速度が低下します。前のデータを削除した後、多くの削除フラグが短期間残り(その後、ガベージコレクションによってすべてクリアされます)、次のDELETE文に影響を与えます。可能であれば、 WHERE番目の条件を絞り込むことをお勧めします。例えば、 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のワークロードを完全に分離するには、OLAPアプリケーションをTiFlash上で実行することをお勧めします。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リンクを削減したいと考えています。