b-shock. Fortress

「ドラゴンクエスト3にみるアンチパターン」補足

「商人の街」の何がいけないのか。ドラゴンクエスト3にみるアンチパターン。の続き。
書き漏らしたことが若干あったので、フォローさせて頂く。

元コメント

【○○○○バーク】

そうですね、あれはかわいそうなことをしたと思います。ただ、これも容量不足が招いた悲劇なんです。 本来なら、あの商人は自分たちのパーティーに戻るハズだったんです。ところが、一度パーティーから はずれた商人を覚えておくメモリーがなくなっちゃって、それで結局は置いてきぼりにされちゃったんですね。 ボクとしても、助け出してあげたかったんですけど、メモリー不足だけはどうにもなりませんからね。 納得してください。

元々はこいつへの反論のつもりで書いた文章です。
それにしても、メモリーのせいにすれば済むとでも思ったのだろうか。納得しません。

Ultima

Wizへの言及はあるが、これについてスルーないのが不自然なほどw
忘れてただけです。勘弁してください。もっとも、Wizのほうが圧倒的に遊んでるから印象に残ってたというのはある。

キャラクターが「喋る」ということ。

ドラクエのポリシーとして、登場人物に喋らせないというポリシーがある。 喋らないほうがRPGの本質に近いのは確かなので、この姿勢を支持したいと思う。
ただ、電源系のRPGがいくらがんばっても真のRPGになることはなく、 私見ではそこまでこだわるポイントではないのだけど。

堀井氏が関わったタイトルとしてクロノトリガーがあり、同様のポリシーを持つ。
パーティのキャラがかなり喋る。その相づちとして主人公クロノは、喋らないまでもポーズをとったりするので、 「不自然なほど喋らない変な子」になっちゃってる。喋らないのも良し悪しだな。
本文のほうでシナリオの説得力の話をしたけど、それさえあれば感情移入は可能。プレイヤーが想像したセリフと 大きな齟齬がなければ喋ってもいいと、個人的には思わなくもない。

さて、ドラクエ3には勇者がひとことだけ喋るシーンがあることが知られている。 堀井氏は「仕方なかった」と言っており、おれも別にこれを問題にしてない。
そんなことよりくだんの商人は、シリーズが自ら課したポリシー「喋らない」に、 この勇者のセリフよりもはるかに抵触してしまっている。 喋らされたセリフの中には、ヒロイズムのかけらもないセリフも含まれたように思う。
この商人。プレイヤーの心理としては「自分のキャラ」であるはずなのだけど、そんなセリフを喋らされてしまう。 色々ひどい。

「D&Dのルールブックに書いてあるでしょうに」

ちょっと唐突な感じがするので補足。
旧D&D(現行D&Dから見れば「クラシックD&D」と呼ばれているもの)の入門セット「ベーシックルール」は 「プレイヤーズルール」「マスターズルール」に分かれている。「マスターズルール」は、ゲームマスター (D&Dではダンジョンマスターと呼ぶべきか)の心得、入門的な記事に多くの紙幅を割いている。
実務的なルールではない精神論を重視する姿勢、すごくいいと思う。

堀井氏はドラクエを、「ファミコンキッズにRPGを紹介するソフト」と位置づけていたはず。 その使命から、RPGと名のつくあらゆるものは研究の対象であったと考える。
原初のRPGであるD&D、そのルールブックは当然精読の対象だっただろう。まして当時、D&Dには日本語訳 があった。(まぁ、特にベーシックルールはいわゆる「超訳」だったけどw)
「シナリオづくりに大事なことも結構書かれてたはずだけど、忘れてしまったのかい?」 という意図で書いた一文でした。

散々けちょんけちょんに言って説得力ないけどw、堀井氏が超人的に勉強熱心だったエピソードは 多くあり、実際には尊敬もしている。だから、これを読んでなかったわけはないという話。
その様なクリエイターにも褒められない創作物があるという話をしているだけで、そこは誤解してほしくない。

