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