こんなのってあり?TiDB+TiFlashをMySQLの分析クエリオフローダーとして使ってみる

DBを運用していて、「データが増えてきて特定の集計クエリが遅い」という悩み、よくありますよね。 インデックスを足しても限界がある、かといってDWHを新設するほどの予算も工数もない。そんなときの選択肢の一つとして、TiDBの列指向ストレージエンジン TiFlash を「既存MySQLの分析クエリオフローダー」として使う構成を検証してみました。

TiDBは通常、行ベースKVSの TiKV をストレージエンジンとして使いますが、TiKVのデータをレプリケーションして列指向で保持する TiFlash を追加することで、OLAP(集計)クエリを高速化できます。

今回試すのは、既存のMySQLには一切手を入れず、その横にTiDB(TiKV + TiFlash)を立てて、変更データだけを継続的に流し込み、分析クエリだけをTiDB側で受ける、という構成です。将来的にTiDB本体への移行を検討する際のPoC環境としても、最小構成のまま流用できるのがメリットです。

目次

前提とする環境

  • OS: Oracle Linux 9.8
  • 構築ツール: TiUP
  • TiDBバージョン: v8.5.7
  • 構成: TiDB / TiKV / TiFlash / DMを同一ホストに同居させた最小構成からスタートするのがおすすめです。 導入コストと検証スピードを優先した構成であり、リソース競合やSPOFといった制約は残るため、本格運用に進む段階では後述の「将来のスケールアウト」でノード分離を行う前提です。
  • ソースDB: 既存運用中のMySQL 8.0(binlog_format=ROW、GTID有効を前提)
  • 想定ソース台数: 2台のMySQLインスタンスからのFan-in(テーブル統合)を想定

環境構築

まずはTiUPで最小構成クラスタを立てます。将来のスケールアウト(ノード分離)をそのまま延長線上で検証できるよう、最初から tiup cluster でトポロジーを定義して構築します。

topology.yaml で、PD/TiKV/TiDB/TiFlashを同一ホスト(ポートを分けて同居)に定義します。

tiup cluster deploy コマンドリファレンス)

DM(Data Migration)とは:MySQL互換DBからTiDBへのデータ同期を専門に担うPingCAP製のツールです。フルダンプ/ロードによる初期移行と、binlogベースの増分同期をひとつのタスクで連続実行できるのが特徴で、今回のように複数MySQLのテーブルを1つのTiDBテーブルへ集約する「Fan-in」もルール定義だけで実現できます(複数MySQLインスタンスの統合について)。設定はYAMLで「ソース(接続先MySQL)」と「タスク(同期対象・ルーティングルール)」を分けて定義 する方式で、sourceをDMクラスタに登録した上で、source-idを指定したtaskを起動する2段構成になっています(DMのアーキテクチャ)。DM自体もTiUPでmaster/workerからなる別クラスタとしてデプロイします。

検証: クラスタが正しく起動しているか

tidb_version() が返り、TiFlashノードがPDに登録されていれば起動は成功です(information_schema.tiflash_replicaの参照方法は公式ドキュメント参照)。

DMでのFan-inレプリケーション

今回は、既存のMySQL(複数台)を想定し、特定テーブルをTiDBに集約するレプリケーション(Fan-inレプリケーション)を設定します(設定ファイルの詳細はMigrate Data Using Data Migrationを参照)。 以下のYAMLのように、ソースDBへの接続定義を行います。 データソースごとに、個別の設定ファイルを作成する必要がある点に注意してください。 今回は、source1.yamlとsource2.yamlを作成しました。

tiup dmctl operate-source コマンドを使って、ソースDBをDMに認識させます。

tiup dmctl operate-source show コマンドを使ってソースが登録された事を確認できます。

次に、各ソースからのレプリケーションタスクを定義します。 今回は、複数ソースからのレプリケーションのため、レプリカとなるTiDBのテーブルをanalytics.ordersにまとめています。 route-rules を複数定義して別々のテーブルとしてマッピングすることももちろん可能です。 詳細はKey Features – Table Routingを参照ください。

このように複数の上流テーブルを1つの下流テーブルへ集約する場合は、 タスク設定のshard-modepessimisticまたはoptimisticを明示します (Merge and Migrate Data from Sharded Tables)。 両者の違いが表れるのは、主に上流の各shardでDDLを実行するときです。

モード DDL実行時の動作 上流DDLの条件 注意点
pessimistic DDLを実行したshardのDML同期を一時停止し、shard group全体で同じDDLが揃ってから下流へ適用 各shardでDDLの順序と内容を揃える DDL待ちで同期が止まる可能性はあるが、誤ったDDLを下流へ適用する前に検出しやすい
optimistic 各shardのDDLを互換性のある形へ調整して直ちに下流へ適用し、DML同期を止めない 実行順やDDL文が異なってもよいが、shard間でスキーマ互換性を維持する 不適切なDDLが先に下流へ適用され、同期停止やデータ不整合につながる可能性がある

