Featured image of post コードドクター【Command】不可逆性細胞無秩序書き換え症候群〜段階的デプロイと安全なロールバック〜

コードドクター【Command】不可逆性細胞無秩序書き換え症候群〜段階的デプロイと安全なロールバック〜

深夜3時。冷や汗でシャツが背中に張り付いているのがわかった。

俺は抱えていたノートPCごと、重厚な鉄の扉を力任せに引いた。 「コード診療所」とだけ書かれたプレート。中は薄暗く、O’Reilly本のタワーが壁のようにそびえ立っている。その奥で、トリプルディスプレイの青白い光に照らされた男の背中が見えた。

「た、頼む! 助けてくれ!」

ドクターは一瞥もくれない。ただ無言で HHKB を叩き続けている。 代わりに、白衣を着た女性——助手のナナコさんが、穏やかな笑みを浮かべて近づいてきた。

「大丈夫ですよ、ここはコード診療所です。まずは落ち着いて、患部を見せていただけますか?」

俺は震える手でノートPCをデスクに置いた。「本番環境のDBを……いや、まだギリギリ壊してないはずなんだが、途中で何が起きてるか分からなくなって……」

俺はSREとして、段階的なデプロイを自動化するスクリプトをPerlで書き上げたばかりだった。だが、それが地獄の始まりだったのだ。

触診:不可逆な破壊の連鎖

ドクターが冷たい目で画面を覗き込む。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 患部:DeployTool.pm
sub deploy ($self) {
    eval {
        $self->_stop_service();
        $self->_backup_db();
        $self->_update_app();
        $self->_start_service();
    };
    if ($@) {
        # エラーが起きたらどうする?
        # 「頼む、途中でコケないでくれ」と祈るしかない。
        push $self->{log}->@*, "ERROR: $@";
        die "Deploy failed! Manual rollback required.\n";
    }
}

「……不可逆細胞、無秩序増殖」

ドクターの低い声が響く。

「え?」俺は聞き返した。 「システムの状態を直接書き換える処理が垂れ流しになっており、元の状態に戻すための自己修復機能が欠落しているとおっしゃっています」とナナコさんが翻訳する。

「当たり前だろ! インフラなんだから、コマンド叩いたら状態が変わるのは当然だ! 途中でネットワークエラーかなんかでコケたら、気合いで手作業で戻すしかないんだよ! オブジェクト指向なんていう回りくどい黒魔術を使う暇は俺にはねえんだ!」

俺の悲痛な叫びに対し、ドクターは短く言い放った。

「カプセル化。そして、履歴」

手術:命令のオブジェクト化

ドクターは俺のPCを引き寄せると、猛烈なスピードでキーボードを叩き始めた。

「待って、何をしてるんだ? ただのサブルーチン呼び出しを、わざわざ面倒なクラスに分けてるのか?」

「『Commandパターン』の適用手術です」とナナコさんが微笑む。「命令(コマンド)そのものを、ひとつのカプセル(オブジェクト)に閉じ込めるんです。そうすることで、命令を配列に入れて持ち運んだり、後から取り消し(Undo)したりできるようになりますから」

「命令を取り消す……?」

ドクターが書き上げたのは、すべてのコマンドの設計図となるインターフェースだった。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 処方:Command::Role.pm
package Command::Role;
use v5.36;

sub new ($class, $server) {
    bless { server => $server }, $class;
}

sub execute ($self) {
    die "execute() must be implemented by subclass";
}

sub undo ($self) {
    die "undo() must be implemented by subclass";
}
1;

「実行(execute)と、元に戻す(undo)。この2つの機能を持たせるんです」とナナコさんが解説する。

ドクターは次々に、具体的な「操作」をオブジェクト化していった。たとえば、アプリケーションの更新処理だ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 処方:Command::UpdateApp.pm
package Command::UpdateApp;
use v5.36;
use parent 'Command::Role';

sub execute ($self) {
    $self->{server}->log("Updating application...");
    $self->{server}{state}{app} = 'new_version';
}

sub undo ($self) {
    $self->{server}->log("UNDO: Reverting application...");
    $self->{server}{state}{app} = 'old_version';
}
1;

集中治療:安全なロールバック機構

「操作をモノにするのは分かった。でも、それでどうやって途中でコケたデプロイを戻すんだよ?」俺はまだ半信半疑だった。

