
※このブログは2025年2月26日に公開された英語ブログ「Time’s Up! How TiDB Efficiently Handles Expired Data」の拙訳です。
大規模なデータを効率的に管理することは、現代のデータベースにとって重要な課題です。特に、すぐに古くなってしまう可能性のある時間に依存したデータを扱う場合はなおさらです。TiDB 6.5から実験的な機能として導入され、TiDB 7.0で一般提供になったTTL (Time To Live) は、期限切れデータの削除を自動化し、運用オーバーヘッドを最小限に抑えながら、データの鮮度を維持するための強力でカスタマイズ可能なソリューションを提供します。
TiDB TTLは、ユーザーが有効期限ルールを設定し、定期的な削除ジョブを実行できるようにすることで、データ保持ポリシーの遵守を簡素化するだけでなく、ストレージの使用を最適化し、システムのパフォーマンスを向上させます。テーブルにTTL属性を設定することで、TiDBは定期的に期限切れのレコードをチェックし、自動的にそれらを削除します。例えば:
CREATE TABLE t (
id INT UNSIGNED PRIMARY KEY,
created_at DATETIME
)
TTL = created_at + INTERVAL 10 HOUR
TTL_JOB_INTERVAL = '1h';
この設定により、created_at + INTERVAL 10 HOUR が現在の時刻よりも前になった場合、レコードが1時間毎に削除されることが保証され、データの新鮮さを維持し、ストレージの使用を最適化します。
このブログでは、TiDBのTTL機能の設計と実装に関する過程を説明します。具体的には以下の内容について詳しく解説します:
- オンラインワークロードへの影響を最小限に抑え、パフォーマンスを向上させるためにTTL関連のシステム変数をどのように設定するか。
- 分散SQLデータベースにおけるTTL機能の実装における考え方やトレードオフ。
- 期限切れデータを効率的に管理するためにTTL機能を最大限に活用するための重要な技術的知見。
TiDB TTLの設計:評価の方法とトレードオフ
TiDBでTTL機能を実装する際、チームは期限切れデータを自動的にクリーンアップするための異なる方法を評価しました。ここでは、検討された2つの主要なアプローチと、なぜTiDBが最終的に定期的なSQLベースの削除方法を選択したのかについて解説します。
オプション1:RocksDBコンパクションフィルタを使用する
この方法は、LSMツリーのコンパクション中に期限切れデータを削除します。TiKVのRawKV TTLはすでにこの機能を使用しています。しかし、このアプローチをTiDBに適用するにはいくつかの課題がありました:
- データフォーマットの変更:各キーと値のペアに有効期限を設定する必要があり、スキーマ更新 (DDL) 時に膨大な読み書き操作が発生します。
- 整合性の問題:テーブルデータとインデックスの整合性を保つことは、特にインデックスとデータが異なるTiKVノードに存在する場合、分散システムでは複雑です。
- トランザクションとの競合:TiDBのトランザクションモデル (MVCC) やロック機構を維持するのが難しくなります。例えば:
- 新しいバージョンを削除すると、古いバージョンが誤って露出してしまう。
- ロックされたデータを早期に削除すると、エラーが発生する。
これらの問題を緩和する (例:削除を遅延させる、読み込み時に期限切れデータをフィルタリングするなど) ことは可能ですが、このアプローチはリスクが高すぎ、TiDBのアーキテクチャに大規模な変更を加える必要があります。
結論:この方法は、複雑さが高く、正確性を損なうリスクがあり、他の機能への影響も大きい。
オプション2:SQLステートメントによる定期的な削除
このアプローチでは、SQLを使用して定期的に期限切れデータを特定し、削除します。シンプルかつ安全な方法ですが、いくつかのトレードオフも存在します。
- メリット
- 正確性の維持:SQLベースの削除により、テーブルデータとインデックスの整合性が保たれ、TiDBのトランザクションモデルと自然に調和します。
- 分離された設計:この方法は他の機能に対する影響が少なく、TTL機能を独立して実装できます。
- 課題
- リソース消費:インデックスがない場合、フルテーブルスキャンが必要になり、リソース負荷が高くなる可能性があります。
- インデックスの制限:分散データベースでは、時刻ベースのカラム (例:created_at ) にインデックスを追加すると、書き込みホットスポットが発生し、パフォーマンスが低下するため、一般的に推奨されません。
これらの課題はあるものの、TiDBはインデックスなしのシナリオを最適化し、フルテーブルスキャンを活用することでシンプルさと互換性を優先しました。
結論:この方法は、正確性・シンプルさ・TiDBのアーキテクチャとの互換性をバランスよく両立しています。
TiDBが定期的な削除を選択した理由
定期的なSQLベースの削除アプローチは、実現可能性、正確性、既存機能への影響の最小化という点で最適なバランスを提供します。この方法は、TiDBのアーキテクチャを大幅に変更する複雑さを回避しつつ、期限切れデータの確実なクリーンアップを実現します。一部のシナリオではリソース消費が大きくなる可能性がありますが、安全性と保守性を優先した設計により、TiDBのTTL機能にとって最適な解決策となっています。
TiDB TTLの仕組み
TiDBのTTL機能は、定期的にクリーンアップジョブをトリガーし、効率的に実行することで、期限切れデータの削除を自動化します。この機能には、TTLジョブのスケジューリングと、定められた運用の制約内での実行という2つの主要なプロセスが含まれます。
TiDB TTLジョブのスケジューリング
TTLジョブは定期的にトリガーされ、リソース消費と迅速なクリーンアップのバランスを取るように設定できます。
- デフォルトの動作:各テーブルのTTLジョブは24時間ごとに実行されます。
- カスタマイズ:TTL_JOB_INTERVAL プロパティを調整することで変更可能です。
- 間隔を長くすると、クリーンアップのリソース消費を抑えられます。
- 間隔を短くすると、期限切れデータを素早く削除できます。
TiDBは、TTLジョブのステータスを mysql.tidb_ttl_table_status システムテーブルで管理しており、以下の主要なカラムが含まれます。
- last_job_start_tim:最も最近実行されたTTLジョブの開始時刻を記録。
- current_job_id:現在TTLジョブが実行中かを示す (ジョブが実行されていない場合は空)。
cronのようなトリガー:
- TiDBノードは10秒ごとに mysql.tidb_ttl_table_status をチェックし、新しいTTLジョブが必要なテーブルがあるかを判定します。
- 新しいジョブは、以下の条件を満たした場合にトリガーされます。
- 現在実行中のTTLジョブがない (current_job_id が空)。
- 最後のジョブの実行から設定された間隔が経過している (last_job_start_time + TTL_JOB_INTERVAL < NOW())。
- 新しいジョブは、以下の条件を満たした場合にトリガーされます。
- ジョブがトリガーされると、TiDBノードはトランザクション内で current_* フィールドを更新し、1つのノードのみがジョブを開始するようにし、重複実行を防ぎます。
ステータステーブルを理解する
TiDBは、TTLジョブのメタデータを mysql.tidb_ttl_table_status テーブルに記録します。以下は主要なフィールドの意味です。
カラム | 説明 |
table_id | TTLジョブが適用されるテーブルの一意識別子。 |
last_job_id | このテーブルの最後に完了したTTLジョブのUUID。 |
last_job_start_time | 直近のTTLジョブが開始されたタイムスタンプ。 |
last_job_finish_time | 直近のTTLジョブが終了したタイムスタンプ。 |
last_job_summary | スキャンされた行、正常に削除された行、エラー (もしあれば) を含む、結果のJSONサマリー。 |
current_job_id | 現在実行中のTTLジョブのUUID (ジョブがアクティブでない場合は空)。 |
current_job_owner_id | このTTLジョブを現在処理しているTiDBノードの識別子。 |
current_job_owner_addr | 現在のジョブを管理するTiDBノードのアドレス。 |
current_job_start_time | 現在のTTLジョブが開始されたタイムスタンプ。 |
current_job_state | アクティブなTTLジョブの状態 (実行中、一時停止中など)。 |
アウトプットの例:
TABLE mysql.tidb_ttl_table_status LIMIT 1\G
*************************** 1. row ***************************
table_id: 85
parent_table_id: 85
table_statistics: NULL
last_job_id: 0b4a6d50-3041-4664-9516-5525ee6d9f90
last_job_start_time: 2023-02-15 20:43:46
last_job_finish_time: 2023-02-15 20:44:46
last_job_ttl_expire: 2023-02-15 19:43:46
last_job_summary: {"total_rows":4369519,"success_rows":4369519,"error_rows":0,"total_scan_task":64,"scheduled_scan_task":64,"finished_scan_task":64}
current_job_id: NULL
current_job_owner_id: NULL
current_job_owner_addr: NULL
current_job_owner_hb_time: NULL
current_job_start_time: NULL
current_job_ttl_expire: NULL
current_job_state: NULL
current_job_status: NULL
current_job_status_update_time: NULL
このテーブルは、TiDBノード間でTTLジョブの調整を行い、特定のジョブを同時に管理するノードが1つだけになるように制御します。
トラフィックの少ない時間帯にTiDBのTTLジョブを実行する
データベースのワークロードには、システム負荷が低い時間帯 (例:夜間) が存在することがよくあります。TiDBでは、この時間帯を活用できるように、TTLジョブを実行できる時間枠を定義できます。
時間枠の設定:
- tidb_ttl_job_schedule_window_start_time:TTLジョブの実行を開始できる最も早い時刻を指定。
- tidb_ttl_job_schedule_window_end_time:TTLジョブの実行を終了する最も遅い時刻を指定。
デフォルトでは、TTLジョブはUTC 00:00 〜 UTC 23:59 の間で実行されます。この時間枠以外では以下の動作になります。
- 進行中のTTLジョブはキャンセルされる。
- 新しいジョブは時間枠が再び開くまでトリガーされない。
動作の仕組み:
- TTLジョブを開始する前に、TiDBは現在時刻が設定された時間枠内にあるかを確認します。
- 時間枠外の場合、すべての進行中のTTLジョブが停止し、新しいジョブは開始されません。
まとめ
TiDBのTTL機能は、cronのようなスケジューリングと設定可能な時間枠を組み合わせることで、効率的かつ影響を最小限に抑えた期限切れデータのクリーンアップを実現します。これにより、適切なタイミングでのデータ削除と運用の安定性を柔軟に両立できます。
次に、期限切れデータの特定と削除のプロセスについて詳しく見ていきます。
期限切れデータの削除
データベース内の古いデータを削除するには、単純な DELETE 文を実行するだけで済む場合があります。例えば、次のようなテーブル設定を考えてみましょう。
CREATE TABLE t (
id INT UNSIGNED PRIMARY KEY,
created_at DATETIME
) TTL = created_at + INTERVAL 10 HOUR TTL_JOB_INTERVAL = '1h';
created_at + INTERVAL 10 HOUR が特定のタイムスタンプより前のレコードを削除するには、次のように実行します。
DELETE FROM t WHERE created_at + INTERVAL 10 HOUR < '2024-12-13 12:52:01.123456';
しかし、テーブルのサイズが大きくなるにつれて、この直接的なアプローチにはいくつかの課題が発生します。
- トランザクションサイズ:単一の DELETE 文が非常に大きなトランザクションを生成し、実行に時間がかかることがあります。失敗した場合、操作全体がロールバックされます。
- リソースへの影響:削除操作はリソースを消費し、その速度を制限するのが難しく、他のオンラインサービスに影響を与える可能性があります。
バッチ処理による削除の最適化
これらの問題に対処するため、TiDBは削除プロセスを2つのステップに分けています。
- 期限切れデータのクエリ:期限切れの基準を満たすレコードを特定します。
- 削除の実行:期限切れレコードを小さな管理可能なバッチで削除します。
実際の処理は次のように進みます:
ステップ1:バッチでクエリを実行
テーブル全体を一度にスキャンするのではなく、SELECT クエリは LIMIT 句を使用してレコードを小さなチャンクで取得します。例えば:
SELECT id FROM t WHERE created_at + INTERVAL 10 HOUR < '2024-12-13 12:52:01.123456' ORDER BY id ASC LIMIT 500;
SELECT id FROM t WHERE created_at + INTERVAL 10 HOUR < '2024-12-13 12:52:01.123456' AND id > x ORDER BY id ASC LIMIT 500;
SELECT id FROM t WHERE created_at + INTERVAL 10 HOUR < '2024-12-13 12:52:01.123456' AND id > y ORDER BY id ASC LIMIT 500;
バッチサイズ (例:500) は、tidb_ttl_scan_batch_size 変数を使用して調整できます。このアプローチにより、一度にクエリされるデータは少量に制限され、システムへの負荷が軽減されます。
ステップ2:バッチで削除を実行
同様に、削除は IN 句を使用してバッチで実行され、各操作で影響を受ける行数が制限されます。例えば:
DELETE FROM t WHERE id IN (id1, id2, ..., id100);
DELETE FROM t WHERE id IN (id101, id102, ..., id200);
これらのバッチのサイズは、tidb_ttl_delete_batch_size 変数を使って制御できます。
バッチ処理が効果的な理由
SELECT と DELETE 操作をバッチに分割することにより、
- 各トランザクションが小さくなり、失敗しにくくなります。
- システムリソースがより効率的に使用され、他のワークロードのパフォーマンス低下リスクが減少します。
- クエリと削除の速度に対して、設定を通じて細かな制御が可能になります。
この方法により、数百万件のレコードを持つ大きなテーブルであっても、データベースに負荷をかけることなく効果的に処理できます。フロー制御とリソース使用の管理に関する詳細は、後で説明します。
ローカルTiDB TTLタスクスケジューリング
期限切れデータを効率的に処理するため、TiDBはTTLジョブを小さなサブタスクに分割し、クラスター全体で並列に実行できるようにします。このアプローチにより、大規模な展開でもワークロードが均等に分散され、スムーズな実行が保証されます。
タスクをサブタスクに分割
TiDBはテーブルを複数のリージョンに分割し、各リージョンをサブタスクに割り当てます。この分割により、ワークロードがシステム全体に均等に分散されます。例えば、テーブルの id 値が1から10,000までの場合、サブタスクは128から999までの値のみを処理するかもしれません。このサブタスクのクエリは、その範囲に絞って処理する条件を含みます。
SELECT id
FROM t
WHERE created_at + INTERVAL 10 HOUR < CURRENT_TIMESTAMP
AND id >= 128 AND id < 999
ORDER BY id ASC LIMIT 500;
このターゲットを絞ったアプローチにより、冗長なスキャンを防ぎ、処理が効率的に保たれます。
サブタスクの並列実行
TiDBは、サブタスクを処理するために2種類のワーカーを使用します:
- スキャンワーカー:期限切れデータをバッチでクエリします。
- 削除ワーカー:スキャン結果に基づいて期限切れのレコードを削除します。
これらのワーカーは並列に動作し、スキャンワーカーが結果を削除ワーカーに渡します。複数のサブタスクがノード全体で同時に実行され、リソースの最適な使用が確保されます。
分散実行と耐障害性
クラスタリソースを最大化するため、TiDBはサブタスクをノード間で分散します。サブタスクは mysql.tidb_ttl_task テーブルに記録され、アイドル状態のノードがタスクを取得して実行します。
もしノードがオフラインになると、他のノードが問題を検出し、未完了の作業を引き継いで実行します。これにより、すべてのサブタスクが確実に完了することが保証されます。この耐障害設計により、TTLプロセスは高い回復力をもちつつ効率的に保たれます。
主な利点
TTLタスクを分割して分散することにより、TiDBは以下を実現します:
- パフォーマンスの向上:サブタスクが並列で実行されるため、実行時間が短縮されます。
- 信頼性のある実行:内蔵された耐障害機能により、タスクを確実に実行します。
- 効率的なリソース使用:ノードがタスクを均等に分配し、ボトルネックを防ぎます。
この合理化されたアプローチにより、TTLジョブは他の操作に最小限の影響を与えつつ、効率的に処理されます。
このような SELECT クエリはすでにバッチ処理されていますが、後続のクエリは前のクエリの結果に依存するため、同時に実行することはできません。一方で、削除処理自体は id の順序に依存しないため、プライマリーキーを利用してシャーディングすることが可能です。
サブタスクの分割
データ量をできるだけ均等に分散するため、TiDBはテーブルのリージョンを min(max(64, tikv count), region count) 部分に分割します。各リージョンが類似のデータ量を持っていると仮定すると、各サブタスクのデータ量も類似することになります。min(…, region count) 部分は理解しやすいもので、リージョン数が少なすぎると分割が不十分になり、各サブタスクが1つしか割り当てられなくなるからです。また、max(128, tikv count) は、クラスタが十分に大きい場合にTTLパフォーマンスをさらに向上させるためです。
各サブタスクは1つ以上の連続したリージョンを含み、これらのリージョンの開始点と終了点がそのサブタスクのスキャン範囲となります。上記のテーブル t を例にすると、もし id 範囲が [128, 999) のサブタスクを取得した場合、このサブタスクは次のような条件を付けて、前述の SELECT 文を実行します: id >= 128 AND id < 999
SELECT id FROM t WHERE created_at + INTERVAL 10 HOUR < '2024-12-13 12:52:01.123456' AND id >= 128 AND id < 999 ORDER BY id ASC LIMIT 500
SELECT id FROM t WHERE created_at + INTERVAL 10 HOUR < '2024-12-13 12:52:01.123456' AND id > x AND id >= 128 AND id < 999 ORDER BY id ASC LIMIT 500
SELECT id FROM t WHERE created_at + INTERVAL 10 HOUR < '2024-12-13 12:52:01.123456' AND id > y AND id >= 128 AND id < 999 ORDER BY id ASC LIMIT 500
...
サブタスク内の複数のバッチは依然として順次実行されますが、複数のサブタスクは互いに干渉することなく同時に実行できます。
リージョンの開始点と終了点をSQLの構文要素(整数や文字列など)にマッピングするのは容易ではありません。例えば、リージョンの境界が有効なUTF-8文字列でない場合もあります。現在TiDBでは、この種の分割に関して多くの制限があるため、使用制限についてはドキュメントを参照してください。
スキャンワーカーと削除ワーカー
複数のサブタスクが同時に実行されるため、TiDBはスキャンワーカーと削除ワーカーを抽象化してこれらのTTLサブタスクを実行します。各サブタスクは、SELECT 文を実行するためにスキャンワーカーを占有し、クエリ結果をチャネルを通じて削除ワーカーに送信します。