DMの既定はpessimisticですが、設定の意図を明確にするため、このPoCでも shard-mode: pessimisticを明示しています。2つのMySQLでordersのスキーマを 同一に保ち、DDLも同じ順序・内容で実行する前提です。DDL中もDML同期を止めたく ないという理由だけでoptimisticを選ぶのではなく、互換性の制約と障害時の リカバリー方法を理解したうえで選択する必要があります。

タスクを開始します。

検証: 同期状態の確認

tiup dmctl query-status コマンドでレプリケーションタスクの状態を確認できます( SHOW REPLCIA STATUS 相当)

データ量は?

TiFlashはTiKVのデータをレプリケーションして保持するため(TiFlashのレプリケーション方式:Raft Learnerとして低コストにリアルタイム複製)、論理的には同じデータをTiKV/TiFlash双方で二重に持つことになります。ただし、TiFlashはRocksDBベースの列指向フォーマットで圧縮して保持するため、単純に元データの2倍にはなりません。

検証: ストレージ使用量の比較

各MySQLに100万行ずつ、合計200万行の比較用データを用意しました。1行あたりの payloadは平均655 bytesで、主キーに加えて (customer_id, event_type, created_at) の複合インデックスを持たせています。

MySQLは2台の.ibd実割当サイズを合算しました。TiKVは TABLE_STORAGE_STATS.TABLE_SIZE、TiFlashは TIFLASH_TABLES.TOTAL_STABLE_SIZE_ON_DISKを使用しています。TiKVについては、 ロード直後に生成されるWALやRaft logを除外したデータ(リージョンのみ)のサイズを対象としています。

実測結果:

対象 計測方法 サイズ MySQL比
MySQL 2台合計 .ibd実割当サイズ 1,656.0 MiB 1.000
MySQL 2台合計(参考) DATA_LENGTH + INDEX_LENGTH 1,623.3 MiB 0.980
TiKV TABLE_STORAGE_STATS.TABLE_SIZE 1,775.0 MiB 1.072
TiFlash TOTAL_STABLE_SIZE_ON_DISK 301.8 MiB 0.182
TiKV + TiFlash 上記2値の合計 2,076.8 MiB 1.254

今回のデータでは、TiKVとTiFlashを合わせた保持量はMySQL 2台分の約1.25倍に収まりました。 TiFlash単体はMySQL比約18.2%で、TiFlash内部の非圧縮サイズ1,456,500,000 bytesに対し、実ディスクサイズは316,442,178 bytes、圧縮率は約4.60:1でした。

この結果は、VARCHAR(1024) の列が主なデータサイズの要因である表を使った場合の値です。 列指向圧縮が効きやすいデータであり、型・値の偏り・カーディナリティによって結果は変わります。

クエリは高速化された?

まず、集計対象のテーブルにTiFlash replicaを作成します。今回のTiFlashは1台 なので、レプリカ数には1を指定しました。

このDDLはTiFlashへのデータ複製完了を待たずに戻ります。ベンチマークを始める前に、 AVAILABLE=1かつPROGRESS=1になったことを確認します。

TiFlashにクエリを向けるには、オプティマイザヒントかセッション変数で読み出しエンジンを指定します(system variable: tidb_isolation_read_enginesEXPLAIN ANALYZE)。

検証: MySQLとTiFlashの集計性能比較

ストレージ比較後の同期ラグ検証でデータを追加したため、今回は MySQL 1の1,500,000行とMySQL 2の1,500,000行、合計3,000,000行を 使用しました。TiDB側のanalytics.benchmark_eventsにも、DMでFan-inした 同じ3,000,000行が存在します。

表はcustomer_idevent_typeamountcreated_atなどの複数列と、 JSONに似たテキストを格納したpayload VARCHAR(1024)列で構成されています。 各MySQLとTiDBで先にANALYZE TABLEを実行し、次の3種類を比較しました。

クエリ 読み取る主な列 処理内容
顧客別売上Top 20 customer_id, amount 10万顧客でCOUNT/SUMし、売上順にTop 20
イベント・payload集計 event_type, amount, payload 8種別で集計し、payload文字数の平均も算出
日別イベント集計 created_at, event_type, amount 日付とイベント種別でCOUNT/SUM

今回のMySQLは2台にデータが分かれているため、1台だけを測るとTiFlash側の 半分のデータしか処理せず、結果も一致しません。そこでMySQL側は2台へ同時に 部分集計を実行し、取得した結果をクライアントでマージして最終結果を作りました。 TiFlash側は、Fan-in済みのテーブルへ1回だけクエリを実行します。

顧客別売上Top 20の場合、MySQL 2台にはそれぞれ次の部分集計を実行します。

各MySQLから返る顧客別の部分集計を合算し、売上順に並べてTop 20を選びます。 TiFlash側では同じ処理をSQL内で完結させます。

