Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MySQL5.7 GA の Multi-threaded slave

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

MySQL5.7 GA の Multi-threaded slave

MySQL5.7 GA の Multi-threaded slave のお話です

Avatar for Takanori Sejima

Takanori Sejima PRO

September 02, 2016
Tweet

More Decks by Takanori Sejima

Other Decks in Technology

Transcript

  1. 今日のお題 - MySQL5.7 GA で Multi-Threaded Slave (MTS) が改善されました -

    --slave-parallel-type=LOGICAL_CLOCK が 追加されて、一見、良さそうなんですが - その実装についてまとめられた記事をみかけな いので、ざっくりまとめてみました - 有識者からのマサカリ歓迎します
  2. 5.7で導入された LOGICAL_CLOCK - MySQL 5.6 の MTS の実装である slave-parallel-type=DATABASE と異なり、

    同じ DATABASE でも、 slave が複数 thread で更新可能 - MySQL の伝統的な replication は SQL_Thread がシング ルスレッドであるがゆえに、 master の更新頻度が高いと slave の SQL_Thread がボトルネックになって、 replication の遅延が発生することがあった - master は複数の Thread で更新処理を実行できたが、 slave は SQL_Thread のみで更新する実装だった
  3. 夢のような機能ではあるけれど - どのようにして、同時実行可能だと判断するの か? - slave が複数の Thread で更新する場合、 slave

    の整合性はどうやって保たれるのか? - master の binlog とどうやって見比べれば良いのか? - stop slave したときの振る舞いは? - master <-> slave 間の connection が切れたと き、再接続や retry は?
  4. 次にわたしがとった行動 - sql/rpl_rli_pdb.cc の commit log を漁る - 関連しそうな WorkLog

    を読む - 知らない用語が出てきたら調べる - ソースコードと MySQL5.7 が出力するバイナリ ログを眺めてみる
  5. はじめに - そもそも、 slave の SQL_Thread がシングルス レッドのとき、どのようにして replication で

    master と同じ状態が復元されるのか? - いたってシンプル - master が注意深く binlog 吐いてる
  6. 例えば InnoDB の場合 1. master で更新処理実行中の各スレッドが、そ れぞれ transaction cache に更新内容をため

    ていく 2. InnoDB で PREPARE する(5.7.10 以降、 innodb_support_xa は常に true) 3. 1. の transaction cache から一連の更新処理 を BEGIN&COMMIT で挟んで binlogに書く 4. InnoDBで COMMIT する
  7. Two-Phase Commit & Group Commit - MySQL の Replication 開発者であらせられる

    Dr. Mats Kindahl の blog この記事がわかりや すいですが - Binary Log Group Commit in MySQL 5.6 - (この後の話に関連して)大事なところを二つだ けかいつまんで解説すると
  8. Two-Phase Commit(2PC) - 参考になるのは ha_commit_trans() や MYSQL_BIN_LOG::ordered_commit() あたり - Binary

    Log Group Commit in MySQL 5.6 の Figure.1 のとおり - storage engine(InnoDBなど)に prepare して - binlog に 書いて - binlog に COMMIT(fsync) してから - storage engineに COMMIT する
  9. Transaction Coordinator Log - ソースコード中に tc_log ってのが出てきますが - Transaction の順序を管理するための

    Log の 抽象クラスが TC_LOG であって、その実装の ひとつが MYSQL_BIN_LOG - MYSQL_BIN_LOG::prepare() や MYSQL_BIN_LOG::commit() が、 Two-Phase COMMIT を実現するために必要 な関数を呼んでる
  10. innodb_support_xa=true と 2PC - innodb_support_xa=true だと、 prepare のと き undo

    log に xid が書き込まれる(5.7.10以降 は常にそうなる) - undo log に xid 書き込まれた PREPARED な transaction は、 クラッシュ後の再起動時、 binlog から xid 読み込んだ後、その xid 使って innobase_commit_by_xid() で最終的に COMMIT される
  11. なんかややこしいですが - クラッシュリカバリ時、xid のない PREPARED は rollback の 対象になるんですが、 xid

    つき の PREPARED は binlog からその xid が取得 できれば COMMIT にできるようです。詳しくは - innobase_xa_prepare() - MYSQL_BIN_LOG::recover() - innobase_xa_recover() - innobase_commit_by_xid()
  12. というわけで、 MySQL の 2PC は - InnoDB のクラッシュリカバリ機能単体では実現 できず、 InnoDB

    のクラッシュリカバリ機能と binlog のクラッシュリカバリ機能とが組み合わ さって、実現されてるようです - binlog のヘッダには open するときに立てて close する ときにリセットするフラグがあるので、正常に close した か(クラッシュしてないか)は、フラグをみて判断してます
  13. Group Commit - Binary Log Group Commit in MySQL 5.6

    の Figure.5 を参照 - flush/sync/commit という stage がある - binlog へ書き出す のが flush stage - binlog に fsync() する のが sync stage - storage engine に commit するのが commit stage - flush stage に書きだした順序で、 commit stage で commit することが保証されている
  14. ソースコード的にいうと - Group Commit はまさに MYSQL_BIN_LOG::ordered_commit() - flush/sync/commit の stage

    を queue で管理 することによって、 fsync() の回数を減らして、 binlog に event 書き出す順番と storage engine に commit する順番を担保している - そして、 binlog に書くとき、各 Transaction を BEGIN - COMMIT でシリアライズしてる
  15. だから binary log は読みやすいし - そして slave の SQL_Thread は性能がでない

    - master は Transaction を並列実行しながらも、 それらをひとかたまりの BEGIN - COMMIT に まとめシリアライズして binlog に吐いている - master では並列実行してる Transaction が、 slave だと BEGIN - COMMIT のひとかたまり が、ひとつずつしか実行できない - まぁ SQL_Thread はシングルスレッドだしね
  16. 次に Anonymous_gtid_log_event - なぜここでGTIDの話がはじまるのか? - WL#7592: GTIDs: generate Gtid_log_event and

    Previous_gtids_log_event always - MySQL5.7.6 以降は、 GTID_MODE=OFF の ときでも、 Anonymous_gtid_log_event を出力 します - それはなぜか - 理由は二つ
  17. WL#7083 はわかる - WL#7592いわく - Therefore, we need to generate

    a per-transaction event also when GTID_MODE = OFF; this is needed e.g. for WL#7083 and WL#7165. - WL#7083は、GTIDをオンラインで有効化する ための修正らしいです。 - なるほどGTIDのためならしょうがない
  18. しかし WL#7165 は - WL#7165: MTS: Optimizing MTS scheduling by

    increasing the parallelization window on master - Anonymous_gtid_log_event や Gtid_log_event には、 MTS を最適化するため の、 logical timestamp が埋め込まれているそ うです
  19. 気を取り直して logical timestamp とは - WL#6314: MTS: Prepared transactions slave

    parallel applier で解説されてます - Lamport clock を使っているようです。 - (すごい雑にいうと)、 slave で並列実行可能で あること示すヒントを、 master は binlog に埋め 込み、 slave は binlog からヒントを読んで、複 数のTransactionを並列実行するようです。
  20. Anonymous_gtid_log_event には - last_committed と sequence_number が 8byte ずつ埋め込まれている。これが MySQL

    での logical timestamp。 - sequence_number は、master で binlog に Transaction をflushする度に increment される - last_committed は、master で commit 済みの Transaction のうち、最も値の大きい sequence_number
  21. そしてAnonymous_gtid_log_eventは - binlog に BEGIN 書きだす前に、出力されてい ます。 - ANONYMOUS_GTID(or GTID)

    -> BEGIN -> (statement or row) -> COMMIT という順で書 かれるわけです。 - ゆえに、後続する Transaction(BEGIN - COMMIT)に sequence_number を付与できる わけです。
  22. つまるところ - --slave-parallel-type=LOGICAL_CLOCK のと き、 GTID の log_event に埋め込まれた logical

    timestamp を利用している。 - slave で複数の Transaction が同時に実行さ れたら、それらの Transaction に紐付いた sequence_number が、(最終的に) slave の last_lwm_timestamp を更新している - lwm == low-water-mark
  23. last_committed - binlog 読んだとき、 その Transaction に紐付 いてる last_committed が

    last_lwm_timestamp より小さければ、実行可 能と slave は判断する - last_committed は、その Transaction が lock を取得するまでに、 COMMIT が完了している べき Transaction を示す値
  24. Group Assigned Queue(GAQ) - last_lwm_timestamp は GAQ と関係している - GAQ

    は、 5.6 のMTSで(WL#5569)できた概 念で、雑にいうと - GAQ は、Coordinator Thread が Relay Log から binlog event 読みだして Worker Thread に 渡すとき、 管理に使う、 固定長の queue - Transaction という job の Group をどのWorker Thread に Assign して、実行完了したかどうかを管理。
  25. GAQ の checkpoint - 具体的には mts_checkpoint_routine() - GAQ使い切るか、次のいずれかのタイミングで - slave_checkpoint_group

    回 Transaction を実行 - slave_checkpoint_period msec 経過したとき - 次のような処理をする - 実行完了した Transaction のエントリを GAQ から削除 - SHOW SLAVE STATUS で表示される情報を更新 - GAQ.lwm を更新。これ重要大変重要 - これが最終的に last_lwm_timestamp を更新する
  26. last_lwm_timestamp の必要性 - Coordinator Thread が checkpoint で GAQ.lwm を更新する理由(推測)

    - Transaction 完了した worker thread が直接 last_lwm_timstamp を更新 するとマズイ - 複数の Worker が Transaction を実行する場合、古くて時間のかかる Transaction が残ってるかもしれない - Transaction を assign した Coordinator がときどき Worker の状態を見 て、 どこまで Transaction 捌けてるか確認して lwm 更新する方が良い
  27. さしあたって - slave-parallel-type=LOGICAL_CLOCK で性 能上がるかどうかは - master の binlog で

    Gtid_log_event や  Anonymous_gtid_log_event の last_committed をみて、同じ last_committed がいくつあるか見ると、参考になる - 同じ last_committed の イベントが多いということは、そ れだけ slave で同時に実行できる Transaction が多い
  28. 例えば - 同じ last_committed の値を数えられるから - $ mysqlbinlog ${BINLOG_FILE} |

    egrep ‘GTID.*last_committed’ | awk '{print $11}' | sort | uniq -c - 同時実行可能な Transaction の数の上限を、 ざっくり数えられる - $ mysqlbinlog ${BINLOG_FILE} | egrep ‘GTID.*last_committed’| awk '{print $11}' | sort | uniq -c | sort -n | tail
  29. - last_committed を基準に複数の worker thread が Transaction を実行していいというこ とになると、 last_committed

    的にOKなら、 InnoDB の COMMIT の順序はどうなってもい いということになる - ということは、 slave が複数存在した場合、 slave ごとに COMMIT の順序が異なるというこ とになる consistency の問題
  30. slave ごとに COMMIT の順が異なると - replication が遅延してるしてないの問題ではな く - slave

    ごとに異なる状態が見えてしまう - 例えば、 master で Table A, Table B という二 つのTable にそれぞれ Record X, Record Yが 別々の Transaction から INSERT されたとき - X しか見えない slave と、 Y しか見えない slave が存在しうることになる
  31. slave-parallel-type=DATABASE では - この consistency の問題を回避するすべがな い - DATABASE が複数あった場合、DATABASE

    間で更新順序が保証されない - 順序が保証されなくても、最終的に整合性は保 たれるだろうけど - 例えば、master で更新処理が終わったとき、すべての slave の table は同じ状態にあるはず
  32. consistency 関連の WorkLog - WL#6813: MTS: ordered commits (sequential consistency)

    - すべての slave は master の binlog と同じ順 番で COMMIT するべきだという WorkLog です - そのためにでてくるオプションが slave_preserve_commit_order
  33. slave_preserve_commit_order - blog にもありますが制限がいくつかあります - On master - binlog_order_commits should

    be enabled - On slave - binlog_order_commits should be enabled - binary log should be enabled - log_slave_update should be enabled - slave_preserve_commit_order should be enabled - そして LOGICAL_CLOCK 必須
  34. なぜ log_slave_updates ? - Commit_order_manager のインスタンスが生 成される条件 になってるんですが - slave

    で binlog を吐くときに、 Two-Phase Commit や Group Commit を使うことで、 InnoDB の COMMIT 順を制御できるから - slave で binlog の COMMIT 順を担保することで、 InnoDB の COMMIT 順を担保している - Master の Group Commit をリプレイしてる
  35. ざっくり流れとしては 1. slave の cordinator thread が relay log 読む

    2. cordinator thread が Commit_order_manager::register_trx() で Commit_order_manager の FIFO な queue に worker thread の id つむ 3. worker thread が並行して Transaction 実行 4. process_flush_stage_queue() で、 2. の queue の順に binlog を書き出す(FLUSH)
  36. そして 5. sync_binlog_file() で binlog を fsync() する (sync_binlog の値次第で、

    fsync しないことも あるけれど) 6. process_commit_stage_queue() で InnoDB に COMMIT する
  37. それから、 Slave での retry - 5.6 のとき、松信さんが MTS でも slave_transaction_retries

    有効にして欲しい と バグレポートあげておられたのですが - WL#6964 で対応されました - slave で一時的なエラーが発生したとしても、こ れで自動で対応可能に - 例えば、 MTS で worker thread 起動しすぎるなどして transaction が timeout しちゃったとしても、 retry できる
  38. あと、補足すると - MTS & Statement-Based Replication & 非決 定性クエリの組み合わせはダメゼッタイ -

    INSERT … SELECT など、 MTS だと slave ごとに結 果が変わってもおかしくない - MTS & READ UNCOMMITTED の組み合わ せもヤバイと思う - slave_preserve_commit_order で保証されるのは、 binlog の COMMIT の順番だけなので
  39. もうちょっと補足すると - slave_pending_jobs_size_max を master の max_allowed_packet より大きくするべき - Coordinator

    Thread が Worker Thread に job (binlog event)を積むとき、slave_pending_jobs_size_max よ り大きな binlog event を積めないので - どっちも default のままなら、充分な余裕があると思う
  40. もっというと - LOGICAL_CLOCK ベースの MTS は、ある程 度 master が忙しくないと、効果が薄い -

    SQL_Thread でやってた仕事が、 cordinator thread と worker thread で分担されるので、オーバヘッドが大きく なる。 WL#6314 の Highe Level Architecture の 3.2 Problems にもそう書いてある
  41. かつて Percona の人は言いました - MySQL5.6 で MTS 使うなら GTID 有効にしま

    しょうと - sql_slave_skip_counter するときなどがつらいんで - 5.7 は slave_preserve_commit_order のおか げでだいぶ良くなったんだけど - すべてはただ一つの問題
  42. Exec_Master_Log_Pos の意味 - MTS 使うとき、 relay_log_info_repository = ‘TABLE’ にして select

    * from mysql.slave_worker_info; するとわかる - (worker thread が更新処理を実行していると き、)worker thread ごとの Master_log_pos と Exec_Master_Log_Pos は一致しない。
  43. SHOW SLAVE STATUS の更新頻度 - MTS のとき、 Exec_Master_Log_Pos などは リアルタイムで更新されない

    - 先ほど出てきた、 GAQ の checkpoint のタイミ ングで、 Exec_Master_Log_Pos などが更新さ れる - MTS を使う場合、 SHOW SLAVE STATUS だ けに頼るわけには行かなくなってくる
  44. Half-applied transacitons - SQL_Thread を KILL などしたとき、 rollback できないと、 transaction

    の Atomicity が保た れない - まぁこれは InnoDB 使えばいいでしょ - MTS 有効なときでも slave_transaction_retries 効くようになったし - MySQL 5.7 すばらしい
  45. Gaps - ざっくりいうと、 5.6 以前の MTS だと binlog の 順に

    Transaction 実行される保証がないので、 「一時的なエラーなどで、 relay log の途中に実 行されてない event が残ったらどうなるの?」と いう話 - これは slave_preserve_commit_order で commit 順保証できるようになって、改善した - MySQL 5.7 すばらしい
  46. Gap-free low-watermark position - これが Exec_Master_Log_Pos の話 - 5.7 で

    LOGICAL_CLOCK 使っても、コレが避 けられない - MTS 有効なとき、 mysql.slave_worker_info の Checkpoint_master_log_pos が、 Exec_Master_Log_Pos になる - worker_thread がどこまで relay log 上の event を実行 したかは、 Exec_Master_Log_Pos では分からない
  47. かつて Yahoo! Inc. の人は言いました - 昨年の Oracle Open World で

    - 「Multi-Threaded Replication 導入しつつ GTID 入れて、GTID入れるために Percona Server 5.6 導入した」と言ってたんですが - MySQL 5.7 になっても、 やっぱり GTID 使える ほうが、 MTS は導入しやすいママなんだなぁ - 遺憾でござる。
  48. 今回、改めて思ったのは - あるていど難しい実装になると、コード読んだだ けでは新機能を理解できないこともある - その点、 MySQL は commit log

    から WorkLog 漁っていけば、設計思想を踏まえて理解してい くことができる - 非常にとっつきやすいOSSで素晴らしいなと思 いました
  49. そして、いまやGTIDは - GTID は master の failover をシンプルにする ためだけではなく -

    GTID を踏まえつつ MTS が設計されているな ど、 replication の性能向上目的でも、(間接的 に)使われるようになってきてる - MySQL5.6 のとき、 Facebook さんらが GTID のバグレポートたくさんしてくれたし、そろそろ導 入していったほうが良い時期かなぁ
  50. ただ、 GTID まだ入れられない場合でも - 「やっべ slave めっちゃ遅延してるどうしよう」と いう状況になったとき、非常手段として、一時的 に LOGICAL_CLOCK

    & slave_preserve_commit_order & log_slave_updates という手段が取れるように なったのはけっこう便利なんじゃないでしょうか - SET GLOBAL relay_log_info_repository = ‘TABLE’ できるから、意外と敷居高くないし
  51. MTS使うときのまとめ - LOGICAL_CLOCK がよさそう - slave-parallel-type=LOGICAL_CLOCK - slave-parallel-workers > 1

    - log_slave_updates=ON - slave_preserve_commit_order=ON - mysql.slave_worker_info 見えると良い - relay_log_info_repository = ‘TABLE’