👻

TiDB Serverless : HTAP と TiFlash

2024/07/17に公開

過去何度か記事で触れたようにTiDBはオンライン向けOLTPトランザクションと分析向けOLAP両方をサポートするHTAPデータベースとして開発が継続されています。

地理的に離れた場所に、適材適所でOLTP型とOLAP型データベースを構築するアーキテクチャが
一般的だとおもいますが、TiDBがHTAPへの道を切り開き多くのデータベースが進化を
繰り広げています。
ビッグデータ解析のアーキテクチャもこれによりスタンダードがわかっていくかもしれません。

これはどのように実現しているのでしょうか。まずそれに触れる前に、列指向型の技術的特徴に触れます。

列指向型データ

話をシンプルにするために一旦データベースを忘れて単純なCSVファイルを想定します。
通常のCSVファイルは行指向型です。例えば以下のようなシンプルなデータを想定します。

3人の年齢の平均値を計算する場合、行指向型ではデータは1行ごとに読み込まれますから、以下のようにデータが読み込まれることになります。

1,Bob,20,2,Alice,42,3,Eve,37

この後(20+42+37)/3が計算されるわけです。つまり必要のないデータを6個読み込んでしまっているわけです。これをParquet等の列指向型にすると読み込まれるデータは以下です。

20,42,37

読み込むデータ量が少なくなるため、当然処理が高速化します。

保存されるデータはクラウドストレージの利用量などを節約するために圧縮されるケースが増えています。また昨今のクラウドを活用した分散ストレージアーキテクチャを採用したデータベースなどはデータのやり取りがネットワークを介して行われるため、圧縮化は必須技術になります。コンピュートパワーやストレージよりネットワークは、はるかに遅いためです。
この視点で先ほどのデータをもう一度見てみます。行指向の場合、

1,Bob,20,2,Alice,42,3,Eve,37

これは以下と言い換えることができます。

int,varchar,int,int,varchr,int,int,varcar,int

一方列指向型データフォーマットは以下です。

1,2,3,Bob,Alice,Eve,20,42,37
int,int,int,varchar,varchar,varchar,int,int,int

どちらの方が圧縮効率が高いかは言うまでもありません。このような理由から分析には一般的に列指向型が使われます。

TiDB と 列指向型ストレージ / TiFlash

TiDBのストレージは2種類あります。
TiKV : 行指向型ストレージ
TiFlash : 列指向型ストレージ
通常TiDB Clusterを起動するとそれがDedicated型であれ、Serverless型であれTiKVで起動されます。

この辺りは中の人のKoiPingさんの解説がわかりやすいです。
https://zenn.dev/koiping/articles/f4b6579f3e6a68

これに対して追加でTiFlashストレージをくっつけTiKVとレプリケーションを張ることが出来ます。
レプリケーションは非同期で行われユーザーは意識する必要がありません。また非同期ですからTiFlashのパフォーマンスはTiKVを用いて動作するOLTPアプリケーションには影響を与えません。

https://docs.pingcap.com/ja/tidb/stable/quick-start-with-htap
https://docs.pingcap.com/ja/tidb/stable/use-tidb-to-read-tiflash

さっそくやってみる

では集計系のデータでTiKVとTiFlashのパフォーマンスの違いを見ていきます。
まずは以下のとてもシンプルなテーブルを作成します。

use test;
CREATE TABLE OLAPTEST (
    ID int,
    Age int,
    PRIMARY KEY (ID)
);

次に1000件のダミーデータを入れます。

import random

with open('insert_olaptest.sql', 'w') as f:
    f.write("INSERT INTO OLAPTEST (ID, Age) VALUES\n")
    for i in range(1, 1001):
        age = random.randint(0, 99)  # 0から99の範囲でランダムに生成
        if i == 1000:
            f.write(f"({i}, {age});\n")
        else:
            f.write(f"({i}, {age}),\n")

実行して生成されたSQLファイルの中身をそのまま実行すれば1000件のデータが挿入されます。
Ageの平均値を取得すると以下になります。

select avg(Age) from test.OLAPTEST;


SQLの実行計画を出力すると以下のようになっており、TiKVが使われていることがわかります。

desc select avg(Age) from test.OLAPTEST;


ではここからTiFlash ストレージを作成してTiKVからデータを非同期レプリケーションさせます。

ALTER TABLE test.`OLAPTEST` SET TIFLASH REPLICA 2;

TiDB Serverlessの場合、作成可能なStorageは2で固定になります。
なお作成されたReplicaを削除する直接的なコマンドはなく、以下で削除が可能になります。

ALTER TABLE test.`OLAPTEST` SET TIFLASH REPLICA 0;

以下のコマンドでReplicaの作成状況を確認できます。

SELECT * FROM information_schema.tiflash_replica WHERE TABLE_NAME = 'OLAPTEST';


なおLOCATION_LABELSですが、ServerlessではないClusterでは意図的に異なるZoneやRegionを指定することでDR用TiFlashを作ることが可能のようです。
AVAILABLEProgress1は若干わかりずらいのですが、これは100%を意味しています
AVAILABLE1ということは2つのReplicaが準備されたという意味です。同様にProgress1はデータ同期が100%行われている、という意味です。

以下のSQLを実行します。

use test;
set @@session.tidb_isolation_read_engines = "tiflash";
select avg(Age) from OLAPTEST;

tikvtiflashの値を切り替えてパフォーマンスを見てください。今回はとてもシンプルなテーブルなのでそこまで大きい差は生まれませんが、ちゃんと差が出ていることがわかります。
実行計画を見ると対象がtiflashになっていることがわかります。

desc select avg(Age) from OLAPTEST;


なお今回は意図的にset @@session.tidb_isolation_read_engines = "tiflash";と利用するストレージを使用していますが、通常はオプティマイザが自動的にtikvtiflashを指定してくれるため、明示的な指定は不要です。
set @@session.tidb_isolation_read_engines = "tiflash";を用いた場合セッション単位で利用されるストレージが確定されるため、単一セッションのSQL単位で明示的に利用するストレージを切り替える場合、以下のようにSQLヒントでの指定も可能となっています。

select /*+ read_from_storage(tiflash[table_name]) */ ... from table_name;

もう少し詳しい情報を知りたい方はこちらを参考にしてください。
https://speakerdeck.com/pingcap0315/tiflashnoshao-jie

Discussion