「商人の街」の何がいけないのか。ドラゴンクエスト3にみるアンチパターン。

2019.03.15 追記 続きあり。

たまには技術ではない話もする。
このブログは一応技術縛りはあるんだけど、今から書こうとしている文章をどこに上げようか散々迷い、結局ここになった。

ファミコンで神ゲーとされたゲームソフト、ドラゴンクエスト3の話をする。
容赦なくぶったたくので、覚悟して頂きたいw

筆者の経歴

ダンジョンズ&ドラゴンズ(以下、D&D)が日本語訳されてから、テーブルトークRPGはかなりやった。
遊んだのは海外モノばかりで、国産モノは大流行したソードワールドRPG含め、一切手を付けてない。

一番やったのはルーンクエストだが。
持論では、このゲームには(2版,3版共)世界観とゲームシステムの間に齟齬があり、せっかくの魅力的な 背景世界が台無しになっていた感があり。
ゲームシステムと世界観の再解釈を行い、システム丸ごとのハウスルールを作成。これを使って、 セッション数で言えばおそらく100回を越えるキャンペーンシナリオのゲームマスターをやり遂げたことは、 誇れる実績と自負する。

ドラゴンクエストシリーズ(以後、ドラクエ)については、1から7までリアルタイムで、各々最低でも1周。
8はクリアまでやらなかったけど、9はクリア後のクエスト全て、11は裏ボス倒したぐらいまでは一応やってる。

以下、ドラクエやRPGへのリスペクトがない人間が書いてると、誤解されたくはないので。
あえてマウント気味の経歴を載せる。

TRPG

TRPGの経験がなくとも、どんな遊びかは知っている読者を想定している。
ご存知ない方は、先にググって調べて頂きたい。

ところでおれは、そもそも「テーブルトークRPG」という用語を好まない。
D&Dの流行という背景がないところにWizardry等が輸入された、当時の日本の状況は多少は理解するも、 成り立ちから言っても、RPGといえばTRPGのことに決まっているのである。
電源系のRPGこそがCRPGとでも名乗るべきだ。その様に、本心では思っている。

普段、空気を読まなければいけない状況でなければTRPGと呼ばず、あえてRPGと呼ぶ。 しかし本稿は、残念ながらその「空気を読まなければいけない状況」であり、以降やむを得ずTRPGと呼ぶ次第。
読者に誤解されては本末転倒なので。

ドラゴンクエスト3の評価点

先に評価点について述べておかないのは、公平ではないだろう。

「勇者」「魔王」

「ありふれた」世界観などと評価されることもあるが、こんなにもストレートに、このテーマを扱ったものは それほど多くない。むしろ良い意味で「王道」と言っていいだろう。
特に「勇者」は、一般名詞に過ぎなかったワードに意味を与え、むしろ特徴ある世界観に出来ている。

プレイヤーキャラクターを特別な名前で呼ぶ手法は、クトゥルフの呼び声(現「クトゥルフ神話TRPG」) の「探索者」あたりに由来するだろうか。
手前味噌だけど、上で述べたルーンクエストのハウスルールでもパクらせて頂いた。

レベリング、レアアイテム

クリア後にひたすら狩り、育成をするのが、ドラクエ3の真の遊びどころと感じている。 転職、レベリング、レアドロップ等、全てWizardry #1(以下、Wiz1)のパクリだが、 それは別に悪いことじゃない。
世にドラクエ3をパクったゲームが多くあれど、遊べるものは多くなかったのでは? 遊べるものに出来ている時点で、なんらかの創意工夫があったはずと、ここは評価していい。

Wiz1の話が出たから少しだけ。 このゲーム自身もD&Dの完パクリであり、他のゲームにパクられることを非難できる立場にない。
序盤のバランスの悪さはどうだろう。KATINOを撃って即宿屋に帰る序盤のゲームバランスには全く爽快感がない。 D&Dのシステムをそのまま移植することにこだわりすぎた為と分析しているが、 たしかにシステム上はD&Dとそっくりではあるものの、D&Dはそもそも連戦を行うようなゲームではない。
D&Dの本質を理解したうえでパクったわけではないと言え、この点ではドラクエ3のほうがいい仕事を していると思う。

