TiDBのベストプラクティス
このドキュメントでは、TiDB を使用する際のベストプラクティスをまとめています。これには、SQL の使用方法、オンライン分析処理 (OLAP) およびオンライントランザクション処理 (OLTP) シナリオにおける最適化のヒント、特に TiDB に特有の最適化オプションが含まれます。
この文書を読む前に、TiDBの技術的原理を紹介する以下の3つのブログ記事を読むことをお勧めします。
序文
データベースは汎用的なインフラストラクチャシステムです。開発プロセスにおいては、様々なユーザーシナリオを考慮し、特定のビジネスシナリオにおける実際の状況に応じて、データパラメータや使用方法を修正することが重要です。
TiDBは、MySQLのプロトコルと構文に対応した分散データベースです。しかし、内部的に分散storageとトランザクションを実装・サポートしているため、TiDBの使用方法はMySQLとは異なります。
基本概念
ベストプラクティスは、その実装原則と密接に関連しています。Raftコンセンサスアルゴリズム、分散トランザクション、データシャーディング、Raftバランシング、SQLからキーバリュー(KV)へのマッピングソリューション、セカンダリインデックスの実装方法、分散実行エンジンなど、基本的なメカニズムを習得することをお勧めします。
このセクションでは、これらの概念について説明します。詳細については、 PingCAPのブログ記事を参照してください。
Raft
Raftは、強力な一貫性を備えたデータ複製を保証する合意アルゴリズムです。TiDBは、最下層でRaftを使用してデータを複製します。TiDBは、成功の結果を返す前に、レプリカの過半数にデータを書き込みます。このようにして、少数のレプリカが失われた場合でも、システムは最新のデータを保持します。たとえば、レプリカが3つある場合、2つのレプリカにデータが書き込まれるまで、システムは成功の結果を返しません。レプリカが失われた場合でも、残りの2つのレプリカのうち少なくとも1つは最新のデータを保持します。
3 つのレプリカを保存する場合、ソースレプリカのレプリケーションと比較して、 Raft のRaftが効率的です。Raft の書き込みレイテンシーは、最も遅いレプリカではなく、最も速い 2 つのレプリカに依存します。そのため、 Raftレプリケーションを使用することで、地理的に分散した複数のアクティブなデータセンターの実装が可能になります。2 つのサイトに 3 つのデータセンターが分散している典型的なシナリオでは、データの一貫性を保証するために、TiDB は 3 つのデータセンターすべてに書き込むのではなく、ローカル データセンターとより近いデータセンターにデータを正しく書き込むだけで済みます。ただし、これは、あらゆるシナリオでクロス データセンター展開を実装できるという意味ではありません。書き込むデータ量が多い場合、データセンター間の帯域幅とレイテンシーが重要な要素になります。書き込み速度が帯域幅を超えたり、レイテンシーが高すぎたりすると、 Raftレプリケーション メカニズムはうまく機能しません。
分散トランザクション
TiDBは完全な分散トランザクションを提供し、そのモデルはGoogleパーコレーターをベースに最適化されています。このドキュメントでは、以下の機能を紹介します。
楽観的トランザクションモデル
TiDBの楽観的トランザクションモデルでは、コミットフェーズまで競合を検出しません。競合が発生した場合、トランザクションは再試行する必要があります。しかし、競合が深刻な場合、再試行前の操作は無効となり、繰り返す必要があるため、このモデルは非効率的です。
データベースがカウンターとして使用されていると仮定します。アクセス同時実行数が多いと、深刻な競合が発生し、複数回の再試行やタイムアウトにつながる可能性があります。そのため、深刻な競合が発生するシナリオでは、悲観的トランザクションモードを使用するか、Redisにカウンターを配置するなど、システムアーキテクチャレベルで問題を解決することを推奨します。ただし、アクセス競合がそれほど深刻でない場合は、楽観的トランザクションモデルでも十分です。
悲観的トランザクションモード
TiDB では、悲観的トランザクション モードは MySQL とほぼ同じ動作をします。トランザクションは実行フェーズ中にロックを適用し、競合状況での再試行を回避して成功率を高めます。悲観的ロックを適用することで、
SELECT FOR UPDATEを使用してデータを事前にロックすることもできます。しかし、アプリケーションシナリオにおける競合が少ない場合は、楽観的トランザクションモデルの方が優れたパフォーマンスを発揮します。
トランザクションサイズ制限
分散トランザクションは2フェーズコミットを実行する必要があり、下位レイヤーはRaftレプリケーションを実行するため、トランザクションが非常に大きい場合、コミット処理が非常に遅くなり、その結果、後続のRaftレプリケーション処理が停止してしまう可能性があります。この問題を回避するために、トランザクションサイズには制限が設けられています。
- トランザクションは(デフォルトでは)5,000個のSQLステートメントに制限されています。
- 各キーと値のペアのエントリは、デフォルトでは最大6MBです。
- キーと値のペアの合計サイズは10GB以下です。
Google Cloud Spannerでも同様の制限を確認できます。
データシャーディング
TiKVは、キーの範囲に基づいて最下層のデータを自動的にシャーディングします。各リージョンはキーの範囲であり、左端が閉じ、右端が開いた区間[StartKey, EndKey)です。リージョン内のキーと値のペアの数が一定の値を超えると、リージョンは自動的に2つに分割されます。
ロードバランシング
Placement Driver(PD)は、TiKVクラスタ全体の状態に応じてクラスタの負荷を分散します。スケジューリングの単位はリージョンであり、ロジックはPDによって構成された戦略です。
KVに対するSQL
TiDB は、SQL 構造を Key-Value 構造に自動的にマッピングします。詳細については、 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回のアクセスという問題は発生しません。
インデックスの列は既にクエリの要件を満たしています。
cテーブルのt列にインデックスがあり、クエリが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メモリパラメータのパフォーマンスを調整する。
書く
前述のとおり、TiDBはキーバリューレイヤーにおける単一トランザクションのサイズを制限しています。SQLレイヤーに関しては、データ行がキーバリューエントリにマッピングされます。インデックスを追加するごとに、キーバリューエントリが1つ追加されます。
注記:
トランザクションのサイズ制限を設定する際には、TiDBエンコーディングのオーバーヘッドと追加のトランザクションキーを考慮する必要があります。各トランザクションの行数は200未満、1行あたりのデータサイズは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ステートメントとヒントによるJoin演算子の選択によって、SQL 実行の同時実行を制御できます。
さらに、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を使用して TiDB を展開すると、監視システムが自動的に展開および構成されます。
監視システムには多くの項目があり、その大部分は TiDB 開発者向けです。ソースコードを深く理解していなくても、これらの項目を理解する必要はありません。アプリケーションやシステムの主要コンポーネントの状態に関連する項目は選択され、ユーザー向けに別のoverviewパネルに表示されます。
監視に加えて、システムログを表示することもできます。TiDB の 3 つのコンポーネント、tidb-server、tikv-server、および pd-server には、それぞれ--log-fileパラメータがあります。このパラメータがクラスタの起動時に構成されている場合、ログはパラメータで構成されたファイルに保存され、ログファイルは毎日自動的にアーカイブされます。 --log-fileパラメータが構成されていない場合、ログはstderrに出力されます。
TiDB 4.0 以降、TiDB は使いやすさを向上させるためにTiDBダッシュボードUI を提供します。 TiDB ダッシュボードにアクセスするには、ブラウザでhttp://{PD_PORT}/ダッシュボードダッシュボードにアクセスします。 TiDB ダッシュボードは、クラスターのステータスの表示、パフォーマンス分析、トラフィックの視覚化、クラスターの診断、ログ検索などの機能を提供します。
文書
システムについて学んだり問題を解決したりする最良の方法は、そのシステムのドキュメントを読み、実装原理を理解することです。
TiDB には、中国語と英語の両方で多数の公式文書があります。問題が発生した場合は、 FAQとTiDB クラスタトラブルシューティングガイドから始めることができます。 TiDBリポジトリ(GitHub)で課題リストを検索したり、課題を作成したりすることもできます。
TiDB には便利な移行ツールも多数あります。詳細については、移行ツールの概要ご覧ください。
TiDB の技術的な詳細に関する記事については、 PingCAP公式ブログサイトを参照してください。
TiDBの最適なシナリオ
TiDBは、以下のシナリオに適しています。
- データ量が大きすぎるため、スタンドアロンデータベースでは対応できません。
- シャーディングはしたくない
- アクセスモードには明らかなホットスポットはありません
- トランザクション、高い一貫性、およびディザスタリカバリが求められます。
- リアルタイムのハイブリッドトランザクション/分析処理(HTAP)分析を実現し、データパイプラインを削減したいと考えている。