pt-online-schema-change の内部挙動と --alter-foreign-keys-method の drop_swap フォールバック問題

https://www.percona.com/doc/percona-toolkit/3.0/pt-online-schema-change.html

pt-online-schema-change(以下:pt-osc)とは、MySQL においてオンラインでスキーマ変更を可能にするツールであり、 Percona Toolkit に同梱されているものの一つ。現在所属しているチームでは前から使われていて、時々 pt-osc の設定変更によるパフォーマンスチューニングや、online migration 時のエラー調査などで触ることが多かった。

昨年夏の話に遡る。当時、pt-osc を利用した Online Migration 実行時に、アプリケーションから Database へのリクエストが時々失敗し、どうやら謎のダウンタイムが存在しているようだという報告を Developer team から受けた。

その時の調査を担当したのだが、結論から言うと、Foreign Keys をもつテーブルの Migration を実行している際、一部のケースで、pt-osc の設計上ダウンタイムが発生せざるを得ない状況に陥っていたということを突き止めた。

このブログでは、その発生原因の詳細と、それを理解するための前提知識としての pt-osc の内部挙動について説明する。

Source Code

https://github.com/percona/percona-toolkit/blob/3.x/bin/pt-online-schema-change

How to achieve online schema change

pt-osc の基本戦略としては、一時的にコピー先の Table を作成し、Trigger を利用して最新の変更を取り入れつつ、コピー元の Table から --chunk-time/--chunk-size オプションによって静的または動的に算出された行数に基づいて徐々にコピーしていく、という挙動になる。発想としてはシンプルだ。

  1. create an empty copy of the table to alter
  2. create triggers on the original table
  3. copy rows from the original table to the new table by small chunks
  4. RENAME table
  5. drop the original table

How to handle Foreign Keys

問題が起こるのは、コピー元の Table が Foreign Keys の参照を子テーブルから持たれている場合だ。どのタイミングで子テーブルの参照を、コピー元の古いテーブルからコピー先の新しいテーブルに切り替えるか、というタイミングがシビアな課題に直面する。

まず、pt-osc が Foreign Keys を保つ場合にどのように動作するかを見てみる。例えば、foo Table に対して、bar Table が子テーブルとして参照を持つとする。この場合、pt-osc は以下のような挙動をする。

pt-osc internal behaviour

pt-online-schema-change - how to handle Foreign Keys

この時、子テーブル bar に存在する foo への Foreign Keys の参照を変更する際の手法を、--alter-foreign-keys-method で選択できる。

  • rebuild_constraints
  • drop_swap
  • auto ... rebuild_constraintsdrop_swap かを動的に判別する
  • none ... 子テーブルの Foreign Keys の参照は、DROP TABLE された存在しないテーブルを向いたままになる

基本的には、rebuild_constraints の利用が望ましい. なぜなら、コピー元の古いテーブルからコピー先の新しいテーブルに参照を切り替える時、ALTER を子テーブルに対して実行するため、スキーマの制約条件をそのまま維持できるからだ。

一方、drop_swap 戦略を用いた場合、ALTER を用いず、古いテーブルを DROP TABLE したあとに新しいテーブルを RENAME するだけなのだ。したがって、理論上は問題なく動くかもしれないが、その間に何らかのデータ更新やデータロスがあった時に、データの整合性は保証されない。

また、ドキュメント に記載されている通り、古いテーブルを DROP TABLE してから新しいテーブルを RENAME するまでの間、若干のダウンタイムが発生する。

This method is faster and does not block, but it is riskier for two reasons. First, for a short time between dropping the original table and renaming the temporary table, the table to be altered simply does not exist, and queries against it will result in an error.

これは処理を考えれば当然のことだろう。この時にアプリケーションが Database になんらかのリクエストをした場合、当然エラーが発生するので、適切な Retry 処理を入れるなどの対応が必要となってくる。

rebuild_constraints fallbacks to drop_swap

しかし、問題は、実は rebuild_constraints を明示的に指定していても、drop_swap に Fallback するケースが有る、ということだ。

きっかけは、たまたまこのブログ で以下の文言を発見したことだ。

However, if the table t2 is too large, the alter operation may take too long, and - drop_swap may be forced. The main methods involved are determined_alter_fk_method, rebuild_constraints and swap_tables of pt-online-schema-change file.

The --alter-foreign-keys-method is determined at sub determine_alter_fk_method():

その後、Source Code を追って確認してみたが、どうやら上記に合致する実装を確認できた。コピーする総行数 $n_rows が、--chunk-size および --chunk-time から動的に算出される閾値 $max_rows を超えた場合、--alter-foreign-keys-method の指定に関わらず、必ず drop_swap 戦略にフォールバックする。

コピー対象の行数が多い場合、ALTER に時間がかかることが想定されるため、より早くテーブルを切り替えるために drop_swap するという設計は理解できなくはない。

ただ、明示的に rebuild_constraints を指定しているにも関わらず、アプリケーションから Database にリクエストする際に謎のダウンタイムが存在しており、実はその原因が drop_swap への Fallback だった、ということが今回発覚したのだった。

Alternative...?

とはいえ、Foreign Keys 制約を持った大規模なテーブルの Online Schema Migration というのは、なかなかチャレンジングな技術的課題ではないだろうか。

同様の Online Schema Migration では、GitHub や Facebook, SoundCloud などからツールが提供されているが、軽く確認したところ、そもそも Foreign Keys をもつテーブルがサポートされていなかったりする。

※ 間違っていたり、ブログ投稿後実装されてサポートされていたり、サポートされているツールがあったりしたらぜひ教えてほしい。

2021-01-14