⚠️ この記事はアイデア出しを筆者が行い、文章の生成に生成AIを使用しています。内容のレビューは筆者が行っています。

読んだもの

Alibaba Cloudの阿里云云原生チームによる、RocketMQの高可用性の記事(出典は末尾)。生成AIと一緒に読んで整理したメモで、記事本文は自分でも開いて確認した。細部(実測値や予約種別など)は追えていないので、ここでは設計の発想だけ書く。

そもそも何が問題か

Active/Standbyで、Broker AからBroker Bへ切り替えたい。ただ、Aが応答しないからといって、Aが止まっているとは限らない。ネットワーク分断やI/Oハングで、AもBも「自分がActiveだ」と思って同じデータを書くと、Split-Brainでデータが壊れる。

「BがActiveになったとAに通知すればいい」と最初は思う。でも通知が届かないから困っているのであって、リースや分散ロックを足しても、失効を認識できない旧ノードは残る。

旧ノードが交代を認識することに依存する設計は、障害時にいちばん当てにならない。

NVMe PRでどう解くか

NVMe Persistent Reservation(PR)は、ストレージ側で「書き込める保持者」を決める仕組み。保持者以外の書き込みは、デバイスが拒否する。

記事の構成では、Multi-Attachで新旧ノードから同じディスクを見えるようにしておき、障害時に新ノードがPRを奪う。つまり、

  • Multi-Attach:同じディスクが見える(書き手の制限はしない)
  • NVMe PR:書ける人を一人に絞る

という役割分担になる。旧Brokerのプロセスが生きていても、交代を知らなくても、書き込みはストレージ側で止まる。

流れを図にするとこうなる(理解用の簡略図で、実際の実装手順そのままではない)。

【通常時】

  Broker A (Master)           Broker B (Shadow)
      │ WRITE可能                 │ データ領域は未マウント
      │                           │ leaseを読むだけ
      ▼                           ▼
  ┌─────────────────────────────────────┐
  │ 共有ブロックストレージ              │
  │ NVMe PR: Aが書き込み保持者          │
  └─────────────────────────────────────┘


【障害発生】

  Broker A                    Broker B
      │ 応答なし                  │ leaseが進まないことを検知
      │ ※実は生きているかも       │
      │ ※交代を知らない           ▼
      │                       PRを奪う(preempt)
      ▼                           ▼
  ┌─────────────────────────────────────┐
  │ 共有ブロックストレージ              │
  │ A: WRITE拒否 ✕    B: WRITE許可 ○    │
  └─────────────────────────────────────┘


【引き継ぎ後】

  Broker A                    Broker B
      │ 旧保持者                  │ 新保持者
      │ WRITEしても拒否される     │ キャッシュ処理 → マウント
      │                           │ → journal/WAL回復
      │                           ▼
      │                       サービス再開

Aは交代を知らないまま動いていてもいい。止めているのはAの心がけではなく、ストレージだ。

交代を確実に伝える仕組みではなく、交代を知らない旧Brokerでもデータを壊せなくする仕組み。 ここが一番おもしろかった。通信の問題を、通信ではなくストレージのアクセス制御に移して消している。

FencingとRecoveryは別もの

「書き込みを止めたなら、それで整合性は保てるのでは?」と最初は思った。でも守っている時間軸が違う。

守るもの 担当
Fencing 交代後、旧所有者にこれ以上壊させない NVMe PR(ストレージ)
Recovery 交代時点で残っている未完了の状態を整える ファイルシステムのjournal回復、RocketMQのログ回復

Fencing:これ以上、旧所有者にデータを壊させない。 Recovery:すでに残っている状態を整理する。

ストレージは「どのメッセージが正しいか」を判断しない。そこはファイルシステムとアプリの仕事。記事ではキャッシュ破棄もこの流れに入っていて、古い状態を新Brokerが使わないための手当てとして位置づけられている。

ついでに:ShadowはSlaveではない

記事に出てくるShadow Brokerは、Slave Brokerとは違う。Slaveは業務データを複製して持つが、Shadowは平時はデータ領域をマウントせず、共有ディスク上のleaseを見張るだけ。故障時に元のディスクの書き込み権を取って引き継ぐ。

なお「複製しない」はRocketMQのサービス層の話で、ディスク自体の冗長性が不要になるわけではない。共有ディスクが壊れた場合は、この方式の守備範囲外。

まとめ

  • 「古いノードに新しい状態を知らせる」のではなく、「古い状態を信じたまま動いても、共有データを変更できなくする」
  • 役割分担が明確:Fencing=ストレージ、Recovery=ファイルシステム/アプリ
  • Fencingだけで全部解決するわけではない(回復処理も、共有ストレージの可用性も別途必要)

通知の成功を期待するのではなく、通知が失敗しても壊れないようにする。それがFencingの核心だと思う。

参考