TiDBのベストプラクティス
このドキュメントでは、SQLの使用や、オンライン分析処理(OLAP)およびオンライントランザクション処理(OLTP)シナリオの最適化のヒント、特にTiDBに固有の最適化オプションなど、TiDBを使用するためのベストプラクティスをまとめています。
このドキュメントを読む前に、TiDBの技術原則を紹介する3つのブログ投稿を読むことをお勧めします。
序文
データベースは一般的なインフラストラクチャシステムです。開発プロセス中にさまざまなユーザーシナリオを検討し、特定のビジネスシナリオの実際の状況に応じてデータパラメータまたは使用方法を変更することが重要です。
TiDBは、MySQLプロトコルおよび構文と互換性のある分散データベースです。ただし、分散ストレージとトランザクションの内部実装とサポートにより、TiDBの使用方法はMySQLとは異なります。
基本概念
ベストプラクティスは、その実装原則と密接に関連しています。 Raftコンセンサスアルゴリズム、分散トランザクション、データシャーディング、負荷分散、SQLからKey-Value(KV)へのマッピングソリューション、セカンダリインデックスの実装方法、分散実行など、いくつかの基本的なメカニズムを学ぶことをお勧めします。エンジン。
このセクションでは、これらの概念の概要を説明します。詳細については、 PingCAPブログ投稿を参照してください。
ラフト
Raftは、強力な一貫性を備えたデータ複製を保証するコンセンサスアルゴリズムです。最下層では、TiDBはRaftを使用してデータを複製します。 TiDBは、成功の結果を返す前に、レプリカの大部分にデータを書き込みます。このようにして、いくつかのレプリカが失われる可能性がありますが、システムには最新のデータが残っています。たとえば、レプリカが3つある場合、データが2つのレプリカに書き込まれるまで、システムは成功の結果を返しません。レプリカが失われると、残りの2つのレプリカの少なくとも1つに最新のデータが含まれます。
Source-Replicaのレプリケーションと比較して、3つのレプリカを格納するには、Raftの方が効率的です。 Raftの書き込みレイテンシは、最も遅いレプリカではなく、最も速い2つのレプリカに依存します。したがって、Raftレプリケーションを使用することで、地理的に分散された複数のアクティブなデータセンターの実装が可能になります。 3つのデータセンターが2つのサイトに分散している典型的なシナリオでは、データの一貫性を保証するために、TiDBは、3つのデータセンターすべてに書き込むのではなく、ローカルデータセンターとより近いデータセンターにデータを正常に書き込む必要があります。ただし、これは、データセンター間の展開をどのシナリオでも実装できることを意味するものではありません。書き込むデータの量が多い場合、データセンター間の帯域幅と遅延が重要な要素になります。書き込み速度が帯域幅を超えているか、レイテンシーが高すぎる場合でも、Raftレプリケーションメカニズムはうまく機能しません。
分散トランザクション
TiDBは完全な分散トランザクションを提供し、モデルにはGoogleパーコレーターに基づいていくつかの最適化があります。このドキュメントでは、次の機能を紹介します。
楽観的なトランザクションモデル
TiDBの楽観的なトランザクションモデルは、コミットフェーズまで競合を検出しません。競合がある場合は、トランザクションを再試行する必要があります。ただし、再試行前の操作は無効であり、繰り返す必要があるため、競合が深刻な場合、このモデルは非効率的です。
データベースがカウンターとして使用されていると仮定します。アクセスの同時実行性が高いと、深刻な競合が発生し、複数回の再試行やタイムアウトが発生する可能性があります。したがって、深刻な競合のシナリオでは、悲観的なトランザクションモードを使用するか、Redisにカウンターを配置するなどのシステムアーキテクチャレベルで問題を解決することをお勧めします。それでも、アクセスの競合がそれほど深刻でない場合は、楽観的なトランザクションモデルが効率的です。
悲観的なトランザクションモード
TiDBでは、悲観的なトランザクションモードはMySQLとほぼ同じ動作をします。トランザクションは実行フェーズ中にロックを適用します。これにより、競合状況での再試行が回避され、より高い成功率が保証されます。悲観的ロックを適用することにより、
SELECT FOR UPDATE
を使用して事前にデータをロックすることもできます。ただし、アプリケーションシナリオの競合が少ない場合は、楽観的なトランザクションモデルの方がパフォーマンスが高くなります。
トランザクションサイズの制限
分散トランザクションは2フェーズコミットを実行する必要があり、最下層がRaftレプリケーションを実行するため、トランザクションが非常に大きい場合、コミットプロセスは非常に遅くなり、次のRaftレプリケーションプロセスはスタックします。この問題を回避するために、トランザクションサイズは制限されています。
- トランザクションは5,000SQLステートメントに制限されています(デフォルト)
- 各Key-Valueエントリは6MB以下です(デフォルト)
- Key-Valueエントリの合計サイズは10GB以下です。
Google Cloud Spannerにも同様の制限があります。
データシャーディング
TiKVは、キーの範囲に応じて最下層のデータを自動的にシャーディングします。各リージョンはキーの範囲であり、これは左閉と右開の間隔[StartKey, EndKey)
です。リージョン内のキーと値のペアの数が特定の値を超えると、リージョンは自動的に2つに分割されます。
負荷分散
配置ドライバー(PD)は、TiKVクラスタ全体のステータスに応じてクラスタの負荷を分散します。スケジューリングの単位はRegionであり、ロジックはPDによって構成された戦略です。
KV上のSQL
TiDBは、SQL構造をKey-Value構造に自動的にマップします。詳細については、 TiDB内部(II)-コンピューティングを参照してください。
簡単に言うと、TiDBは次の操作を実行します。
- データの行は、キーと値のペアにマップされます。キーには
TableID
が接頭辞として付けられ、行IDが接尾辞として付けられます。 - インデックスは、キーと値のペアとしてマップされます。キーの接頭辞は
TableID+IndexID
で、接尾辞はインデックス値です。
同じテーブル内のデータまたはインデックスには同じプレフィックスが付いています。これらのキー値は、TiKVのキースペースの隣接する位置にあります。したがって、書き込むデータ量が多く、すべてが1つのテーブルに書き込まれる場合、書き込みホットスポットが作成されます。連続して書き込まれるデータの一部のインデックス値も連続している場合(たとえば、 update time
のように時間とともに増加するフィールド)、状況はさらに悪化します。これにより、書き込みホットスポットがいくつか作成され、システム全体のボトルネックになります。
同様に、すべてのデータが焦点を絞った狭い範囲(たとえば、連続する数万または数十万行のデータ)から読み取られる場合、データのアクセスホットスポットが発生する可能性があります。
二次インデックス
TiDBは、グローバルインデックスでもある完全なセカンダリインデックスをサポートします。多くのクエリはインデックスによって最適化できます。したがって、アプリケーションがセカンダリインデックスをうまく利用することが重要です。
多くのMySQLの経験がTiDBにも当てはまります。 TiDBには独自の機能があることに注意してください。以下は、TiDBでセカンダリインデックスを使用する際の注意事項です。
セカンダリインデックスが多いほど良いですか?
セカンダリインデックスはクエリを高速化できますが、インデックスを追加すると副作用があります。前のセクションでは、インデックスのストレージモデルを紹介しました。追加のインデックスごとに、行を挿入するときにもう1つのKey-Valueがあります。したがって、インデックスが多いほど、書き込み速度が遅くなり、より多くのスペースが必要になります。
さらに、インデックスが多すぎるとオプティマイザの実行時間に影響し、不適切なインデックスはオプティマイザを誤解させます。したがって、セカンダリインデックスが多いからといって、パフォーマンスが向上するわけではありません。
どの列がインデックスを作成する必要がありますか?
前述のように、インデックスは重要ですが、インデックスの数は適切である必要があります。アプリケーションの特性に応じて適切なインデックスを作成する必要があります。原則として、パフォーマンスを向上させるには、クエリに含まれる列にインデックスを作成する必要があります。インデックスを作成する必要がある状況は次のとおりです。
- 高度に区別された列の場合、フィルター処理された行はインデックスによって大幅に削減されます。
- 複数のクエリ条件がある場合は、複合インデックスを選択できます。複合インデックスの前に同等の条件の列を配置することに注意してください。
たとえば、一般的に使用されるクエリが
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はKey-Valueレイヤーの単一トランザクションのサイズを制限します。 SQLレイヤーの場合、データの行はKey-Valueエントリにマップされます。追加のインデックスごとに、もう1つのKey-Valueエントリが追加されます。
ノート:
トランザクションのサイズ制限を設定するときは、TiDBエンコーディングのオーバーヘッドと追加のトランザクションキーを考慮する必要があります。各トランザクションの行数は200未満、単一行のデータサイズは100KB未満にすることをお勧めします。そうしないと、パフォーマンスが低下します。
ステートメントをINSERT
、 UPDATE
つ、またはDELETE
のステートメントであるかどうかに関係なく、ステートメントをバッチに分割するか、ステートメントに制限を追加することをお勧めします。
大量のデータを削除する場合は、 Delete from t where xx limit 5000;
を使用することをお勧めします。ループを削除し、ループを終了する条件として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ワークロードを完全に分離するには、TiFlashでOLAPアプリケーションを実行することをお勧めします。 TiFlashは、OLAPワークロードで優れたパフォーマンスを発揮する列型ストレージエンジンです。 TiFlashは、ストレージレイヤーで物理的な分離を実現し、一貫した読み取りを保証します。
監視とログ
監視メトリックは、システムのステータスを学習するための最良の方法です。 TiDBクラスタと一緒に監視システムを展開することをお勧めします。
TiDBはGrafana + Prometheusを使用してシステムステータスを監視します。 TiUPを使用してTiDBを展開する場合、監視システムは自動的に展開および構成されます。
監視システムにはたくさんの項目があり、その大部分はTiDB開発者向けです。ソースコードの深い知識がなければ、これらの項目を理解する必要はありません。アプリケーションまたはシステムキーコンポーネントの状態に関連するいくつかの項目が選択され、ユーザー用に別のoverview
のパネルに配置されます。
監視に加えて、システムログを表示することもできます。 TiDBの3つのコンポーネント、tidb-server、tikv-server、およびpd-serverには、それぞれ--log-file
つのパラメーターがあります。クラスタの起動時にこのパラメータが設定されている場合、ログはパラメータで設定されたファイルに保存され、ログファイルは毎日自動的にアーカイブされます。 --log-file
パラメータが設定されていない場合、ログはstderr
に出力されます。
TiDB 4.0以降、TiDBは使いやすさを向上させるためにTiDBダッシュボードのUIを提供します。ブラウザでhttp:// $ {PD_IP}:$ {PD_PORT} / dashboardにアクセスすると、TiDBダッシュボードにアクセスできます。 TiDBダッシュボードは、クラスタステータスの表示、パフォーマンス分析、トラフィックの視覚化、クラスタ診断、ログ検索などの機能を提供します。
ドキュメンテーション
システムについて学習したり、問題を解決したりするための最良の方法は、そのドキュメントを読み、その実装原理を理解することです。
TiDBには、中国語と英語の両方で多数の公式文書があります。問題が発生した場合は、 FAQとTiDBクラスタートラブルシューティングガイドから始めることができます。問題リストを検索したり、 GitHub上のTiDBリポジトリで問題を作成したりすることもできます。
TiDBには、多くの便利な移行ツールもあります。詳細については、 移行ツールの概要を参照してください。
TiDBの技術的な詳細に関するその他の記事については、 PingCAP公式ブログサイトを参照してください。
TiDBの最良のシナリオ
TiDBは、次のシナリオに適しています。
- データ量がスタンドアロンデータベースには大きすぎます
- シャーディングをしたくない
- アクセスモードには明らかなホットスポットがありません
- トランザクション、強力な一貫性、およびディザスタリカバリが必要です
- リアルタイムのハイブリッドトランザクション/分析処理(HTAP)分析を行い、ストレージリンクを削減したいと考えています。