このプロジェクト名は「チタン(Titanium、Ti)」に由来しています。開発者は元素の特性を意識し、「KV」は主に使われるキーと値を指します。発音は「タイ・ケイ・ヴィー」です。
製品概要
TiKVは拡張性、極めて低いレイテンシー、そしてシンプルさを重視して設計された分散型キー・バリュー・データベースです。他の類似ソリューションと異なり、既存の分散ファイルシステムへの依存が少ない点が特徴です。また、Google SpannerやHBase、F1と同様の機能を実現することを目指しています。
上のイメージはこのソリューションの主要なコンポーネントを示しています。ここでは、それぞれを簡単にご説明します。
前のセクションではTiKVのコンポーネントを取り上げました。これらのコンポーネントは互いに通信しながら動作し、その中でProtocol Buffer Protocolが使われます。ただしRustはgRPCとの互換性がないため、TiKVでは独自にプロトコル形式を定義しています。
それぞれの通信は1つまたは複数のメッセージを交換することで行われ、メッセージ形式はHeader + Payloadです。
ここで、PayloadにはProtocol Bufferのデータが含まれており、ヘッダに指定された長さを持ちます。そのため最終的なメッセージやペイロードはヘッダ情報をもとに読み取り、適切にデコードされます。
Headerは16バイトのシーケンスで、以下の形式です: | 0xdaf4(2 bytes magic value) | 0x01(version 2 bytes) | msg_len(4 bytes) | msg_id(8 bytes) |
kvprotoというプロジェクトにはTiKVの通信を行うためのプロトコルが実装されています。もう一つのプロジェクトtipBにはプッシュダウンのアルゴリズムが含まれています。
TiKVアーキテクチャをより深く知りたい場合は、kvprotoの下記ファイルが参考になります:
貴社の外部プロジェクトでTiKV APIを活用したい場合、次のファイルを使用してください:
raft_cmdpb.proto: 基本的なKey-Value機能のみ必要な場合に利用します。
Raftは多くのシステムで採用されるアルゴリズムで、高いパフォーマンスやフォールトトレランスを得るのに役立ちます。TiKVではこの合意アルゴリズムを用いて分散システムでの一貫性を高められるようにしています。貴社のプロジェクトで必要に応じて利用できます。
Raftが選ばれた理由は、実装が比較的容易で、実運用にも向き、高い実用性と移行のしやすさを備えているからです。TiKVでのRaftは、etcdから完全に移植されています。
TiKVプロジェクトでRaftを使う方法を知りたいですか?次のステップをご参照ください。
ストレージの詳細情報(HardStateとConfStateなど)は、この関数で返されます。
fn initial_state(&self) -> Result<RaftState>;
このステップでは、lowからhighの範囲内にあるログエントリを返します。
fn entries(&self, low: u64, high: u64, max_size: u64) -> Result<Vec<Entry>>;
指定したログインデックスに対応するログエントリのtermを取得します。
fn term(&self, idx: u64) -> Result<u64>;
// 4. 現在位置における最初のログエントリのインデックスを取得
fn first_index(&self) -> Result<u64>;
// 5. 現在位置における最後のログエントリのインデックスを取得
fn last_index(&self) -> Result<u64>;
// 6. 現在のスナップショットを生成し、返します
fn snapshot(&self) -> Result<Snapshot>;
Step 1: ここでは、Raftのストレージ特性を定義し、デプロイします。
Step 2: 初期状態に関する設定(構成やハードウェア/ストレージ)を渡すために、RawNodeオブジェクトを作成します。
configデータでは、election_tickやheartbeat_tickなど定期的に行われるtickが重要です。リーダーはハートビートの頻度を設定し、heartbeat_tickの値がこのしきい値に達すると、フォロワーにハートビートを送信し、経過時間をリセットします。
フォロワー側では、election_tickによって選挙の経過が監視され、しきい値に達すると選挙(合意形成)が開始されます。
Step 3: RawNodeが生成された後は、たとえば100msごとなどのtick間隔で、このノードのtickインターフェイスが繰り返し呼ばれます。
Step 4: TiKVのRaftでデータを書き込みたい場合、Proposeインターフェイスを呼び出します。このとき任意のバイナリ形式データをパラメータとして渡すと、それが複製されます。データの取り扱いは外部ロジックに委ねられます。
Step 5: メンバーシップを変更する必要がある場合は、propose_conf_changeインターフェイスを使います。RawNodeからConfChangeオブジェクトを送ることで、特定ノードの追加や削除などを行えます。
Step 6: TickやProposeのような関数が呼び出されると、Raftは該当するRawNodeの状態をReadyにします。これには3つの意味のいずれか、または複数が含まれます:
Raftはこれらの状態を処理し終わると、Advance関数を使って次のノードまたはプロセスにReady状態を通知します。
TiKVではMioというRustライブラリを使ってRaftを利用しています。手順は以下のとおりです:
これらのステップはすべてのRaftグループに対して行われます。それぞれのグループは独立して動作し、特定のRegionに紐づきます。
TiKVの起動直後は、(-inf, +inf)という範囲を持つ1つのRegionだけが存在します。最初のSplitはデータが64MBのしきい値に達したときに行われます。その後もSplit Keyに基づいてリピートされ、必要に応じて新たなRegionが作られます。
TiKVのロードマップにはMergeプロセスもありますが、まだ実装が進行中です。将来的には、データ量が少ない隣接するRegionをまとめてより大きいRegionとする仕組みが考えられています。
先に述べたように、PDはTiKVクラスタの中枢となる存在です。クラスタの高い一貫性と可用性を確保する責任を担っています。
PDは制御の中心点であるため、単一障害点になりうる可能性があります。これを避けるため、複数のPDサーバーを起動し、その中から1つをリーダーとして選出します。リーダー選出にはetcdの選挙メカニズムが使われ、公平に進行します。
選出されたリーダーは外部との通信を担当し、サービスを提供します。障害などで応答しなくなったときは、同じ選挙プロセスにより別のリーダーが選ばれます。
リーダーがダウンして別のリーダーに切り替わる場合、その新リーダーが最新のデータを持ち、一貫性を保てるかどうかが問題です。
この対策として、PDのデータはetcdに保存されます。etcdは分散型のKVストアなので、データの一貫性を保証できます。新しいPD(リーダー)はetcdにあるデータを取得して動作します。
以前のバージョンでは外部のetcdサービスが必要でしたが、現在のバージョンではPDがetcdに組み込まれており、高速かつシンプルでパフォーマンスも高くなっています。
現在のPlacement Driverの主な役割は以下のとおりです:
TiKVのトランザクション機能は、GoogleのPercolatorやXiaomi - Themisに着想を得ています。しかし、TiKVのモデルは若干異なり、最適化を加えています。主な違いは以下のとおりです:
TiKVでのトランザクションが動作する大まかな流れは、以下のとおりです:
この概念はHBaseで使われている仕組みと似ていますが、TiKVのコプロセッサは動的ロードではなく静的に処理されます。主な役割はTiDB TiKVをサポートする場面で、Splitやプッシュダウンのシナリオに効果を発揮します。以下に具体的な例を示します:
手順はざっくり以下のようになります:
TiKVがGETやPUTリクエストをどのように処理しているか、あるいはレプリカをどう更新するかなどを簡単に見ていきます。
TiKVには、シンプルな操作、プッシュダウン付き、トランザクション型の3種類が存在します。リクエストの性質によって使い分けますが、現在は最終的にすべて単純なKV操作として扱われています。
たとえばPUT操作(k1, v1)が行われる場合の流れは以下のとおりです:
GETリクエストの場合も同様のフローをたどります。つまり、Raftノードの大多数で複製されることで、分散システムとしてデータのリニアライズ性を保ちます。
TiKVのRaftではフォロワーが読み取りサービスを提供する仕組みや、リーダーがleaseを活用してRaftログを介さずに読み取りを行う提案もあります。これらの提案はTiKVのパフォーマンスを最適化する目的があります。
TiKVのStoreは、データのサイバーセキュリティを高めるために複数の複製(レプリカ)を用意しています。これらのレプリカはPeerとして扱われ、Region内に配置されます。もしRegionに必要な数のレプリカが不足していれば新たに追加し、多すぎる場合は削除します。
このレプリカの増減を扱うのがTiKVのRaftメンバーシップ変更機能で、タイミングや変更内容はPDによって制御されます。
イメージとしては、PDがマネージャー役で、Membership Changeが実際に働くアクターという関係です。
具体例で流れを見てみましょう:
注意: この時点ではRegionのメタ情報にレプリカが追加されたことが反映されただけです。もしリーダーが新しいフォロワーにデータがないと判断した場合は、そのフォロワーへスナップショットを送ります。
注意2: Raftの論文では、TiKVやetcdのMembership Changeの実装方法とは少し異なる手順が書かれています。そこではPropose時点でRegionのメタにPeerが追加されますが、TiKVやetcdではログ適用時にPeerが追加される形をとっています。
TiKVは信頼性の高い分散データベースソリューションで、ロードマップを見ても今後さらに機能拡充が見込めます。現時点でもCNCFで成熟度が十分と判断されており、商用プロジェクトに利用可能です。
本記事で概要を把握できたと思います。アーキテクチャや仕組みが理にかなっていてパフォーマンスも期待できると感じたなら、次のプロジェクトに導入する選択肢として検討できます。たとえばTiKVとKubernetesを組み合わせると、新しいアイデアを実現できる可能性があります。
最新情報を購読