このように、TTLの実行速度は以下のいくつかの要因によって制限される可能性があります:
- スキャンワーカーが少なすぎて、多くのサブタスクが実行待ちの状態になる。
- 削除ワーカーが少なすぎて、スキャンされた期限切れデータが削除待ちになり、それがスキャンワーカーをブロックする。
- サブタスクが非常に少なく、削除が順次かつ遅く行われる。
TiDBは、tidb_ttl_scan_worker_count と tidb_ttl_delete_worker_count という変数を使用して、スキャンワーカーと削除ワーカーの数を調整します。これらの変数はTTL機能によるリソース消費を制限するために減少させることも、TTLのクエリや期限切れデータの削除を高速化するために増加させることもできます。
適切な値を維持することは容易ではありません。期限切れデータの分布やテーブルの幅はテーブルごとに異なるため、これらの2つの変数は実際の条件に基づいて柔軟に調整する必要があります。TTLの速度を改善することを目指す場合、以下の原則に従ってこれらの2つの変数を段階的に調整できます。
下記のTiDB GrafanaモニタリングパネルでTTLセクションを確認します。これには、異なるステージでのスキャンワーカーと削除ワーカーの比率が記録されています。もしスキャンワーカーがほとんどディスパッチ状態 (スキャンされた期限切れデータを削除ワーカーに送信) で、削除ワーカーがほとんどアイドル状態でないことがわかれば、tidb_ttl_delete_worker_count を増加させることができます。

