GA

2025/06/20

バイナリログのバックアップについて考える in 2025

rsync

シェルが必要(たとえば ~/.ssh/authorized_keys で mysqldの実効アカウントにログインできるとか)なので新規に採用は嫌な感じ。
それを飲めるなら候補の一つとしてアリな気はする。
帯域ネックなら -z で圧縮をかけるとCPUを使いつつ転送に圧縮をかけられるが、転送後のファイルは圧縮されていない状態で現れるので保管を考えると更に圧縮する必要があって手間は手間。 binlog_transaction_compression が有効なら最初から圧縮されているのでベター。
binlog暗号化をされている状態でも保管はできるが、暗号化されたバイナリログを読み出すためにはkeyringが必要で、mysqldを起動させないといけない。
mysqldを止めずに実行する前提なので、バイナリログの最後のイベント(今まさに書き込んでいるイベント)は壊れているかもしれない。ただmysqlbinlogでデコードする時にワーニングが出るだけなのでわかっていさえすれば大丈夫。デフォルトではファイルのタイムスタンプが一致すれば次回はスキップされる。その時アクティブだったバイナリログは大体先頭から転送し直しになる。
コマンドの実行間隔がRPOになるが、他にレプリカがあれば最新のデータはそいつから取り出せるのでPITRの文脈であれば数分~15分, 30分, 1時間くらいは許容範囲なのではないかしら。

$ rsync user@host:/var/lib/mysql/mysql-bin.* ./
$ ll
total 375940
-rw-r----- 1 yoku0825 yoku0825       381 Jun 20 09:37 bin.000009
-rw-r----- 1 yoku0825 yoku0825       569 Jun 20 09:37 bin.000010
-rw-r----- 1 yoku0825 yoku0825 269449054 Jun 20 09:38 bin.000011
-rw-r----- 1 yoku0825 yoku0825 115493747 Jun 20 09:38 bin.000012
-rw-r----- 1 yoku0825 yoku0825        52 Jun 20 09:38 bin.index

mysqlbinlog

俺の大好きなパターン。 REPLICATION SLAVE 権限が必要。 —read-from-remote-server—raw を組み合わせて使う。 --raw を使わない場合はデコード後のテキストファイルが手に入るが、この場合PITRの時の —stop-datetime などのオプションが使えなくなる = テキストエディタで編集しないといけないのでなるべくそのままのバイナリログの形が好み。

  • 日々の覚書: mysqlbinlogでバイナリログをバックアップするとか
  • yoku0825/binlog_stream_container: Docker image for streaming MySQL binlog backup.
    通信経路圧縮をするにはzlibを使った -C だったが、8.0.18とそれ以降では —compression-algorithms でzstdが指定可能。zstdの方が軽くて速い。これもbinlog圧縮がされているなら圧縮後の転送量&ファイルサイズで済む。
    rsyncのようなワイルドカード指定がないので1つずつバイナリログファイル名を指定するか、「このバイナリログ以降のすべてをストリームし続ける( = —stop-never )」の2択しかない。転送対象に指定したバイナリログは手元にあろうがなかろうが全部先頭から再転送になる。
    rsyncと違ってmysqldが落ちている間は使えない。基本的にファイルを全部読み切ってから終了(または、 --stop-never で次のバイナリログを待機)するので最後のイベントが無効ってことはない。mysqlbinlog側がプロセスダウンしない限りは。
    binlog暗号化されている場合、mysqlbinlogが保管したファイルは平文に戻っている。得られたバイナリログを別途暗号化するなりなんなりが必要。
    --stop-never を使っている場合はmysqlbinlogプロセスが常駐して ( レプリケーションのレプリカと同じように ) バイナリログを逐次受信するためRPOがすこぶる短いことが期待できる。プロセスダウンをちゃんと検知して必要なら再起動する必要はあるけれども。
    そのへん(プロセスが起動された時に手元の最後のバイナリログのみ先頭から取り直す、手元にバイナリログがないならサーバーにある最初のファイルから読み出す)をケアしたスクリプトがこんな感じ。
  • https://github.com/yoku0825/binlog_stream_container/blob/main/binlog_stream_wrapper.sh#L22-L53

