riq0h.jp/content/post/test.md
Rikuoh b0154155cf
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
初稿完成
2023-11-11 22:16:27 +09:00

6 KiB
Raw Blame History

title date draft tags
あると思っていたバックアップが一つもなかった 2023-11-11T20:16:27+09:00 true
tech
diary

自分だけは、と思っていても起きる時は起きる。いつまでもあると思うな親と金とバックアップ。Cloudflare R2にcronで毎日アップロードしていたMastodonインスタンスのデータベースが、いつの間にか全部消えていた。AP実装においてデータベースはすべてである。これをなくしたらもはや取り返しはつかない。ドメインもなにもかも変えて一からやり直すしかない。

だからこそインスタンスの運営者はデータベースの保全に気を配る。わざわざオブジェクトストレージにアップロードしているのもサーバ本体の破損にデータベースが巻き込まれるのを防ぐためで、ローカルにバックアップを置くよりもクラウド上の方が安全との判断からだ。にも拘らず、ちょっとした行き違いが重なると瞬時に破滅の刃が首元まで迫ってくる。かつて僕が書いたバックアップの自動スクリプトの例を下記に記す。

#!/bin/bash

echo "Backup begin..."
cd /home/ユーザ/インスタンスのディレクトリ/
docker exec mastodon-db-1 pg_dump -Fc -U user db | gzip -c >> backup.gz
chmod 744 ./backup.gz
echo "Success!"

su - ユーザ << bash
 echo "Uploading to Cloudflare R2..." 
 cd /home/ユーザ/プロジェクトディレクトリ/
 npx wrangler r2 object put "あんたのバケット名/$(date +\%Y\%m\%d_\%H-\%M-\%S).gz" --file=/home/ユーザ/インスタンスのディレクトリ/backup.gz
 rm /home/ユーザ/インスタンスのディレクトリ/backup.gz
bash

実際のところ、このスクリプト自体にはさほど問題はない。もちろん万全を期すならエラー時の条件分岐を仕込んでメールで通知するなど他にやりようはあったとはいえ、今回の問題はもっと単純な仕様の読み落としによるものだった。スクリプトの後半、wranglerでCloudflare R2にファイルをアップロードする公式的なやり方が、意外にもバックアップ全消しの引き金を司っていた。

日々、流れ込む投稿やユーザ情報を蓄えるデータベースは肥大化の定めから逃れられない。インスタンス設立当初は100MBにも満たなかったバックアップはすぐに200MBを越え、現時点では300MBをも上回る。 しかし、wranglerがサポートする最大アップロード容量は315MBまでだったのだ。 君ら、知っていたか 僕は知らなかったよ。空バケットの画面に堂々と書いてあるのにな。データベースが315MBを越えたある日以来――それがいつなのかはもはや知る由もないが――毎日行われる予定のアップロード処理は途中で止まっていたことになる。

一方、バックアップを預かるCloudflare R2のバケット側では、一週間以上古いファイルを自動で削除する設定が施してあった。むやみにストレージをあふれさせるわけにはいかないので、これは特段におかしい措置ではない。ところが、新しいバックアップが来ない状態で定期削除が実行されると、1日経過するたびに既存のファイルが消されていき最終的にはすべてのバックアップが消滅する。結果、記事冒頭の画像の通り、まさにもぬけの殻と相成った。

翻ってスクリプトファイルに戻ると、最後にローカル側のバックアップを削除している。必要なファイルはクラウド上にアップロードされているはずなので、ローカルに残していても意味はない。そもそもサーバの生存をあてにしない前提のスクリプトなので、これ自体も悪手とまでは言えない。ストレージ容量も貴重ゆえ非効率な多重化は避けたい。

以上のように措置の一つ一つは必ずしも大きな問題ではなかったのに、わずかな過ちがドミ倒しのごとく連鎖して最終的にすべてのバックアップを失ってしまった。なお、ずいぶんすっとぼけた前振りを延々としてきたが、インシデント発覚時点でMastodonインスタンスは全然普通に動いていたので現実の障害には至っていない。

さしあたり、容量制限によって機能を果たせないwranglerに代わりrcloneを採用してスクリプトの内容を刷新した。さらに、ローカル側にも古いファイルを一定期間置く形に改めた。こうすればローカルかオブジェクトストレージのどちらかに一応バックアップが残るだろう。

#!/bin/bash

echo "Backup begin..."
cd /home/ユーザ/インスタンスのディレクトリ/
docker exec mastodon-db-1 pg_dump -Fc -U mastodon_user mastodon_db | gzip -c >> "/home/ユーザ/backup/$(date +\%Y\%m\%d_\%H-\%M-\%S).gz"
echo "Success!"

echo "Syncing..."
find /home/mastodon/backup/ -mtime +7 -name "*.gz" | xargs rm -f
rclone sync copy /home/ユーザ/backup/ r2:mastodon-backup

echo "Finish!"

おまけ

そもそもCloudflare R2上のバックアップファイルを定期的に消していたのは一定以上のデータ格納量を上回ると課金されるためだが、今回の件とは無関係にインスタンスのメディアファイルをずっと預けっぱなしにしていたのでどのみち金を払う羽目になった。それにしてもおぞましい金額だ。メルカリで使い古しの参考書などを売って100円ほど銭を稼がなければならない。