TiDBワークショップ参加レポート
はじめに
先日、PingCAP社が主催するTiDBのワークショップに参加する機会がありました。TiDBはMySQL互換の分散データベースとして注目されていますが、その内部アーキテクチャがどのようになっているのか、具体的に学びPingCAP Certified TiDB Associateの取得を目指すことが目的でした。
このセッションは、TiDBの全体像から各コンポーネントの役割、さらには分散トランザクションやデータ一貫性の仕組みまで、非常に濃い内容でした。本記事では、そのワークショップで学んだ内容を自分なりに整理しました。
TiDBとは?アーキテクチャの全体像
TiDBは、オンライントランザクション処理 (OLTP) とオンライン分析処理 (OLAP) の両方を単一のデータベースでサポートする、HTAP (Hybrid Transactional/Analytical Processing) データベースです。
その最大の特徴は、以下の要素を兼ね備えている点です。
- 水平方向のスケールアウト/スケールイン: コンポーネントごとに追加・削除することで、性能や容量を柔軟に調整できます。
- 金融グレードの高可用性: Raftプロトコルを利用してデータの冗長性を確保し、障害発生時もサービスを継続できます。
- リアルタイムHTAP: 行ストア(TiKV)と列ストア(TiFlash)を組み合わせ、トランザクションと分析をリアルタイムに実行します。
- MySQL互換: MySQL 5.7プロトコルと互換性があり、既存のMySQLエコシステムをそのまま活用できます。
- クラウドネイティブ: クラウド環境での運用を前提として設計されています。
TiDBクラスタは、主に以下の3つのコアコンポーネントで構成されています。
- TiDB Server: SQLレイヤー。SQLの解析や実行計画の最適化を担当します。ステートレスです。
- TiKV Server: ストレージレイヤー。実際のデータをKey-Value形式で保存する分散ストレージです。
- PD (Placement Driver) Server: メタデータ管理レイヤー。クラスタ全体の「頭脳」として、データの配置情報やタイムスタンプの管理を行います。
さらに、分析処理を高速化するために TiFlash Server という列指向のストレージエンジンも存在します。アプリケーションはMySQLプロトコルを介してTiDB Serverに接続し、TiDB ServerがPDやTiKV/TiFlashと連携してクエリを処理する、というのが全体の流れです。
各コンポーネントの役割
1. TiDB Server
TiDB Serverは、クライアントからのSQLリクエストを受け付ける窓口です。データを保存しないため「ステートレス」であり、必要に応じて台数を増やすことで負荷分散が可能です。
主な役割は以下の通りです。
- クライアント接続の処理: MySQLプロトコルで接続を受け付け、認証・認可を行います。
- SQLの解析とコンパイル: 受け取ったSQL文を解析して抽象構文木 (AST) を生成し、それを基にコストベースの最適化を行い、最適な実行計画を生成します。
-
リレーショナルデータとKVデータの変換: TiDBではリレーショナルなテーブル構造を、TiKVが理解できるKey-Value形式にマッピングします。具体的には、
TableID
とRowID
(または主キー)を組み合わせてグローバルに一意なキーを生成します。 -
オンラインDDL: テーブルをロックすることなく、
ALTER TABLE
のようなDDL操作を実行できます。 - ガベージコレクション (GC): MVCC(後述)によって不要になった古いバージョンのデータをクリーンアップします。
2. TiKV Server
TiKVは、TiDBのデータが実際に格納される分散Key-Valueストレージです。単なるデータストアではなく、分散環境におけるトランザクション、一貫性、可用性を担保するための非常に高度な仕組みが実装されています。
データの永続化エンジン: RocksDB
TiKVは、データの永続化を行うストレージエンジンとして、Facebookが開発したRocksDBを採用しています。RocksDBはLSM (Log-Structured Merge) ツリーというデータ構造を持ち、特に書き込み性能に優れています。
書き込みプロセス
データが書き込まれる際のフローは以下のようになっています。
- WAL (Write-Ahead Log) への記録: まず、データの変更内容をWALファイルに書き込みます。これにより、万が一サーバーがクラッシュしてもメモリ上のデータが失われるのを防ぎ、耐久性を確保します。
-
MemTableへの書き込み: 次に、メモリ上のデータ構造である
MemTable
にデータを書き込みます。新しい書き込みは常にこのMemTable
に対して行われます。 -
Immutable MemTableへの変換:
MemTable
が設定されたサイズに達すると、書き込みがブロックされないように、そのMemTable
は読み取り専用のImmutable MemTable
に変換されます。同時に、新しい書き込みを受け付けるための新しいMemTable
が作成されます。 -
SSTableへのフラッシュ: バックグラウンドのスレッドが、
Immutable MemTable
の内容をディスク上のSSTable (Sorted String Table) というファイル形式で永続化します。
読み取りプロセスとキャッシュ
データを読み取る際は、まず最新のデータが格納されているMemTable
から検索し、見つからなければディスク上のSSTableを検索します。SSTableは複数レベルに階層化されており、最新のデータほど浅いレベルに存在します。
この読み取りを高速化するため、TiKVは以下の仕組みを利用しています。
- ブルームフィルター: 特定のキーがSSTable内に存在するかどうかを確率的に高速で判断する仕組みです。これにより、不要なディスクアクセスを削減します。
- ブロックキャッシュ: 頻繁にアクセスされるデータをメモリ上にキャッシュしておき、ディスクへのアクセスを減らします。
分散トランザクションの心臓部: Percolatorと2PC
TiDBはGoogleのPercolatorという論文で提唱されたモデルをベースに、分散トランザクションを実現しています。これは2フェーズコミット (2PC) プロトコルに基づいています。
トランザクションデータを管理するため、TiKV (RocksDB)内では以下の3つのカラムファミリー (CF) が使われます。
- Default CF: 実際のデータの値が格納されます。
- Lock CF: トランザクション中のデータにかけられるロック情報が格納されます。
- Write CF: トランザクションが正常にコミットされた際のコミット情報(どのバージョンを読むべきかなど)が格納されます。
2フェーズコミットは、以下の2段階の処理で実行されます。
-
Prewriteフェーズ:
- トランザクションで変更されるすべてのデータに対してロックをかけ、その情報をLock CFに書き込みます。
- 変更後の新しいデータを、トランザクションの開始タイムスタンプ(
start_ts
)と共にDefault CFに書き込みます。
-
Commitフェーズ:
- Prewriteがすべて成功したら、PDからコミット用のタイムスタンプ(
commit_ts
)を取得します。 - トランザクションのプライマリキーに対応するコミット情報をWrite CFに書き込みます。この書き込みが成功した時点で、トランザクションはコミットされたとみなされます。
- その後、バックグラウンドでLock CFに記録されたロックをクリーンアップします。
- Prewriteがすべて成功したら、PDからコミット用のタイムスタンプ(
高い並行性を実現するMVCC
TiDBはMVCC (Multi-Version Concurrency Control) を採用しており、これにより「読み取りが書き込みをブロックせず、書き込みが読み取りをブロックしない」という高い並行性を実現しています。
データの更新や削除が行われる際、古いデータを直接上書き(あるいは物理削除)するのではなく、PDから発行されたタイムスタンプ(start_ts
, commit_ts
)を使って新しいバージョンのデータを作成します。これにより、あるトランザクションがデータを変更中でも、他のトランザクションはコミット済みの古いバージョンのデータを読み取ることができ、処理がブロックされません。
データ一貫性を支えるMulti-Raftプロトコル
分散環境でデータの一貫性と可用性を保つために、TiKVはRaft合意プロトコルを利用します。
- RegionとRaftグループ: データはRegionという単位(デフォルト96MB)で分割して管理されます。各Regionは複数のレプリカ(デフォルトは3つ)を持ち、このレプリカの集合が1つのRaftグループを形成します。
- Multi-Raft: TiKVクラスタ全体では、多数のRegionが存在するため、多数のRaftグループが同時に動作することになります。これをMulti-Raftと呼びます。
-
書き込みの流れ: データの書き込みは、必ずRaftグループのLeaderレプリカに対して行われます。
- Propose: Leaderはクライアントからの書き込み要求を受け取ります。
- Append/Replicate: Leaderはその要求をRaftログとして自身のログ(RocksDB Raftインスタンス)に追記し、同時に他のFollowerレプリカにそのログを複製します。
- Commit: 過半数のレプリカ(Leader自身を含む)がログの永続化に成功すると、そのログは「コミット済み」と見なされます。
- Apply: Leaderはコミット済みのログを解析し、その内容を自身の状態(RocksDB KVインスタンス)に適用します。この時点で書き込みが完了し、クライアントに成功が返されます。
計算を高速化するCoprocessor
COUNT(*)
のような集計処理やWHERE
句によるフィルタリングをTiDB Server側で行う場合、TiKVから大量のデータをネットワーク経由で転送する必要があります。これを避けるため、TiKVにはCoprocessorという計算エンジンが内蔵されています。
TiDBのオプティマイザは、SQLの実行計画を作成する際に、一部の処理(集計、フィルタリング、LIMITなど)をTiKVにプッシュダウンするよう計画します。TiKVのCoprocessorが各ノードで分散して計算を行い、その結果だけをTiDB Serverに返すことで、ネットワーク転送量を大幅に削減し、TiDB ServerのCPU負荷を軽減します。
3. PD (Placement Driver) Server (メタデータレイヤー)
PDはTiDBクラスタ全体の「頭脳」であり、司令塔の役割を果たします。
- メタデータストレージ: どのデータ(Key)がどのRegionにあり、そのRegionのLeaderがどのTiKVノードにあるか、といった情報を一元管理します。TiDB ServerはPDに問い合わせることで、目的のデータがどこにあるかを知ることができます。
- TSO (Timestamp Oracle) の生成: TiDBクラスタ内でグローバルに一意かつ単調増加するタイムスタンプ(TSO)を発行します。このTSOがトランザクションの開始・コミット時刻となり、MVCCの基盤となります。
- スケジューリング: 各TiKVノードの状態(ディスク使用量、負荷状況など)をハートビートで収集し、クラスタ全体のバランスを取るようにRegionを自動で移動させます。例えば、特定のノードに負荷が集中している場合、そのノードのRegionを他のノードに移動させてホットスポットを解消します。
4. TiFlash Server (列ストアレイヤー)
TiFlashはTiDBのHTAPアーキテクチャを完成させるための重要なコンポーネントです。
- カラムナストレージ: TiKVが行ストアであるのに対し、TiFlashは列指向ストアです。列指向ストアは、分析クエリのように特定の列だけを大量にスキャンする場合に非常に高いパフォーマンスを発揮します。
- リアルタイムレプリケーション: TiFlashはRaft LearnerとしてTiKVのRaftグループに参加します。これにより、TiKVのトランザクション処理に影響を与えることなく、データを非同期でリアルタイムにTiFlashへ複製できます。
- ワークロード分離: TiKVがOLTP、TiFlashがOLAPのワークロードをそれぞれ担当することで、トランザクション処理と分析処理が互いに干渉しなくなります。
- スマート選択: TiDBのオプティマイザは、クエリのコストを判断し、TiKVの行ストアから読むべきか、TiFlashの列ストアから読むべきかを自動で選択します。
- MPP (Massively Parallel Processing): 複数のTiFlashノードが連携して並列で計算処理を行うアーキテクチャをサポートしており、大規模なJOINや集計をさらに高速化します。
まとめ
今回のワークショップを通じて、TiDBが単なるMySQL互換の分散データベースではなく、トランザクションと分析の両方の要求に応えるために、各コンポーネントが緻密に連携し合う、非常によく設計されたシステムであることを深く理解できました。
- TiDB Server が賢くSQLを処理し、
- PD がクラスタ全体を最適に管理し、
- TiKV がデータを安全かつ一貫性を保って保存し、
- TiFlash がリアルタイム分析を可能にする。
これらのコンポーネントが組み合わさることで、高いスケーラビリティ、可用性、そしてHTAPという強力な機能が実現されています。特に、読み書きをブロックしないMVCCの仕組みや、Raftプロトコルによるデータ一貫性の担保は、分散システムを扱う上で非常に重要なポイントだと再認識しました。
今後、大規模なデータを扱うシステムを設計する際には、TiDBが有力な選択肢の一つになることは間違いないでしょう。
Discussion