tom__bo’s Blog

MySQL!! MySQL!! @tom__bo

TiDBでPoint in Time Recovery

概要

この記事ではTiDBv6.1でのPoint In Time Recovery(PITR)について私なりの方法を紹介します。 運用中にめったに行うことはないですが、PITR (wikipedia)がいつでもできることは運用上重要ですよね。
しかし、残念なことに2022/07/06現在、TiDB公式ドキュメントにPITRの説明はないようです。まずはこのことを確認し、その後、TiDB Binlogを利用してユーザデータをPITRする方法を紹介します。 ユーザデータと書いたのはTiDB Binlogという更新差分を取得する仕組みがDCLによる変更内容を補足しないためです。つまり、CREATE USER文やGRANT文は厳密にPITRすることができません。 (DCLによって更新されるデータを"ACLデータ"、それ以外のデータを"ユーザデータ"とします)

以前の記事に引き続き、GCP上で動作確認までしたかったのですが、時間がなく断念しました。 手元の環境ではACLを除くユーザデータはここで紹介する手順でイレギュラーケースでなければ問題なくPITRができることを確認しました。 (*イレギュラーケース: pumpサーバ, reparoサーバが故障したり、各コンポーネントが超高負荷に陥って異常終了する場合。こういった異常時の挙動は検証できていません。)

公式で説明されている方法ではないので、手探りした内容です。もし、より良い方法を知っているかたがいたら教えて下さいm(. .)m、その時はこの記事も修正します。

TiDBでPITRする方法がない?

この記事を書いている2022/07/06現在TiDBで公式で説明されているPITRの方法はないようです。

TiDBでのPITRについてggってみると以下のような結果がヒットします。

もしPITRに進展があれば上記のGithub Issueで確認できるかもしれません。

TiDB Binlogを利用してPITRを頑張る

TiDBにはTiDB server, Placement Driver(PD), TiKVからなる最小コンポーネント構成に対する更新を別のTiDBクラスタMySQL, MariaDB, Kafka streamにレプリケーションするTiDB Binlogというクラスターを追加することができます。

TiDB Binlog チュートリアルの図

上の図のように、TiDB BinlogはTiDB serverで発生した更新を取得し、適用先のプロトコルに変換して適用するコンポーネントです。 この出力先にはdb-typeを"mysql"とすることで上述した適用先を指定しますが、"file"(ファイル出力)を指定することもできます。これによってgRPC形式のファイル出力を得ることができます。 この出力ファイルはさらにreparoコマンドを使って人間が読める出力に変換したり(MySQLでいうmysqlbinlogコマンド相当)、再度MySQLに変更を適用することができます。 これらを利用して、バックアップ時のTSO(TimeStamp Oracle, いわゆるトランザクションID)を自分で管理すればPITRができそうです。
困ったことに前回紹介したBRコマンドのバックアップや、TiDB Binlogはユーザアカウントを管理する"mysql", "INFORMATION_SCHEMA", "PERFORMANCE_SCHEMA"を無視するので、これらも自前で考慮する必要があります。

TiDB Binlogを追加したTiDBの前提構成

TiDB Binlogは、TiDB serverから変更分を取得するPump(ポンプ)サーバとそれらを集約し、時系列に並べ直すDrainerで構成されています。 この構成は、TiUPを利用してsimple-tidb-binlogトポロジーcomplex-tidb-binlogトポロジーのサンプルを利用して構築することが可能です。

これらTiDB Binlogを追加した以下のようにな構成を前提とします。

この構成ではTiDBに対する更新差分(DML,DDL)がTiDB Binlogのクラスターに蓄積されている状態になります。

TiDB Binlogを利用したユーザデータのPITR手順

ここからはTiDB Binlogを利用してユーザデータをPITRする方法を説明します。

前述したようTiDB Binlogでは特定のスキーマ(mysql, INFORMATION_SCHEMA, PERFORMANCE_SCHEMA)に対する変更は無視されるので、これらのスキーマに対する更新がある場合は別途記録するか、TiDBのStale Read(いわゆるHistorical Read)を利用するなどして定期的に保全する必要があります。

DCLの更新差分が取得できないとはいえ、バックアップ時のスナップショットとしては同一のものを取得したいため、dumplingというデータダンプとHistorical Readの機能を利用して必要なスキーマ、テーブルを取得します。 注意点として、ユーザデータとACLデータはバックアップにかかる時間に大きく差が開く可能性があります。ユーザデータは数TBを超えている可能性がありますが、ACL情報はMB単位になることも稀だからです。そのため、ユーザデータを取得してからACLデータのバックアップをしようとすると、Historical Readできる時間範囲を超えてしまう可能性があります。(この時間範囲はGCに関連した設定で調整可能です。) なので、まずバックアップを取得するスナップショット時刻となるTSOを取得し、そのスナップショット時刻のデータをユーザデータ・ACLデータで揃えてバックアップします。

これらを踏まえたバックアップからリカバリまでの流れを示すと以下のようになります。 青色の矢印がバックアップの流れ、オレンジ色がリカバリの流れになります。 バックアップ先には複数の選択肢がありますが、(S3互換の)オブジェクトストレージとしています。

バックアップ手順

  1. TSOの取得、このTSOより前に開始されたトランザクションがないか確認。
  2. ACLデータをバックアップ
  3. ユーザデータをバックアップ

(1)TSOの取得

show master statusコマンドを実行することで現在のTSOを取得できます。 このPositionに出力されるTSOをバックアップ時刻(バックアップTSO)とします。