商人の街

では本題に入ろう。

商人の街とは、ドラゴンクエスト3のエピソードのひとつ。
クリアアイテム「イエローオーブ」入手の為のイベントであり、クリアの為に避けて通れない。

このイベントがゲーム史に残るひどさであるという主張が、本稿の主題。
TRPGのマスターであればアンチパターンになるし、TRPGの経験がない方であっても、ゲームのシナリオに 興味を持つ方には何かを考えるきっかけになるかもしれない。

ざっくり挙げる。

  • メンバーが強制離脱させられる。復帰できない。
  • 勇者の仲間だった者が、何故か悪役にされている。
  • プレイヤーの努力による名誉の挽回が不可能。

以下、このそれぞれについて順に述べていく。

メンバーが強制離脱させられる。復帰できない。

このやらかしについては、説明はあまり必要ないだろう。
他ゲームも割とやってる。昔は、希少アイテムを持ったまま離脱する者すらいた。

ドラクエ3より先にWiz1などを経験していれば、通常はルイーダの酒場に待機組がおり、 4人しか育てていないなどということはなかっただろう。
ドラクエにはキャラクターロストのリスクがないし、4人だけ集中して育てるという攻略自体は 本来妥当。それもあって、アリアハンの住人に「キャラクターをたくさん作ったほうがいい」と アドバイスする者がいないのだろう。
かと思えば、シナリオによって強制的に離脱されるという、理不尽なリスクが存在したわけだ。 4人のメンバーのうち1人が商人だったプレイヤーも、全国に少しぐらいはいただろう。 この人、もしかしたら以降の攻略を諦めたかも知れない。

この点はさすがに問題ありと思ったようだ。
後の移植版ではこの商人が復帰するらしいのだけど、だがしかし、正直そんなことには興味ない。 ファミコン版の名誉を挽回することに全くつながってないし、何より以下に述べる もっとデカいやらかしをスルーしたことに、強い疑問を感じる。

勇者の仲間だった者が、何故か悪役にされている。

多くのRPGに、「プレイヤーの英雄願望を叶える目的」という前提があると思う。 勇者の仲間であるこの商人もヒーローの1人として扱うべきと考える。

悪役にされている意味が本気でわからなかった。
小粋な寓話でも書いているつもりだったのか? マスターの独りよがりでシナリオを書いてはいけないんだぜ。D&Dのルールブックに書いてあるでしょうに。

例えば、ドラクエ3の典型的なプレイヤー像として、中学生男子に登場して頂く。
パーティメンバーとして女の子の商人つくり、好きな子の名前をつけ、こっそり遊ぶ。そんなシーンを想像する。 彼はこのゲームにどのような感想を持っただろうか?

思うに、RPGは物語である以前にゲームであり、一般的な物語に許される全ての手法を 使ってよいとは考えてない。
最低でも何かしらの達成感が必要。悲劇に達成感を持たせることは非常に難しいから、 よほどでなければご法度であると考えている。(ここには異論があっていいかも)

プレイヤーの努力による名誉の挽回が不可能。

最後にして最悪のやらかし。仮にもRPGだから、努力で結末を変えられるという前提がある。

ただ、たとえTRPGでも、実際にはそんなことは無理。
ではどうするかというと、シナリオに説得力を持たせるということに尽きる。 説得力のあるシナリオではおのずとプレイヤーの行動が決まり、プレイヤーの自主的な 選択と、ゲームマスターの意図の間で齟齬が発生しない。

さて、「商人の街」はどうだろう。
一方的に悪役にされ、坂道をころげおち、努力の余地なく悪人にされる。 仲間のマスターがこんなシナリオをやったら、おれは叱るでしょうね。 「おまえほんとに、みんなを楽しませようとしてたのかよ?」