( ´-`).oO( holded ってなんだ… held だろう…

$ export MYSQL_PWD=xx
$ for num in {9..12} ; do
> mysqlbinlog -h sandbox.oci.yoku0825.work -P 64080 --raw -ubinlog -R --compression-algorithms=zstd "$(printf "%06d" $num)"
> done

$ ll
total 375936
-rw-r----- 1 yoku0825 yoku0825       381 Jun 20 09:40 bin.000009
-rw-r----- 1 yoku0825 yoku0825       569 Jun 20 09:40 bin.000010
-rw-r----- 1 yoku0825 yoku0825 269449054 Jun 20 09:40 bin.000011
-rw-r----- 1 yoku0825 yoku0825 115493747 Jun 20 09:41 bin.000012

MySQL Shellのutil.dumpBinlogs

最近の新顔。MySQL Shell 9.2か何かの新機能で、 MySQL Shell 8.4には入っていない 。2025年6月現在ではInnovation ReleaseのMySQL Shellが必要。
対象になるバイナリログファイルを特定するために SHOW BINARY LOGS を実行するので REPLICATION CLIENT 権限、実際にバイナリログを吸い上げるために REPLICATION SLAVE 権限が必要。MySQLプロトコルを使うので mysqld がダウンしてるとダメ。
俺が詳しくないだけかも知れないけど通信経路の圧縮はなさそう? サーバー側のbinlog圧縮が効いてれば節約になるのは他のと同じ。コイツだけ、「転送してきたバイナリログを圧縮してファイルに落とす」ので別途の圧縮は不要( binlog_transaction_compression
初回のみ、「最初に取り始めるバイナリログファイル名」を指定する必要があるが、2回目以降は「初回のバックアップで読み切ったポジションをメタデータファイルに残す」ので勝手に差分取得が可能。

mysqlbinlog と違って常駐はできないのでrsyncと同じくRPOの制約がある。実行のたびに指定したディレクトリに日時を示したディレクトリができるっぽい。

$ mysqlsh -h sandbox.oci.yoku0825.work -P 64080 -ubinlog -- util dumpBinlogs ./ { --startFrom=bin.000009 }
$ ll
total 8
drwxr-x--- 2 yoku0825 yoku0825 4096 Jun 20 09:58 2025-06-20-00-56-58
-rw-r----- 1 yoku0825 yoku0825  457 Jun 20 09:56 @.binlog.info.json

$ ll
total 8
drwxr-x--- 2 yoku0825 yoku0825 4096 Jun 20 09:58 2025-06-20-00-56-58
-rw-r----- 1 yoku0825 yoku0825  457 Jun 20 09:56 @.binlog.info.json

$ ll 2025-06-20-00-56-58/
total 182440
-rw-r----- 1 yoku0825 yoku0825       251 Jun 20 09:58 @.binlog.done.json
-rw-r----- 1 yoku0825 yoku0825     31614 Jun 20 09:56 @.binlog.json
-rw-r----- 1 yoku0825 yoku0825       275 Jun 20 09:56 bin.000009.json
-rw-r----- 1 yoku0825 yoku0825       274 Jun 20 09:56 bin.000009.zst
-rw-r----- 1 yoku0825 yoku0825       278 Jun 20 09:56 bin.000010.json
-rw-r----- 1 yoku0825 yoku0825       342 Jun 20 09:56 bin.000010.zst
-rw-r----- 1 yoku0825 yoku0825       295 Jun 20 09:58 bin.000011.json
-rw-r----- 1 yoku0825 yoku0825 130723963 Jun 20 09:58 bin.000011.zst
-rw-r----- 1 yoku0825 yoku0825       324 Jun 20 09:57 bin.000012.json
-rw-r----- 1 yoku0825 yoku0825  56018567 Jun 20 09:57 bin.000012.zst

もう一度実行すると差分がまた日付のディレクトリに現れる。今度は startFrom の指定をする必要はない。バックアップ先ディレクトリで一度でもdumpBinlogsが実行されていれば勝手に途中から実行する。

$ mysqlsh -h sandbox.oci.yoku0825.work -P 64080 -ubinlog -- util dumpBinlogs ./
Starting from previous dump: /home/yoku0825/work/2025-06-20-00-56-58, created at: 2025-06-20 00:56:58 UTC
Starting from binary log file: bin.000012:115493970
Will finish at binary log file: bin.000012:121584248
Dumping 1 binlogs (6.09 MB of data) using 4 threads
100% (6.09 MB / 6.09 MB), 3.27 MB/s, 2.46 MB/s compressed, 1 / 1 binlogs done
Dump was written to: /home/yoku0825/work/2025-06-20-01-03-00
Total duration: 00:00:01s
Binlogs dumped: 1
GTID set dumped: 6f9ee6f0-1feb-11f0-a8f1-02001702f486:449-9081
Uncompressed data size: 6.09 MB
Compressed data size: 4.64 MB
Compression ratio: 1.3
Events written: 17267
Bytes written: 4.64 MB
Average uncompressed throughput: 3.35 MB/s
Average compressed throughput: 2.55 MB/s

$ ll
total 16
drwxr-x--- 2 yoku0825 yoku0825 4096 Jun 20 09:58 2025-06-20-00-56-58
drwxr-x--- 2 yoku0825 yoku0825 4096 Jun 20 10:03 2025-06-20-01-03-00
-rw-r----- 1 yoku0825 yoku0825  457 Jun 20 09:56 @.binlog.info.json

$ ll 2025-06-20-01-03-00/
total 4568
-rw-r----- 1 yoku0825 yoku0825      42 Jun 20 10:03 @.binlog.done.json
-rw-r----- 1 yoku0825 yoku0825   31480 Jun 20 10:03 @.binlog.json
-rw-r----- 1 yoku0825 yoku0825     355 Jun 20 10:03 bin.000012.json
-rw-r----- 1 yoku0825 yoku0825 4636447 Jun 20 10:03 bin.000012.zst

$ ll */bin.000012*.zst
-rw-r----- 1 yoku0825 yoku0825 56018567 Jun 20 09:57 2025-06-20-00-56-58/bin.000012.zst
-rw-r----- 1 yoku0825 yoku0825  4636447 Jun 20 10:03 2025-06-20-01-03-00/bin.000012.zst

ファイルも先頭から取り直しているわけではなかった。

各zstファイルを解凍するといつもの(デコード前の)バイナリログが出てくる。
util.dumpBinlogs で取ったバックアップは util.loadBinlogs でディレクトリを指定する形で一気に適用できる。一つ一つ解凍して mysqlbinlog | mysql とかする必要はない。

binlog暗号化に関する話は mysqlbinlog と一緒。binlog圧縮の解凍はクライアントの責務だけど、暗号化されたbinlogを適用可能に復号するのはMySQLサーバー側の責務なので、MySQLプロトコルを使うクライアントサーバー形式だとどうしてもこうなる ( なので gh-ost とかも一緒)

【2025/06/20 18:18】

ただし、


     MySQL  localhost:3306  JS > util.dumpBinlogs('/backup/binlog_mysqlsh')
    The 'gtid_mode' system variable on the source instance is set to 'OFF'. This utility requires GTID support to be enabled. (ArgumentError)で

でした。最近サンドボックスも全部gtid_mode=ON統一だから気が付かなかった…

意外と楽しかった。

2025/06/17

バックアップをネットワークまたいだファイルサーバに置きたいけど指定した以外のファイルは上書きもされたくないし読まれたくもない

TL;DR


普段から使っていたのはファイルサーバー側でポートを開いてリダイレクトしちゃう。実際にはもう少し「ランダムにポートを開いてその番号を教えてくれるスクリプトだけをssh越しに実行させる」とか「curlで叩くとトークン検証してランダムにポートを開いてその番号を教えてくれるAppにする」とかの工夫はできる。

nc -l 13306 > /path/to/myfile

バックアップはリモートから書き込む。bash使いなので /dev/tcp は好き。

xtrabackup -S /usr/mysql/8.4.5/data/mysql.sock -uroot --backup --stream=xbstream | pv >  /dev/tcp/file_server/13306

書き込みが終わる(異常終了を含む)とncプロセスは落ちるので再利用のためにはレシーバー側でncの再実行が必要。そのタイミングでリダイレクト先のファイル名を変更できるので上書きも回避できる(noclobberでエラーにするか、別のファイルとして受けるかの違いではある)

もし通信経路を通す前に圧縮したいなら送信元で圧縮すればいいし、

xtrabackup -S /usr/mysql/8.4.5/data/mysql.sock -uroot --backup --stream=xbstream | pzstd -c | pv  > /dev/tcp/file_server/13306

受信側でそのまま展開したいならncからパイプしてやればいい(xbstreamまではパイプで繋げるけどxtrabackup —prepareは別でやらないといけない)

nc -l 13306 | pzstd -dc | xbstream -x

受ける側でパスワードをかけたりもした(ランダムに推測不可能なものを作ってDBAにだけメールしてた気がする)

nc -l 13306 | openssl aes-256-cbc -pbkdf2 -e -pass pass:something > /tmp/myfile.enc

$ file /tmp/myfile.enc
/tmp/myfile.enc: openssl enc'd data with salted password

$ openssl aes-256-cbc -pbkdf2 -d -in /tmp/myfile.enc -out /tmp/myfile
enter AES-256-CBC decryption password:

$ pzstd -dc /tmp/myfile | xbstream -x
/tmp/myfile         : 75159378 bytes

デーモンにしたりすると面倒だけど後々が楽。。

2025/06/10

Oracle Linux 8でMySQL 5.7のrpmをビルドする

TL;DR


MySQL Product Archives で見たら、MySQL 5.7はOracle Linux(に限らずRHEL系の)8に対応したrpmはなかった。けど欲しい。

というわけで(el7用のrpmファイルでも動くような気はするけど一応) Oracle Linuxのコンテナイメージ の中でrpmbuildするメモ。

# wget http://repo.mysql.com/yum/mysql-5.7-community/el/7/SRPMS/mysql-community-5.7.44-1.el7.src.rpm
# rpm -i mysql-community-5.7.44-1.el7.src.rpm

# dnf install rpm-build

# rpmbuild -bb ~/rpmbuild/SPECS/mysql.spec
error: Failed build dependencies:
        cyrus-sasl-devel is needed by mysql-community-5.7.44-1.el8.x86_64
        libaio-devel is needed by mysql-community-5.7.44-1.el8.x86_64
        ncurses-devel is needed by mysql-community-5.7.44-1.el8.x86_64
        numactl-devel is needed by mysql-community-5.7.44-1.el8.x86_64
        openldap-devel is needed by mysql-community-5.7.44-1.el8.x86_64
        openssl-devel is needed by mysql-community-5.7.44-1.el8.x86_64
        perl is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Carp) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Config) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Cwd) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Data::Dumper) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(English) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Errno) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Exporter) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Fcntl) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(File::Basename) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(File::Copy) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(File::Find) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(File::Path) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(File::Spec) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(File::Spec::Functions) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(File::Temp) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Getopt::Long) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(IO::File) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(IO::Handle) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(IO::Pipe) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(IO::Select) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(IO::Socket) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(IO::Socket::INET) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(JSON) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Memoize) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(POSIX) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Sys::Hostname) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Time::HiRes) is needed by mysql-community-5.7.44-1.el8.x86_64
        perl(Time::localtime) is needed by mysql-community-5.7.44-1.el8.x86_64
        time is needed by mysql-community-5.7.44-1.el8.x86_64
        zlib-devel is needed by mysql-community-5.7.44-1.el8.x86_64

依存のものを入れる。

# dnf install -y cyrus-sasl-devel libaio-devel ncurses-devel numactl-devel openldap-devel openssl-devel perl time zlib-devel perl-JSON

# rpmbuild -bb ~/rpmbuild/SPECS/mysql.spec
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.5u4iqi
+ umask 022
+ cd /root/rpmbuild/BUILD
+ cd /root/rpmbuild/BUILD
+ rm -rf mysql-5.7.44
+ /usr/bin/mkdir -p mysql-5.7.44

..
+ cmake ../mysql-5.7.44 -DBUILD_CONFIG=mysql_release -DINSTALL_LAYOUT=RPM -DCMAKE_BUILD_TYPE=Debug -DWITH_BOOST=.. '-DCMAKE_C_FLAGS= -g -pipe -Wall -Werror=format-security   -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' '-DCMAKE_CXX_FLAGS= -g -pipe -Wall -Werror=format-security   -Wp,-D_GLIBCXX_ASSERTIONS -fexceptions -fstack-protector-strong -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' -DWITH_INNODB_MEMCACHED=1 -DINSTALL_LIBDIR=lib64/mysql -DINSTALL_PLUGINDIR=lib64/mysql/plugin/debug -DMYSQL_UNIX_ADDR=/var/lib/mysql/mysql.sock -DMYSQLX_UNIX_ADDR=/var/run/mysqld/mysqlx.sock -DFEATURE_SET=community -DWITH_EMBEDDED_SERVER=1 -DWITH_EMBEDDED_SHARED_LIBRARY=1 -DWITH_NUMA=ON '-DCOMPILATION_COMMENT=MySQL Community Server - Debug (GPL)' -DMYSQL_SERVER_SUFFIX=
/var/tmp/rpm-tmp.AHE541: line 47: cmake: command not found
error: Bad exit status from /var/tmp/rpm-tmp.AHE541 (%build)

RPM build errors:
    Bad exit status from /var/tmp/rpm-tmp.AHE541 (%build)

cmake忘れてた…というかBuildRequiresに書いておいてくれればいいのに。そのほか、ビルド中に転けたパッケージも入れた。

# dnf install -y cmake gcc gcc-c++ libtirpc-devel
# dnf install -y --enablerepo=ol8_codeready_builder rpcgen
# rpmbuild -bb ~/rpmbuild/SPECS/mysql.spec

これで完成。

# ll ~/rpmbuild/RPMS/x86_64/mysql-community-*
-rw-r--r--. 1 root root  22908224 Jun 10 03:31 /root/rpmbuild/RPMS/x86_64/mysql-community-client-5.7.44-1.el8.x86_64.rpm
-rw-r--r--. 1 root root    323348 Jun 10 03:31 /root/rpmbuild/RPMS/x86_64/mysql-community-common-5.7.44-1.el8.x86_64.rpm
-rw-r--r--. 1 root root   3790152 Jun 10 03:32 /root/rpmbuild/RPMS/x86_64/mysql-community-devel-5.7.44-1.el8.x86_64.rpm
-rw-r--r--. 1 root root  59816800 Jun 10 03:32 /root/rpmbuild/RPMS/x86_64/mysql-community-embedded-5.7.44-1.el8.x86_64.rpm
-rw-r--r--. 1 root root 172178112 Jun 10 03:33 /root/rpmbuild/RPMS/x86_64/mysql-community-embedded-devel-5.7.44-1.el8.x86_64.rpm
-rw-r--r--. 1 root root   1866328 Jun 10 03:32 /root/rpmbuild/RPMS/x86_64/mysql-community-libs-5.7.44-1.el8.x86_64.rpm
-rw-r--r--. 1 root root 166402044 Jun 10 03:31 /root/rpmbuild/RPMS/x86_64/mysql-community-server-5.7.44-1.el8.x86_64.rpm
-rw-r--r--. 1 root root 150149116 Jun 10 03:32 /root/rpmbuild/RPMS/x86_64/mysql-community-test-5.7.44-1.el8.x86_64.rpm

2025/06/09

Re: セキュアそうでセキュアじゃない少しセキュアな気分になれるmysql_config_editorに直接--passwordを指定できるようにしてみた



  • mysql_config_editor--password, -p だけは引数を受け付けられなくて、常にプロンプトでパスワードを要求する

    • それはそれでいいんだけど、スクリプトとかで動的に .my.login.cnf を作るのに面倒
    • なので --password をオプションで受け付けられるようなパッチを書いてみた
  • .mylogin.cnf は鍵なしでパラメータを読み出せる (パスワードがマスクされているのは単に平文で読みだした後に mysql_config_editor 自身がマスクしているだけ)ので、いじるついでにこれも出せるようにしてみた

    • ちなみに my_print_defaults -s "セクション名" ならマスクもせずに取り出せる( my_print_defaults でいけるって知らなくて足してしまった…)

パッチはこれ。MySQL 5.7.44の mysql_config_editor ベース。
mysql_config_editor_patch

$ cd /path/to/mysql-5.7.44
$ wget https://gist.githubusercontent.com/yoku0825/6d8a2fee64de1b687f7c41bd6052f69e/raw/f860e29acb41990c055b56c73e06a5008bf4c481/mysql_config_editor_patch -O mysql_config_editor.patch
$ patch --backup client/mysql_config_editor.cc mysql_config_editor.patch
$ make mysql_config_editor
$ client/mysql_config_editor set --login-path=yoku0825 -uabc -pdef
$ client/mysql_config_editor print --all
[yoku0825]
user = abc
password = def

2025/05/21

レプリケーション構成でない2つのMySQLに確実にデータを書き込みたいメモ

  • それぞれのMySQLはSemisyncで正しくフェイルオーバーしたりGroup Replicationだったりで書き込みは信用できることにする
  • サロゲートキー(だけ)でなくて比較可能なPRIMARY KEYが存在することにする
  • 取り敢えず簡単なINSERTで考えるけどUPDATEもありそう

という前提で考え事。
FEDERATEDストレージエンジンはそもそもトランザクションをサポートしないので論外とする。

App側の2層コミットっぽいもの

2 Phase Commitはそもそもコーディネーターの障害に対する耐性がない…。

eval
{
  $dbh1->begin;
  $dbh1->do("INSERT INTO ..");
  $dbh2->begin;
  $dbh2->do("INSERT INTO ..");
};

if ($@)
{
  $dbh1->rollback;
  $dbh2->rollback;
}

$dbh1->commit;

### この瞬間にAppのプロセスが落ちるとダメ
$dbh2->commit;

この場合「必ずdbh2の方が足りない状態になる」のは確かかもしれない。

補償トランザクション的な何か

さっきののcommitの部分にさらに一捻り…したところで

eval
{
  $dbh1->commit;
};
$dbh2->rollback if $@;

eval
{
  $dbh2->commit;
};

if ($@)
{
  $dbh1->begin;
  ### ここらへんで落ちるとやっぱりダメ
  $dbh1->do("DELETE FROM ..");
  $dbh1->commit;
}

補償トランザクション形式だとUPDATEの巻き戻しがなかなか大変そうだし、結局落ちるとダメにはなる。この場合もdbh2が足りない状態になるのは当たりだろうか。
ちなみに MySQLのXAステートメント を使ったところでxidをどこかに記録しておかないと処理が引き継げないので結局何も記録せずに単一プロセスでやろうというのは無理。

さっきのと合わせて、dbh1の側の created_at みたいなのにインデックスを張っておいて定期的にスキャンして突合するような感じになるはず。DELETEまで考えるとdbh2側をスキャンしてdbh1側をNested Loopスキャンもしないといけなくなる(何がなくなったのかは片方だけ見てもわからないから)

非同期ダブルライト型

少なくとも片方のMySQLにはある程度確実に書ける(書けなければちゃんとエラーになる(実際semisyncではこれは保証できないんだけどいったん置いておく)、書いた以上はMySQL側がクラッシュしてフェイルオーバーしても残る)という前提に基づくと、

$dbh1->begin;
eval
{
  $dbh1->do("INSERT INTO metadata ..");
  $dbh1->do("INSERT INTO real_table ..");
  $dbh1->commit;
};

$dbh1->rollback if $@;

メタデータ(例えばdbh2に発行するべきINSERT文そのものでもいい)のテーブルへの記録と、dbh1側の書き込みたいテーブルを1つのトランザクションにまとめると、この2つのテーブルに関してはトランザクションで保護される。
$dbh1->commit までにAppがクラッシュした場合はAppとしてエラーになってメタデータもテーブルも残っていない状態になるし、 $dbh1->commit まで到達した場合は両方とも残っている。
で、別途ワーカーが必要。

$dbh1->begin;
my $sth= $dbh1->prepare("SELECT * FROM metadata ORDER BY id ASC LIMIT 1 FOR UPDATE SKIP LOCKED");
my $row= $sth->selectrow_hashref;
eval
{
  $dbh2->begin;
  $dbh2->do("INSERT INTO .."); 
  $dbh2->commit;
}
$dbh2->rollback if $@;
### ここらへんで落ちるとmetadataだけおかしくなる
$dbh1->do("DELETE FROM metadata ..");
$dbh1->commit;

この場合、

  • ワーカーが動くまではdbh1とdbh2で不整合が起こり続ける(dbh2が足りない状態になる)こと
  • ワーカーがクラッシュするとmetadataだけ消えずに余ること
    なのでmetadataと実際のテーブルの更新情報を突合するためのキーは必ず必要になって(dbh1に記録する時にApp側でUUID生成して、実テーブルの方に last_updated_by = ? みたいに持つとか)ワーカーが同じ更新を二重実行しても安全になるようにしてやらないといけない。
    けれどスキャンは必要なさそうだしワーカーの起動が制御しやすそうだなと思ったのでした。

まるでmysql.slave_relay_log_infoとSQLスレッドだな、とは思った。
ただの考え事でした。


【2025/05/21 21:22】

「非同期ダブルライト型」と呼んでいたやつは「Transactional Outboxパターン」という名前がついていると教えてもらいました! ありがとうございますm(_ _)m



トランザクションアウトボックスパターン - AWS 規範ガイダンス

2025/04/18

Oracle Linux 8.10でのMySQL 9.3.0ビルド

MySQL Bugs: #117993: cmake failed MySQL 9.3.0 + Oracle Linux 8.10 で再現しなかったらしく、フレッシュインストールで試す。

$ wget https://dev.mysql.com/get/Downloads/MySQL-9.3/mysql-9.3.0.tar.gz
$ tar xf mysql-9.3.0.tar.gz
$ cd mysql-9.3.0/
$ sudo dnf install -y cmake

$ cmake -DCMAKE_INSTALL_PREFIX=/usr/mysql/9.3.0 -DMINIMAL_RELWITHDEBINFO=OFF -DFORCE_INSOURCE_BUILD=1
CMake Warning:
  No source or binary directory provided.  Both will be assumed to be the
  same as the current working directory, but note that this warning will
  become a fatal error in future CMake releases.

-- Running cmake version 3.26.5
-- Could NOT find Git (missing: GIT_EXECUTABLE)
-- This is .el8. as found from 'rpm -qf /'
-- Looking for a devtoolset compiler
CMake Warning at CMakeLists.txt:393 (MESSAGE):
  Could not find devtoolset compiler/linker in /opt/rh/gcc-toolset-13

CMake Warning at CMakeLists.txt:395 (MESSAGE):
  You need to install the required packages:

   yum install gcc-toolset-13-gcc gcc-toolset-13-gcc-c++ gcc-toolset-13-binutils gcc-toolset-13-annobin-annocheck gcc-toolset-13-annobin-plugin-gcc

CMake Error at CMakeLists.txt:397 (MESSAGE):
  Or you can set CMAKE_C_COMPILER and CMAKE_CXX_COMPILER explicitly.

-- Configuring incomplete, errors occurred!

$ sudo yum install gcc-toolset-13-gcc gcc-toolset-13-gcc-c++ gcc-toolset-13-binutils gcc-toolset-13-annobin-annocheck gcc-toolset-13-annobin-plugin-gcc
$ sudo dnf install -y --enablerepo=ol8_codeready_builder openssl-devel ncurses-devel libtirpc-devel rpcgen cpp
$ rm CMakeCache.txt
$ cmake -DCMAKE_INSTALL_PREFIX=/usr/mysql/9.3.0 -DMINIMAL_RELWITHDEBINFO=OFF -DFORCE_INSOURCE_BUILD=1

通ったわ…。

$ scl -l
You use deprecated syntax "-l|--list", use "list-collections" instead.
gcc-toolset-11
gcc-toolset-13

おま環だった。ごめんUmesh..

2025/04/16

HeatWave MySQLがどれくらいVanilla MySQL GPL版と同じくらいか考える旅 / 読み取りレプリカ編その2

日々の覚書: HeatWave MySQLがどれくらいVanilla MySQL GPL版と同じくらいか考える旅 / 読み取りレプリカ編 の続き。

読み取りレプリカを1つ足してみた。ついでなので “MySQL.8.HA” にしてみたんだけれどこれは単に無視されるのかしら…(クラスタトータルのECPUも8ECPUしか増えなかったので、たぶんこれは関係なく1台だけ足されるんだと思う)

mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| CHANNEL_NAME              | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION | MEMBER_COMMUNICATION_STACK |
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
| group_replication_applier |           |             |        NULL | OFFLINE      |             |                |                            |
+---------------------------+-----------+-------------+-------------+--------------+-------------+----------------+----------------------------+
1 row in set (0.00 sec)

前回気が付かなかったけど、読み取りレプリカは bind_address の設定が増えてた。ロードバランサ側のネームスペースのIPアドレスはWEB画面から見られるIPのいずれでもない。

+---------------+-------------------------------------------+
| Variable_name | Value                                     |
+---------------+-------------------------------------------+
| bind_address  | 10.0.0.195/mysql,10.5.195.88/loadbalancer |
+---------------+-------------------------------------------+
1 row in set (0.01 sec)

ロードバランサ側のIPはちゃんと分散するようになった。単純なラウンドロビンじゃないかしら。

$ time for n in {1..100} ; do mysql -h 10.0.0.118 -uadmin -sse "SELECT @@hostname" ;  done | sort | uniq -c
     58 eiewhuzpjiihavkp
     42 fuis3dpz4ueksuzu

real    0m2.259s
user    0m1.027s
sys     0m0.496s

1台クラッシュさせてみると、少なくとも10秒程度でロードバランサから外している気配はなくて、再起動してくるのに合わせてロードバランサに戻っている。これSQL_Threadだけ止めたりしたらどうなるんだろう。

$ while true ; do for n in {1..100} ; do mysql -h 10.0.0.118 -uadmin -sse "SELECT @@hostname" 2> >(sort -u);  done | sort | u
niq -c ; date; sleep 1; done
     50 eiewhuzpjiihavkp
     50 fuis3dpz4ueksuzu
Wed Apr 16 04:59:49 GMT 2025
     56 eiewhuzpjiihavkp
     44 fuis3dpz4ueksuzu
Wed Apr 16 04:59:52 GMT 2025
     50 eiewhuzpjiihavkp
     50 fuis3dpz4ueksuzu
Wed Apr 16 04:59:55 GMT 2025
     51 eiewhuzpjiihavkp
     49 fuis3dpz4ueksuzu
Wed Apr 16 04:59:58 GMT 2025
     43 eiewhuzpjiihavkp
     57 fuis3dpz4ueksuzu
Wed Apr 16 05:00:02 GMT 2025
      6 ERROR 2003 (HY000): Can't connect to MySQL server on '10.0.0.118:3306' (111)
      1 ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 104
     42 eiewhuzpjiihavkp
     51 fuis3dpz4ueksuzu
Wed Apr 16 05:00:05 GMT 2025
     45 ERROR 2003 (HY000): Can't connect to MySQL server on '10.0.0.118:3306' (111)
     55 fuis3dpz4ueksuzu
Wed Apr 16 05:00:08 GMT 2025
     47 ERROR 2003 (HY000): Can't connect to MySQL server on '10.0.0.118:3306' (111)
     53 fuis3dpz4ueksuzu
Wed Apr 16 05:00:10 GMT 2025
     52 ERROR 2003 (HY000): Can't connect to MySQL server on '10.0.0.118:3306' (111)
     48 fuis3dpz4ueksuzu
Wed Apr 16 05:00:13 GMT 2025
     50 ERROR 2003 (HY000): Can't connect to MySQL server on '10.0.0.118:3306' (111)
     50 fuis3dpz4ueksuzu
Wed Apr 16 05:00:16 GMT 2025
     54 eiewhuzpjiihavkp
     46 fuis3dpz4ueksuzu
Wed Apr 16 05:00:19 GMT 2025

↓エラーログ的にはこのへん。

mysql> SELECT * FROM performance_schema.error_log WHERE error_code <> 'MY-011071'\G

..
*************************** 386. row ***************************
    LOGGED: 2025-04-16 05:00:05.999056
 THREAD_ID: 0
      PRIO: Note
ERROR_CODE: MY-013930
 SUBSYSTEM: Server
      DATA: systemd notify: STATUS=Server startup in progress
*************************** 387. row ***************************
    LOGGED: 2025-04-16 05:00:05.999924
 THREAD_ID: 0
      PRIO: System
ERROR_CODE: MY-015015
 SUBSYSTEM: Server
      DATA: MySQL Server - start.

..
*************************** 604. row ***************************
    LOGGED: 2025-04-16 05:00:16.789157
 THREAD_ID: 0
      PRIO: System
ERROR_CODE: MY-010931
 SUBSYSTEM: Server
      DATA: /usr/sbin/mysqld: ready for connections. Version: '8.4.4-u5-cloud'  socket: '/var/run/mysqld/mysql.sock'  port: 3306  MySQL Enterprise - Cloud.
*************************** 605. row ***************************
    LOGGED: 2025-04-16 05:00:16.789172
 THREAD_ID: 0
      PRIO: System
ERROR_CODE: MY-013292
 SUBSYSTEM: Server
      DATA: Admin interface ready for connections, address: '127.0.0.1'  port: 7306
*************************** 606. row ***************************
    LOGGED: 2025-04-16 05:00:16.789529
 THREAD_ID: 0
      PRIO: Note
ERROR_CODE: MY-013930
 SUBSYSTEM: Server
      DATA: systemd notify: READY=1
STATUS=Server is operational
MAIN_PID=144141

..

エラーログで思い出したついでに読み取りレプリカを追加した時のエラーログ。

*************************** 1408. row ***************************
    LOGGED: 2025-04-16 05:31:42.525733
 THREAD_ID: 0
      PRIO: Note
ERROR_CODE: MY-013930
 SUBSYSTEM: Server
      DATA: systemd notify: READY=1
STATUS=Server is operational
MAIN_PID=35573

*************************** 1409. row ***************************
    LOGGED: 2025-04-16 05:34:50.412149
 THREAD_ID: 44
      PRIO: System
ERROR_CODE: MY-010597
 SUBSYSTEM: Repl
      DATA: 'CHANGE REPLICATION SOURCE TO FOR CHANNEL 'oci_managed_read_replica' executed'. Previous state source_host='', source_port= 3306, source_log_file='', source_log_pos= 4, source_bind=''. New state source_host='10.0.114.60', source_port= 7306, source_log_file='', source_log_pos= 4, source_bind=''.
*************************** 1410. row ***************************
    LOGGED: 2025-04-16 05:34:50.624034
 THREAD_ID: 47
      PRIO: Note
ERROR_CODE: MY-010581
 SUBSYSTEM: Repl
      DATA: Replica SQL thread for channel 'oci_managed_read_replica' initialized, starting replication in log 'FIRST' at position 0, relay log '/db/replication/relay-log-oci_managed_read_replica.000001' position: 4
*************************** 1411. row ***************************
    LOGGED: 2025-04-16 05:34:50.625468
 THREAD_ID: 46
      PRIO: System
ERROR_CODE: MY-014002
 SUBSYSTEM: Repl
      DATA: Replica receiver thread for channel 'oci_managed_read_replica': connected to source 'ocirpl@10.0.114.60:7306' with server_uuid=07c65c46-18f9-11f0-89e5-020017067068, server_id=-1253503313. Starting GTID-based replication.