ドクターの指先が止まり、カッ、とエンターキーが叩かれた。最後のピース、『Invoker(呼び出し元)』となるデプロイジョブのクラスだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 処方:DeployJob.pm
package DeployJob;
use v5.36;

sub new ($class) {
    bless { commands => [], history => [] }, $class;
}

sub add_command ($self, $cmd) {
    push $self->{commands}->@*, $cmd;
}

sub execute ($self) {
    eval {
        for my $cmd ($self->{commands}->@*) {
            $cmd->execute();
            # 成功したコマンドだけを履歴(スタック)に積む
            push $self->{history}->@*, $cmd;
        }
    };
    if ($@) {
        my $err = $@;
        # エラー発生時は、履歴を逆順に取り出しながら undo を呼ぶ
        $self->undo();
        die "Deploy failed, rolled back securely. Reason: $err";
    }
}

sub undo ($self) {
    # 履歴スタックから後入れ先出し(LIFO)で取り出す
    while (my $cmd = pop $self->{history}->@*) {
        $cmd->undo();
    }
}
1;

俺は息を呑んだ。

「……『操作』をモノとして配列に入れておけば……後から取り出して逆再生できるのか……!?」

「その通りです」とナナコさんが頷く。「手術台の上で使ったメスや鉗子を、順番に元のトレイに戻していくようなものです。これでもう、腹の中にガーゼを置き忘れるような事故は起きません」

俺は今まで、一体何時間かけて手作業でログをたどり、怯えながらコマンドを手打ちでロールバックしていたというんだ。

術後経過:逆再生される履歴

ロールバック成功を確認するモニターの前で安堵する患者とドクター、ナナコ

モック環境でのテスト実行が始まった。 わざと途中の UpdateApp でネットワークエラーを発生させる。

デプロイは失敗した。だが、システムは沈黙しなかった。

1
2
3
4
5
Stopping service...
Backing up DB...
Updating application... (エラー発生!)
UNDO: Restoring DB...
UNDO: Starting service...

一度停止したサービスが自動的に立ち上がり、DBのバックアップ状態がきれいに解除され、完全に「元の正常な状態」へと戻っていた。

「……完璧だ」 俺は安堵と、オブジェクト指向に対する畏敬の念で震えていた。

その時、ドクターが突然立ち上がり、俺の肩のあたりに向かってスッと長い腕を伸ばしてきた。

(ドクター……。そうか、『もう一人でインフラの全責任を抱え込むな』と、俺の肩を……なんて温かいヤツだ……)

俺は感動で目を潤ませ、ドクターの手を握り返そうとした。

「……リンク・アップ」

ドクターは俺の背後にあるルーターの、抜けかけていたLANケーブルをカチッと挿し直しただけだった。

「……あ、いや……」

ナナコさんは無言でルーターのアクセスランプの点灯を確認すると、静かにお茶を片付け始めた。いたたまれない沈黙が診察室を包む。

俺は真っ赤な顔でノートPCを鞄に突っ込んだ。 「く、黒魔術、悪くねえな! さっそく明日のデプロイツールを書き直してやるぜ!」

照れ隠しで叫ぶと、俺は外開きの重厚な扉を勢いよく押して、夜明けの街へと飛び出した。


処方箋まとめ

症状適用すべき経過観察
取り消し(Undo)機能が必要
操作の履歴を記録・キューイングしたい
状態変更を伴う複雑なトランザクション
単純な情報の取得(読み取り専用)

治療のステップ

  1. Command(インターフェース)の定義: executeundo メソッドを持つ役割を作成する。
  2. ConcreteCommand(具体的な命令)の実装: 操作の数だけサブクラスを作り、処理内容と復元手順をカプセル化する。
  3. Invoker(呼び出し元)の作成: コマンドを受け取り、順に実行し、成功したものをスタック(履歴)に積む。
  4. ロールバックの自動化: エラーを検知したら、スタックから LIFO(後入れ先出し)でコマンドを取り出し、undo を実行する。

助手より

インフラの操作は一度実行すると元に戻せないことが多く、そのプレッシャーは計り知れませんよね。コマンドパターンを使えば、「命令の実行」と「実行のタイミング」を切り離すことができます。これでもう、深夜の障害対応で冷や汗を流すことも減るはずです。新しいデプロイツールが、安定稼働の助けとなることをお祈りしていますね。

——ナナコ

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。