冒頭に挙げた評価点がなければ、この一事を以て、ドラクエ3に対して「話にならないクソゲー」という 評価すら可能だろう。 ドラクエ3をパクったゲーム多くあれど、そのどれも、恐らくこんなやらかしをしてない。

「プレイヤーを楽しませる」という前提がある人間が書くシナリオは、 決してこんなグダグダなものにならない。と、主張させて頂く。

最後に

悪口をたくさん書いたが、これらはそのままアンチパターンになっている。 どうすれば改善できるかは自明なので、特に書かない。

「女の子の商人がおっさんに変わる」というよくあるネタも、上に挙げたやらかしから見れば、 重箱の隅に過ぎない。

2019.03.15 追記 一部修正。てにおは等表記上のものだけ。

RubyのHashで、再帰的なマージ

Rubyの Hash#merge をよく使うけど、ハッシュの孫要素から先の面倒を見るものが、 モロヘイヤで必要になった。
何を言っているのか、ちょっと伝わらなそうなので。以下、実際のテストコード。

何をしたいのかは伝わると思う。

このような事例はきっとあるのでググってみたが、書かれた時代が古くて若干冗長に見えるものが多く、 結局、参考にしたものの大半を手直しした。(こーゆうのも「再発明」っていわれちゃう?)

試行錯誤の結果、たどり着いたのがこれ。

こちらも細かいところは説明しませんw。
かように、正解はシンプルなものになることが多いですね、という話。

  • 最後の dest.compact (要素の中からnilのものを削除)はお好みに合わせて。 再帰的にnilの要素を削除したかったから、今回はここに差し込む以外になかった。

  • 3項演算子が苦手で滅多に使わなかったけど、今回のような十分にスコープが小さな 時は積極的に使うとすっきりしますね。(ただ、複数行にまたがるときにあえて ifブロックではなく3項演算子を使うとよい理由は、まだ理解できないです)

  • 破壊メソッドは、今回は特に必要なかったから書きませんでした。 もし必要でも、ちょっと直すだけで書けるはず。

Hashクラスにモンキーパッチをあてるのはやめましたw
でも、 Hash#merge の動作は本来これであって欲しい。

FreeBSDにGROWIをセットアップする(続き)

FreeBSDにGROWIをセットアップするの続き。
その後、godの設定ファイルを何箇所か修正した。

変更点は、以下の通り。

ファイルアップロード

環境変数 FILE_UPLOADlocal に。
ファイルアップロードをローカルファイルシステムに行う設定。

npm “uws” の利用をやめる

環境変数 EIO_WS_ENGINEws に。
これはGROWIの設定ではなく、依存するnpm engine.ioに 対する設定。

停止コマンド

pkill -f /path/to/growi に変更。
pkill コマンドの -f オプションが肝。コマンドラインオプションを含めたコマンド全体から、 停止すべきプロセスを検索してくれる。

Mastodon 2.7

Mastodon 2.7.0がリリースされた。
rc1が出たときに修正が必要な箇所を2ヶ所見つけたが、リリースまでに解決しなかった。
同様の現象に悩まされている人の為に、記録を残す。

しばらく記事の間が空いたが、空気を読まずに進行する。

Web UIのタイムライン画面が表示できない

Web UIのフロントにパッチを当てる羽目になった。

FreeBSD 12.xで、ストリーミングAPIが動作しない

これは実はMastodon 2.7とは無関係で、FreeBSD 12の話。
ストリーミングAPIではuwsが使用されているが、このnpm は実際にはとっくにEOL。
npmに含まれているバイナリのコードが、FreeBSD 12.xにて遂に動作しなくなった。

本家Issueに載ってたパッチを適用し、解決。
uwsではなくwsを使う実装になってる。

FreeBSDにGROWIをセットアップする

ある目的で、Mastodonと連携させる為のGROWIが必要になった。

対象はFreeBSD。
GROWIにはportsもパッケージもない為、以下、gitから手作業で導入していく 大まかな流れ。不明点があれば、適宜ググって頂ければと。