スキャンワーカーがディスパッチ状態にほとんどなく、削除ワーカーが頻繁にアイドル状態である場合、スキャンワーカーが非常に忙しいことを示しており、その場合は tidb_ttl_scan_worker_count を増加させるべきです。

TTLタスクの実行が遅くて、スキャンワーカーと削除ワーカーが両方とも idle 状態にある場合は、対応するTTLテーブルがドキュメントで説明されている複数のサブタスクに分割する条件を満たしているかどうかを確認してください。
DELETEステートメントのフロー制御
TTLでは、削除 (特別な書き込みの一種) はリソースをかなり消費するため、他のビジネスオペレーションに影響を与えないように、TTL機能の削除速度を制限する仕組みがあります。TiDBは、TiDBノードでTTL機能によるDELETEステートメントの実行数を秒単位で制限するために、システム変数 tidb_ttl_delete_rate_limit を提供しています。
これは、rate.Limiter を通じて実装されています。tidb_ttl_delete_rate_limit がゼロでない場合、TTLモジュールは各DELETEステートメントを実行する前に rate.Limiter.Wait() を呼び出して、現在の実行速度が tidb_ttl_delete_rate_limit/s を超えないようにします。
この制限は、TiDBノードごとに適用され、複数のTTLタスクがあっても、1つのノードで実行される削除の合計速度は tidb_ttl_delete_rate_limit を超えません。
複数ノードでのTiDB TTLタスクスケジューリング
前述の内容では、TiDB 6.5でのTTL実装のほとんどがカバーされています。
理想的には、TTLジョブは複数のサブタスクに分割して同時に実行できますが、すべての64サブタスクが同じTiDBで実行されると、クラスタ全体のリソースを十分に活用できません。したがって、TiDB 6.6ではTTLサブタスクを異なるTiDBノードに分散させる改善が行われ、各TiDBノードのスキャンワーカーと削除ワーカーが機能するようになりました。このセクションでは、この改善の設計について紹介します。
サブタスクスケジューリング
TiDBは、mysql.tidb_ttl_task テーブルを使用してTTLジョブから分割されたサブタスクを記録します。テーブル構造は次の通りです:
CREATE TABLE tidb_ttl_task (
job_id varchar(64) NOT NULL,
table_id bigint(64) NOT NULL,
scan_id int(11) NOT NULL,
scan_range_start blob DEFAULT NULL,
scan_range_end blob DEFAULT NULL,
expire_time timestamp NOT NULL,
owner_id varchar(64) DEFAULT NULL,
owner_addr varchar(64) DEFAULT NULL,
owner_hb_time timestamp NULL DEFAULT NULL,
status varchar(64) DEFAULT 'waiting',
status_update_time timestamp NULL DEFAULT NULL,
state textDEFAULT NULL,
created_time timestamp NOT NULL,
PRIMARY KEY (job_id,scan_id) /*T![clustered_index] NONCLUSTERED */,
KEY created_time (created_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin
TiDBノードがTTLジョブを開始すると、ローカルでの実行を直接開始するのではなく、複数のサブタスクに分割し、それらを tidb_ttl_task テーブルに登録します。
TTLジョブスケジューリングと同様に、各TiDBノードは1分ごとにこのテーブルをポーリングします。現在のノードにアイドル状態のスキャンワーカーがあり、owner_id が空のサブタスクが存在する場合、そのサブタスクの owner_id を更新し、アイドル状態のスキャンワーカーにそのサブタスクを実行させます。これにより、どのノードでも発行されたサブタスクを検出して実行できます。完了すると、オーナーはサブタスクのステータスを「完了」に更新し、現在のサブタスクが完了したことを示します。
どのTTLジョブが実行されたかについて気にする必要がないため、TTLジョブのオーナーはほとんど何もしません。10秒ごとに自分が管理するTTLジョブを確認し、対応するすべてのサブタスクが完了したかをチェックします。すべてが完了すれば、このTTLジョブは正常に終了したことになります。
tidb_ttl_scan_worker_count 変数は各ノードのスキャンワーカーの数を制御するため、クラスタ全体のスキャンワーカー数は tidb_ttl_scan_worker_count * TiDBのノード数 になります。これにより、クラスタに多くのTiDBノードがある場合、同時に実行されるTTLサブタスクの数が非常に多くなり、多くのリソースを消費することになります。
tidb_ttl_scan_worker_count はTiDBノード数の整数倍でしか同時実行されるサブタスクの数を制限できないため、微調整には不便です。TiDBは tidb_ttl_running_tasks という変数を提供し、同時に実行されるTTLサブタスクの数をさらに制限します。これにより、クラスター全体で同時に実行されるTTLサブタスクの数を制限できます。設定可能な値は -1 または [1,256] です。-1は、クラスターのTiKVノード数と同じ意味です。
フォールトトレランス
上記の設計により、各TTLジョブとサブタスクは特定のTiDBノードに属しています。そのため、TiDBノードが何らかの理由でオフラインになると、そのノードに属するジョブやサブタスクは進行できなくなります。この状況を避けるために、TiDBはTTLジョブとサブタスクに対してハートビートメカニズムを導入しました。
鋭い読者は、mysql.tidb_ttl_table_status テーブルと mysql.tidb_ttl_task テーブルの両方にハートビート時刻を更新するためのカラムがあることに気づいたかもしれません。mysql.tidb_ttl_table_status ではこのカラムは current_job_owner_hb_time、mysql.tidb_ttl_task では owner_hb_time です。
各TiDBノードは10秒ごとに自分が担当するTTLジョブのハートビート時刻を更新し、1分ごとに自分が担当するTTLサブタスクのハートビート時刻を更新します。もしノードがオフラインになると、対応する行のハートビート時刻の更新が停止します。他のノードが、指定された間隔の2倍の時間が経過したことを確認すると、対応するTTLジョブやサブタスクのオーナーが異常状態にあることが分かります。
これを発見したTiDBノードは、自分をジョブのオーナーとして設定し、すべてのサブタスクが完了しているかを定期的に確認し始めます。また、アイドル状態のスキャンワーカーを持つTiDBノードは、自分をサブタスクのオーナーとして設定し、クエリの実行と削除を開始します。
TiDB TTLの設定項目
前のセクションでは、TiDB TTLの全体設計と実装について、すべてのTTL関連の設定項目を順を追って紹介しました。読者はこれらの変数の起源と機能を明確に理解しているはずですが、さまざまなニーズに対応するために設計された多くの設定項目があるため、圧倒されるかもしれません。以下にまとめます:
テーブルごとの設定:
- TTL_JOB_INTERVAL:単一のテーブルに対する2つのTTLジョブがトリガーされる開始時刻の間隔を制御します。
TTL関連のグローバル変数:
- tidb_ttl_delete_batch_size と tidb_ttl_scan_batch_size:バッチ処理を実行するためのSELECTおよびDELETEステートメントのデータ量を調整します。上記の「期限切れデータの削除」セクションを参照してください。通常、調整する必要はありません。
- tidb_ttl_scan_worker_count と tidb_ttl_delete_worker_count:各TiDBノードのスキャンワーカーと削除ワーカーの数を調整します。「スキャンワーカーと削除ワーカー」セクションまたはドキュメントを参照して調整します。
- tidb_ttl_running_tasks:同時に実行されるTTLサブタスクの数を制限します。TTLリソースの使用を制限する場合は減らし、TTLパフォーマンスを向上させ、サブタスクのスケジューリングからスキャンワーカーへの制限を解消する場合は増やします。
- tidb_ttl_delete_rate_limit:単一のTiDBノードでのTTL削除レートを制限します。
- tidb_ttl_job_schedule_window_start_time と tidb_ttl_job_schedule_window_end_time:TTLが実行される1日の時間帯を制限します。
TiDB TTLのベストプラクティス
TiDBのTTL機能がどのように動作するかを理解すれば、自然にいくつかのベストプラクティスをまとめることができます。これらのルールに従うことで、TTL機能をより効果的に活用できます:
- クラスタ化インデックスを使用しているテーブルでは、整数型または照合順序がbinary、utf8mb4_bin、utf8mb4_0900_binの文字列を使用することを検討してください。そうしないと、サブタスクを分割できません。
- データの有効期限に基づいて、適切な TTL_JOB_INTERVAL を設定してください。データの有効期限と TTL_JOB_INTERVAL の比率が大きすぎないようにしてください。そうでないと、各TTLタスクでの期限切れデータの割合が非常に小さくなり、テーブル全体をスキャンする必要があるため、リソースが無駄になります。
まとめ
現在のTiDBのTTL実装にはいくつか改善の余地があります。例えば、期限切れデータの割合が非常に低い場合、1回の SELECT ステートメントの実行時間が急激に増加します。なぜなら、500件の期限切れレコードを集めるために、より多くのデータ (場合によってはテーブル全体) をスキャンする必要があるからです。また、特定の条件を満たすテーブルのみがサブタスクを分割できます (制限についてはドキュメントを参照できます)。
TiDBのTTL機能は継続的に改善されていますので、皆様のフィードバックをお待ちしています。さらに良くするためにどのような改善が必要かを、X (旧Twitter)、LinkedIn、またはSlack チャンネルを通じてお聞かせください。
TiDB Cloud Dedicated
TiDB Cloudのエンタープライズ版。
専用VPC上に構築された専有DBaaSでAWSとGoogle Cloudで利用可能。
TiDB Cloud Serverless
TiDB Cloudのライト版。
TiDBの機能をフルマネージド環境で使用でき無料かつお客様の裁量で利用開始。