MySQL側の時間は、2台への並列クエリ開始から、結果受信、部分集計のマージ、 並び替えが完了するまでのエンドツーエンド時間です。ウォームアップ後に 両方式を交互に5回ずつ実行し、中央値を採用しました。\n\nマージを行ったpythonコードはシンプルにsubprocessとしてmysqlクライアントを呼び出し、結果TSVを以下のループで合算しています。

実測結果:

クエリ MySQL 2台+マージ(5回の範囲/中央値) TiFlash(5回の範囲/中央値) 高速化 時間短縮
顧客別売上Top 20 9,604.8~10,005.3 ms/9,717.9 ms 150.2~207.2 ms/163.3 ms 59.51倍 98.3%
イベント・payload集計 8,111.2~8,676.1 ms/8,450.5 ms 574.7~598.4 ms/587.9 ms 14.37倍 93.0%
日別イベント集計 10,876.8~11,333.4 ms/11,087.6 ms 135.0~186.2 ms/173.2 ms 64.02倍 98.4%

このPoC構成では、3種類すべてでMySQL 2台から集計するよりもTiFlashへ オフロードした方が高速でした。特に、ソースをまたいだ大量の部分集計結果を アプリケーション側で統合せず、TiFlashのMPP内で処理を完結できる効果が 大きく出ています。

スケールアウト検証

検証: PD、TiKV、TiDB Serverのノード追加

最後に、このOLAP用環境を本格的に利用したくなった場合の事を想定し、スケールアウト検証をしました。

ここでは、単一VM構成からSPOFの排除のために PDを2台、TiKVを2台、TiDB Serverを1台 追加しました。\n今回はPD/TiKVを同居させていますが、これは推奨ではなくできれば分離させたほうが望ましい点はご注意ください。\nまた、DMはそのままTiDBに残しています。

VM IP vCPU / メモリ 追加コンポーネント
scale-pd-tikv2 192.168.56.13 2 / 4 GiB PD、TiKV
scale-pd-tikv3 192.168.56.14 2 / 4 GiB PD、TiKV
scale-tidb2 192.168.56.15 4 / 8 GiB TiDB Server

tiup cluster scale-outへ渡したトポロジーは次のとおりです (tiup cluster scale-out)。

以下のコマンドでスケールアウトを実行します。

PDのmax-replicas=3に対し、確認時の通常TiKV Region 22個が3台すべてへ 配置されました。今回はLeader数が .13のTiKVに偏りましたが、概ね按分されたと見てよいでしょう。

DMLの継続性も確認するため、連続INSERTを流しながらスケールアウトを実行しました。

書き込みは約100ms間隔で次の3系列へ流しました。

  • MySQL source 1のapp_db.orders(DMでTiDBへ同期)
  • MySQL source 2のapp_db.orders(DMでTiDBへ同期)
  • 192.168.56.10:4000の検証用TiDBテーブルへの直接INSERT

ワークロードは173.2秒継続し、その開始3秒後から160.272秒かけて スケールアウトしました。完了後も10秒間書き込みを継続しています。

書き込み先 成功 SQLエラー 平均 p99 最大
MySQL source 1 1,150 0 46.0ms 58ms 125ms
MySQL source 2 1,151 0 45.9ms 59ms 74ms
TiDB .10:4000へ直接 1,255 0 33.3ms 38ms 12,874ms

SQLエラーやデータ欠落は観測されず、クラスタ全体を停止せずに 拡張できました。一方、TiDB直接INSERTのうち1件は完了まで12.874秒かかっています。 今回の環境の想定ではまだ初期導入段階と考えられるため、これが大きな問題になるケースは少ないと考えますが、できるだけワークロードが少ない時間帯を選んで実施することを推奨します。

以上で想定通りSPOFが排除できました。追加したTiDBを活用する場合は上位のLBで改めてルーティングする必要がありますのでご注意ください。

TiDBには、TiProxyという製品に特化したロードバランサがありますので、これをデプロイすると良いでしょう。

まとめ

  • 既存MySQLに手を入れず、DM経由でTiDB(TiKV + TiFlash)に変更データを集約し、分析クエリだけをオフロードする構成はPoCとして始めやすいかと思います。
  • 300万行の集計3種類では、TiFlashへのオフロードはMySQL 2台からのFan-in相当集計より約14.37~64.02倍高速でした。これはあくまで検証用サーバでの結果になりますので本番相当のスペックで確認をおすすめします。
  • PD 3台、TiKV 3台、TiDB Server 2台、へオンラインで拡張できました。
  • TiDB Server間の透過的な切り替えにはロードバランサーまたはVIPが必要です
  • この提案を始める際は、まず単一ホストの最小構成でスタートするのがおすすめです。本格運用に進む段階でノード分離・Placement Rulesでの配置設計を行うとよいでしょう。

Let’s Enjoy TiDB!

スマートスタイルTECHブログについて

スマートスタイルTECHブログでは、日頃MySQLのサポート業務に従事している有資格者で構成された技術サポートチームがMySQLに関する技術情報を発信しています。データベースのお困りごとはお気軽にご相談下さい。

よかったらシェアしてね!
  • URLをコピーしました!
目次