Featured image of post コードドクター【Observer】ポーリング過呼吸〜愛のPush型依存関係逆転〜

コードドクター【Observer】ポーリング過呼吸〜愛のPush型依存関係逆転〜

来院

「先生、助けてください! 私の愛が……大切な推しのサーバーを落としかけているんです!」

私はコード診療所の重厚な鉄の扉を勢いよく引いて飛び込んだ。息を切らし、抱えたノートPCを震える手で差し出す。

私の名前は権藤鉄朗、42歳。インフラとバックエンド一筋で15年、サーバーの死活監視とCronジョブの安定運用に魂を捧げてきた堅物エンジニアだ。しかし最近、人生の歯車が大きく狂い始めた。地下アイドル「ピュア・バイナリ」の魅力にドハマりしてしまったのだ。

彼女たちのゲリラライブ告知や限定グッズの販売開始を、1秒でも早く、誰よりも早く検知したい。その純粋な熱意は、いつしか私のインフラスキルを暴走させ、自作の自動通知システムへと結実した。だが、それが悲劇の始まりだった。

薄暗い室内。積まれたO’Reilly本とマザーボードの山を越えた先のデスクで、黒いシャツ姿の男——コードドクターは、トリプルディスプレイから目を離すことなく静かに呟いた。

「愛のDDoS攻撃。……愚かだ」

「DDoSじゃありません! これは純粋な熱意のCronジョブです!」

私は必死に弁明したが、声は裏返っていた。

触診

私はノートPCを開き、稼働中の自作スクリプトをドクターに見せた。

そのとき、ナナコさんが新しいボールペンをドクターの横に置いた。「インクが切れかけていましたので」 ドクターは無言で画面から顔を上げ、ペンとナナコさんを交互に見つめた後、何故か居住まいを正して深く一つ頷いた。彼女からの事務的な気遣いを、自分への特別な好意だとでも受け取ったのだろうか。ナナコさんは全く意に介する様子もなく、静かに私の方へ向き直った。私はこの奇妙な空気にただ戸惑うしかなかった。

 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
package SubjectPoller;
use v5.36;
use utf8;
use experimental 'signatures';

# カタブツインフラエンジニアが書いた、愚直なポーリングとハードコード通知の塊
sub new ($class, $target_api) {
    bless {
        target_api => $target_api,
        last_status => 'normal',
        request_count => 0,
        notifications => [],
    }, $class;
}

sub check_once ($self) {
    # 相手のインフラ(推しのサーバー)に負荷をかける(Pull型)
    $self->{request_count}++;
    my $current_status = $self->{target_api}->fetch_status();
    
    # 状態が変わっていたら、各所へ通知(ハードコードでガチガチの密結合)
    if ($current_status ne $self->{last_status}) {
        # 通知先が増えるたびにここを修正・リリースしなきゃいけない
        push $self->{notifications}->@*, "[LINE連携] ピュア・バイナリ: $current_status";
        push $self->{notifications}->@*, "[Discord Bot] \@everyone 状態更新: $current_status";
        push $self->{notifications}->@*, "[パトランプ] 回転開始! ($current_status)";
        
        $self->{last_status} = $current_status;
    }
}

ドクターは画面を一瞥し、渡されたばかりのペンでモニタの縁を軽く叩いた。

「執着性DDoS型・愛着障害。……典型的なPull型の悲劇」

男が吐き捨てた痛烈な言葉に、私は息を呑んだ。その傍らに立っていた助手のナナコさんが、穏やかな微笑みを浮かべて口を開いた。

「権藤様。相手の都合を考えず、1秒ごとに『ライブある?』『グッズ出た?』とドアをノックし続ける設計です。お相手のインフラへの過剰な確認行為は、愛情ではなくただの重たい負荷(ストーカー行為)になりかねません。お相手のサーバーが心配ではないですか?」

「ス、ストーカー!? いや、私はただ、一番に知りたいだけで……!」

私は顔を真っ赤にして崩れ落ちた。インフラエンジニアとして、不要なトラフィックで他人のサーバーに負荷をかけることの罪深さは痛いほどわかっている。だが「1秒でも早く推しの情報を得たい」という欲望が、理性を凌駕してしまったのだ。おまけに、通知先が増えるたびにスクリプト本体のif文へハードコードを追加する始末。これでは運用も立ち行かない。

「先生、どうすればいいんでしょうか。私はただ、推しと繋がっていたいだけなのに……」

ドクターは無言で私のPCを引き寄せ、キーボードに手を乗せた。

「愛は、信じて待て」

外科手術

ドクターの指先が凄まじい速度で踊り出し、私のスパゲッティコードが鮮やかに切り刻まれていく。

「見に行くのではない。……待ち受けろ」

短い言葉と共に、ドクターはObserver(観察者)パターンの概念をコードに練り込んでいった。

 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
36
37
38
39
40
41
42
43
44
package Subject::Idol;
use v5.36;
use utf8;
use experimental 'signatures';

# ドクターが提示した、健全なPush型の Subject(配信元)
sub new ($class, $name) {
    bless {
        name => $name,
        status => 'normal',
        observers => [], # サブスクライバー(ファン)のリスト
    }, $class;
}