mysql> show master status;
+-------------+--------------------+--------------+------------------+-------------------+
| File        | Position           | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+-------------+--------------------+--------------+------------------+-------------------+
| tidb-binlog | 434402433292042241 |              |                  |                   |
+-------------+--------------------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

このTSOよりも前に開始されたトランザクションがバックアップ中やバックアップ後にCOMMITされると困るので、この時刻よりも前に実行されたトランザクションがないかを確認します。 INFORMATION_SCHEMAスキーマのTIDB_TRXテーブルを参照することで確認できます。

SELECT * FROM INFORMATION_SCHEMA.TIDB_TRX WHERE ID <= {バックアップTSO};
... (省略)

このクエリの結果がある場合は、スリープしてリトライしたり、バックアップ失敗とするなど適宜ハンドリングします。

(2) ACLデータをバックアップ

これにはPingCAP社が提供するデータダンプツールのdumplingを利用します。 このオプションに--snapshotがあり、これにTSOや時刻を指定することで、Historical Readした結果をダンプすることができます。 また、ここで必要なのはACLに関連するデータだけなので、対象テーブルは以下の6つになります。 (Todo: このテーブルだけで正しいか確認が必要)

例えば以下のようにバックアップします。

tiup dumpling \
  -u backup_user \
  -p backup_user \
  -P 4000 \
  -h {host} \
  --filetype sql \
  -o /tmp/backup-test \
  --filter mysql.user \
  --snapshot 434402482942115841

(3) ユーザデータをバックアップ

同様にバックアップTSOを指定して、ユーザデータをバックアップします。 これは前回の記事で紹介したようにtiup brコマンドを利用します。 スナップショットを指定するオプションは--backuptsです。

tiup br backup full \
    --pd "{pd-hostname}:2379" \
    --storage "s3://tidb-test/data" \
    --s3.endpoint "hoge.com" \
    --s3.region "us-east-1" \
    --log-file backupfull.log \
    --backupts 434402482942115841

これで最初に取得したバックアップTSOで時刻を揃えたバックアップができました。

リカバリと差分適用

バックアップされたデータのリストは単純です。 差分データの適用はreparoといういわゆるmysqlbinlog相当のコマンドを利用する必要があります。(後述)

  1. ユーザデータのリストア
  2. ACLデータのリストア
  3. 差分データの適用

(1) ユーザデータのリストア

前回の記事で紹介したようにリストアします。特に特筆するべきことはありません。

tiup br restore full \
    --pd "{pd-hostname}:2379" \
    --storage "s3://tidb-test/data" \
    --s3.endpoint "hoge.com" \
    --s3.region "us-east-1" \
    --log-file restoretable.log

(2) ACLデータのリストア

dumplingで取得したACL関連のテーブルをrestoreします。 バックアップ時に例示したように--filetype sqlを指定していれば、sqlファイルとなっているので、実行すれば良いです。 このときrootユーザなどはリストア先のTiDBクラスタと重複するため、フィルタ処理が必要です。

mysql -h {new-tidb-host} ... < acl_backup.sql

(3) 差分データの適用

更新差分のデータはTiDB BinlogのアーキテクチャではPumpサーバたちを経由してDrainerサーバに時系列で蓄積されています。ファイル名やドキュメント上での説明ではbinlogということになっていますが、MySQLと同じ形式のbinlogではなく、TiDB独自のgRPCのダンプファイルになっています。 これを復元して人間が読めるような形にしたり、他のTiDBもしくはMySQLに適用するためにはreparoというコマンドを使います。

reparoコマンドは現時点ではtiupに同梱されていないため、TiDB toolsを取得してインストールします。 このreparoコマンドを利用した操作はbinlogを保持しているDrainerサーバで実行します。

まずは復旧時点のTSOを把握するために、reparoのコンフィグファイルのdest-typeを"print"として復旧対象の時刻を探します。この復旧対象の時刻をリカバリTSOと呼びます。 "dest-type = print"を指定することで更新差分とその実行時刻やTSOといったメタデータが人間が読める形式で出力されます。ここから復旧対象の直前のトランザクションとそのTSOを見つけ、リカバリTSOとします。 リカバリTSOが見つかったら、バックアップTSOとリカバリTSOを同コンフィルのstart-tsostop-tsoに指定してreparoコマンドを実行します。

reparo -config reparo.yaml

reparoの実行に関してはだいぶコンフィグファイルや実行方法を省略してしまいましたが、設定可能なオプションについてはreparoのドキュメントを参照してください。

これでユーザデータのPITRが完了します。   あとはバックアップ後のDCLを復元できればTiDB全体の復旧ができます。

想定される課題

いくつかこのPITR方式の懸念点を列挙しておきます。 これらをどう解決するかはまだ検討中ですので、間違いやもっと良い方法があれば是非コメントいただきたいです。

  • DCLの完全なPITRができない可能性がある
    • 短い間隔でこの部分のバックアップを取ったり、DCL実行時に別所に保存する必要がある
  • Pumpが故障した場合に"Binlog"が欠損する可能性がある
  • Drainerは冗長化ができない様子
  • TiDB Binlogの導入がTiDB全体のパフォーマンスに影響する(要検証)

終わりに

TiDBの開発速度はとても早く、コンポーネントや管理ツールも豊富ですが、それを安定運用するためのベストプラクティスは導入するバージョンや必要な要件によって自分で手探りする必要があります。 そこがエンジニアとして面白い部分ではありますが、データの欠損は重大な問題になりかねないので、どんどん情報を集めていきたいです。 なにかご意見や質問があればこのブログのコメントやtwitterなどに是非お願いします。