動作環境

  • nginx
  • Node.js 8.x以上
  • Ruby 2.4.x以上

が適切にセットアップされているものとする。

リポジトリのクローン

GROWIの実行ユーザーを仮に growi として。

1
2
3
4
5
cd ~growi
git clone https://github.com/weseek/growi.git
cd growi
git checkout release
yarn

release ブランチが運用に適すると判断した。

MongoDBの導入

これ以降の操作は、すべてroot権限で行う。
GROWIのストレージとなる、MongoDBを導入。

1
2
3
4
pkg install mongodb36
pkg install mongodb36-tools
sysrc mongod_enable=YES
sysrc mongod_flags="--setParameter=disabledSecureAllocatorDomains=*"

3.6系は、今日時点での最新安定版。
toolsパッケージにはバックアップの為の mongodump コマンド等が入っており、 事実上必須。
mongod_flags を設定しているのは、ログをsyslogに出したい為で、 そうでなければデフォルトのままでも可。

/usr/local/etc/mongodb.conf

これもログの出力先をsyslogに変更する為の対応で、ほかはデフォルト。
不要ならばデフォルトのままの mongodb.conf でも可。

最後に、MongoDBのデーモンを起動。

1
service mongod restart

Godの導入

Godを未導入の環境に新規導入するものとして。
既に導入されている場合は、うまく辻褄をあわせていただきたい。

対象システムであるGROWIはNode.js製なのだから、 本来であればPM2 あたりを使うべきであるというご指摘はごもっとも。

1
2
gem install god
mkdir /usr/local/etc/god

/usr/local/etc/local.god

以下、設定例 。(ファイル名はたぶん何でもいい)
めちゃ強引だが、とくに反省はしないw

/usr/local/etc/god/growi.god

GROWIの実行ユーザー growi のホームディレクトリが、 先ほど同様 /home/growi であるとして。

ポートをデフォルトの3000から3012にずらしているのは、 Mastodonインスタンスと同じホストへの導入を想定している為。 3012に深い意味はない。

停止コマンドが汚すぎる。ちょっと反省してる…。
機会あったら直したい。

追記
エントリ「FreeBSDにGROWIをセットアップする(続き) 」にて、godファイルを何箇所か修正。

/usr/local/etc/rc.d/god

Godを自動起動するには、以下のようなファイルを作成。

FreeBSDのrc.dスクリプト、実は細かいところはよく知らない。
見様見真似で上記のように書いて、一応動いてはいるけど。

ここまでできたら、Godを起動。

1
service god restart

リバースプロキシ

以下、Let’s Encryptの証明書を利用する、nginxの設定例。
詳細な説明は割愛。設定後、nginxの再起動。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 443 ssl;
server_name growi.example.com;

ssl_protocols TLSv1.2;
ssl_ciphers EECDH+AESGCM:EECDH+AES;
ssl_ecdh_curve prime256v1;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_certificate /usr/local/etc/letsencrypt/live/growi.example.com/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/growi.example.com/privkey.pem;
add_header Strict-Transport-Security max-age=63072000;

location / {
proxy_pass http://localhost:3012;
}
}

監視

以下の項目を監視している。

  • https://growi.example.com/ 上の適当なページが200を返すこと。
  • TCPのポート27017が開いていること。

バックアップ

以下、実行例。

1
mongodump --port 27017 --out /tmp/destdir

このあと /tmp/destdir をアーカイブして、他のホストにrsyncする手順等 の自動化を行う。

以上。

Xubuntu 18.10

リリース直前だが空気を読まず、Xubuntu 18.10をMacBook Airに適用していくのである。
MacBook AirとXubuntuという組み合わせにニーズなどあるのか?などとは考えないことにする。

18.04の時の記事はこちら→ Xubuntu 18.04

基本的な更新の手順

1
sudo do-release-upgrade -d

-d は開発版を含めるという意味。
今回のように、リリース前のものでも適用したい場合はこのオプションが必要。

トラックパッドの2本指スクロールの向きを上下逆に