# ファンをリストに登録する(Webhookの登録など)
sub attach ($self, $observer) {
    push $self->{observers}->@*, $observer;
}

# ファンをリストから外す(推し活引退)
sub detach ($self, $observer) {
    $self->{observers} = [ grep { $_ ne $observer } $self->{observers}->@* ];
}

# 状態が変わった時にだけ実行される管理者側の操作
sub set_status ($self, $new_status) {
    $self->{status} = $new_status;
    
    # 状態が変わったことを、自ら全員に通知する(Push型)
    $self->notify();
}

# 状態の取得
sub get_name ($self) { return $self->{name}; }
sub get_status ($self) { return $self->{status}; }

# リストに登録されたファン全てに状態を通知する
sub notify ($self) {
    foreach my $observer ($self->{observers}->@*) {
        $observer->update($self);
    }
}

1;

ナナコさんが、その見とれるようなコードの構造を静かに解説してくれた。

「状態の提供元を『Subject(対象)』、監視する側を『Observer(観察者)』として分離する手術です。権藤様から1秒ごとに聞きに行く(Pull)のではなく、アイドル側(Subject)に権藤様の通知先を登録(attach)しておき、何か変更があった時だけ向こうから知らせてもらう(Push)設計ですね」

さらに、ドクターは私(ファン)側のコードも美しく書き直した。

 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
package Observer::Fan;
use v5.36;
use utf8;
use experimental 'signatures';

# 通知を受け取る側(Observer)。
# Subject(アイドル側)から update メソッドが呼ばれることを期待している。
sub new ($class, $platform, $username) {
    bless {
        platform => $platform,
        username => $username,
        notifications => [],
    }, $class;
}

# Subject側から状態変化時にPushで叩かれる(Webhookハンドラのようなもの)
sub update ($self, $subject) {
    my $msg = sprintf(
        "[%s/%s] %s の状態が %s になりました!全裸待機します!",
        $self->{platform},
        $self->{username},
        $subject->get_name(),
        $subject->get_status()
    );
    push $self->{notifications}->@*, $msg;
}

1;

「なんという……美しい依存関係の逆転……!」

これなら推しのサーバーを1秒ごとに叩く必要はない。アイドル側で状態が変わった瞬間に、登録された全ての通知先へ一斉にupdateが呼ばれるのだ。通知先(LINEやDiscord)を増やしたければ、Idol->attach(新しいFan)とするだけでよく、アイドル側の本体コードに一切手を触れる必要もない。

術後経過

手術後のコードを眺めていた私は、ふと重大な事実に気がつき、ハッと息を呑んだ。

「ちょっと待ってください。この $idol->attach($gondo_line) というコード……私が推し(Subject)の内部リストに、『私自身(Observer)』を直接登録するということですよね?」

「ええ。実システムとしては、WebhookのURLや通知先エンドポイントを登録する形になりますね」とナナコさん。

「つまり……推しのシステムのインメモリ上に、私の存在が公式のファンとして保持され、推しから私へ直接(Pushで)連絡が来るッ……!? これはもう、実質的な交際宣言と言っても過言ではないのでは!?」

私が歓喜に震えながらドクターを見ると、彼は無言でペンを取り、カルテに『重度の妄想癖を併発』と短く書き足していた。

「……あくまで、パブリッシャーとサブスクライバーという、非同期のドライな関係かと存じます。お大事に」

ナナコさんの呆れたような、しかし優しい声に見送られながら、私はPCを抱え直した。

「推しからの直接通知(Push)、全身全霊で全裸待機します!」

私は診療所の鉄の扉を元気よく(押して)飛び出した。正しい設計と、少しばかり歪んだ愛情を胸に抱き、清々しい足取りで自分のサーバー構築へと戻っていった。


処方箋まとめ

症状適用すべき経過観察
状態変化を知るために、一定間隔で無限ループ(ポーリング)している
状態遷移のたびに、密結合された複数のクラスへ通知(メソッド呼び出し)をベタ書きしている
特定のイベント発生時に、動的に通知先を増減させたい
1対1の単純なやり取りで、相手側のレスポンスを待つだけで済む場合

治療のステップ

  1. 状態の変化を発信する側を Subject(対象)、受け取る側を Observer(観察者)として役割を明確に分離する。
  2. Subject 側に、Observer を登録・解除するメソッド(attach / detach)を用意し、内部にリストとして保持する。
  3. Subject の状態が変化したタイミングで、リスト内の全 Observer に対して一斉に通知メソッド(update)を呼び出す(通知のPush化)。

助手より

権藤様、今回は無事に「愛の暴走」を止めることができて何よりです。 システムも人間関係も、相手の都合を考えずに距離を詰めすぎる(密結合・過剰ポーリング)と、思わぬ負荷をかけてしまうものです。相手を信じて待ち、必要な時だけ連絡を受け取る(疎結合・Push型通知)という距離感こそが、長く健全な関係を保つ秘訣ですね。 これからもインフラ監視の専門知識を活かして、素晴らしい推し活ライフをお送りください。 ただ、本当に全裸で待機して風邪など引かれませんよう。

——ナナコ

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