毎度のことだが、過去記事と 同じ手順で再設定可能。

結論から言うと、今回は xinput set-prop 12 289 -200 -200 だった。この289が毎回変わるのである。

以下、調べる手順を説明していく。
xinput list を実行して、トラックパッドのデバイスIDを調べるところまではよいとして。

1
2
3
4
5
6
7
8
9
10
11
12
% xinput list
⎡ Virtual core pointer id=2 [master pointer (3)]
⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)]
⎜ ↳ bcm5974 id=12 [slave pointer (2)]
⎣ Virtual core keyboard id=3 [master keyboard (2)]
↳ Virtual core XTEST keyboard id=5 [slave keyboard (3)]
↳ Power Button id=6 [slave keyboard (3)]
↳ Video Bus id=7 [slave keyboard (3)]
↳ Power Button id=8 [slave keyboard (3)]
↳ Sleep Button id=9 [slave keyboard (3)]
↳ FaceTime HD Camera (Built-in): id=10 [slave keyboard (3)]
↳ Apple Inc. Apple Internal Keyboard / Trackpad id=11 [slave keyboard (3)]

一見11が正解に見えるが、実際には12である!
「マウスとタッチパッド」の設定を開いてデバイス名を確認するのがよいと思う。

余談だけど、この画面には「スクロール方向を反転する」というチェックボックスがある。
これを使えば、一見面倒な手順を踏まず全て解決に見えるけど、実際には使ってはいけない地雷なので要注意。 この設定項目、アプリによっては効かなかったりする。

閑話休題。トラックパッドのデバイスIDが12とわかったら、そのあとは

1
xinput list-props 12 | grep "Synaptics Scrolling Distance"

を実行して、プロパティIDを知ることが出来る。

ウチの環境では、セッションと起動→自動開始アプリケーションに以下のコマンドを書き加えて終了。

1
xinput set-prop 12 289 -200 -200

その他

使い始めたばかりなので、まだわかっていないことが多いが。

  • Guakeはテーマの切り替えが必要だった。一度設定すれば以降は正常動作。
  • Node.jsのリポジトリ https://deb.nodesource.com/node_8.x/ は、今日時点で18.10に未対応。

個人的にはこのあと、GPD Pocketにも18.10を適用の予定。

FreeBSDでUserminをセットアップ(リバースプロキシを利用)

以前、Userminに関する記事を書いたが、その後別のサーバにセットアップした。
20000/tcpを直接晒すのではなく、リバースプロキシを通して普通に443で公開したかったので、手順を修正。

以下、 usermin.example.com にUserminをセットアップする例。
全てroot権限で行う。

パッケージインストール

1
2
pkg install usermin
sysrc usermin_enable="YES"

セットアップスクリプト

1
2
cd /usr/local/lib/usermin
./setup.sh

対話形式で設問に回答すると、 /usr/local/etc/usermin が作成される。

設定ファイルを編集

/usr/local/etc/usermin/miniserv.conf に、以下の項目の更新・追記が必要。

1
2
3
4
log=0
syslog=1
denyusers=root
allow=127.0.0.1 localhost
  • rootアカウントの利用は許可するべきではない。
  • localhostのみ許可しているのは、リバースプロキシを通じた利用だから。
  • 通常ログを止めてsyslogにしているのは、単なる好みw お好みに合わせて。

/usr/local/etc/usermin/config にも要追記。

1
referers=localhost usermin.example.com

usermin.example.com は実際には、ユーザーから見たドメイン名。
証明書のコモンネームと合わせておけば間違いないと思う。

許可するモジュールの選択

そのまま /usr/local/etc/usermin で、以下実行。

1
echo 'user: changepass' > webmin.acl
  • パスワード変更機能だけ必要。以前の記事では procmail も入れてたが、 結局使わなかったので削除。

Apacheにリバースプロキシを設定

1
2
3
4
5
6
7
8
<VirtualHost *:443>
ServerName usermin.example.com
SSLEngine on
SSLCertificateFile /usr/local/etc/letsencrypt/live/usermin.example.com/fullchain.pem
SSLCertificateKeyFile /usr/local/etc/letsencrypt/live/usermin.example.com/privkey.pem
ProxyPass / http://localhost:20000/
ProxyPassReverse / http://localhost:20000/
</VirtualHost>

certbot(Let’s Encrypt)の使い方については省略。
nginxの場合も、似たような感じで。

変更を反映

1
2
service usermin restart
service apache24 restart

ZFSスナップショットを削除する

ZFSで、スナップショットの削除を行う為には、特別な権限付与が必要。

以下、postgresユーザーに対して、 zroot/postgres におけるスナップショットの作成と削除を 許可する設定。

1
zfs allow postgres snapshot,destroy,mount zroot/postgres

destroy権限の他にmount権限も必要なのがハマりどころ。

PostgreSQLのデータディレクトリに対し、ZFSのスナップショットを撮る

Mastodonの運用…に限らないけど、データベースのバックアップを取る必要がある。 pg_dump を行って rsync なりでリモートに送るのが簡単、実際その方法で1時間ごとに バックアップを取っていた。

Mastodonのデータベースを1年も運用すると、お一人様インスタンスでも1GB超のダンプファイルになる。
この大きさになると、1時間ごとに実行するのは困難。 1時間ごとに pg_dump を実行するお手軽バックアップ、1年にして破綻。
頻度を1日ごとに落とした。

個人的に、正規のバックアップは論理バックアップであるべきと考えており、この1日ごとの pg_dump は 辞められない。
一方で、今まで通りの1時間粒度のバックアップも、やはり続けたいと思っている。

それを実現するアイディアが、ZFSのスナップショット。
地雷運用と言われようとあえてFreeBSDを使い続けてきたが、その真価がついに。

以下の手順、全てroot権限にて。

マウントポイント

FreeBSDで通常のセットアップを行うと、PostgreSQLのデータディレクトリは /var/db/postgres の配下に 置かれるだろう。
ZFSのスナップショットはマウントポイントの単位で行われる為、このディレクトリを独立したマウントポイントに 分ける必要がある。

以下を実行すると、 /var/db/postgres既にあるファイルが削除される。
必要なら、それこそ pg_dump 等でバックアップしてから!

1
2
3
zfs create zroot/postgres
zfs set mountpoint=/var/db/postgres zroot/postgres
zfs set snapdir=visible zroot/postgres

zfstoolsを導入

スナップショットの保存、古いスナップショットの破棄、DBの一時停止等をひと通りやってくれる gemがある。
Mastodonインスタンスが置かれたサーバにRubyがインストールされてないはずはなく、 zfstoolsは以下の手順で導入可能。

1
2
gem install zfstools
zfs set com.sun:auto-snapshot=postgresql zroot/postgres

この手順で、 /usr/local/bin/zfs-auto-snapshot がインストールされる。

実行時ユーザーにsnapshot権限を付与

zfs-auto-snapshot は、OSのpostgresユーザーで実行するのが手っ取り早い。
このユーザーにはZFSのスナップショット作成の権限が与えられてない為、対応が必要。

1
zfs allow postgres snapshot zroot/postgres

追記(2018-09-24)
訂正あり。削除権限の付与も必要。 ZFSスナップショットを削除する

wal_levelを設定

PostgreSQL側で、 wal_level を設定する必要がある。
/var/db/postgres/data96/postgresql.conf 等に以下追記。 (data96のところ、PostgreSQLのバージョンによって変わるから適用に読み替えて)

1
wal_level = replica

反映させる為には、恐らく再起動が必要。

1
service postgresql restart

スナップショットを撮る!

ここまでで準備終わり。以下のコマンドでスナップショットが撮れる。

1
sudo -u postgres /usr/local/bin/zfs-auto-snapshot hourly 24

実際の実行はcron等で。

おまけ

スナップショットの一覧を表示。

1
zfs list -t snapshot

誤って作りまくったスナップショットの破棄。

1
zfs destroy スナップショット名