冗長化 (HA) 設定
プラットフォーム 2セット (A · B) を並列に配置し、片方が完全に停止しても 運用データを1件も 失わない 構成です。発電所・大型プラント・24/365無停止設備のためのものです。

HA は データの可用性・一貫性・無欠損 を担当します。設備の安全状態は、どのような ソフトウェア障害でも PLC / 安全PLC (SIS) が独立して保証します (IEC 61508 / 61511 SIL)。プラットフォームは安全を保証する主体ではありません。
2つの約束
1 · データは失わない
片方が停止している間に相手側にのみ蓄積された区間を、復旧後に 取りこぼした分だけ 取り戻します。 時間を計算したり推測したりしません — Kafka コミットオフセットカーソル がどこまで受信したかを 正確に把握しているからです。
2 · 制御は一箇所からのみ出力される
制御コマンドはデータと 正反対 です。両方に送信せず、健康な単一リーダーにのみ 送信されます。 権限が曖昧な場合は 送信しません (fail-closed)。
接続が 拒否 された場合は送信前なので安全に他方へ渡します。しかし タイムアウト は 「送信されたかされていないか不明」な状態のため再試行しません — 再試行が即ち 二重動作 です。
構成 — エッジ・プラットフォームペア
PLC 1台をエッジ2台がそれぞれ読み取り、異なるプラットフォームに送信します。そのため両方のプラットフォームが 同じデータをライブで受信します。
1台のPLCを2つのエッジが独立収集し、エッジ#1はAのみへ、エッジ#2はBのみへ送信します。 プラットフォーム1つにエッジ2台を接続するのとは異なります — それは重複保存です。
同じスイッチに接続すると、そのスイッチが単一障害点となり冗長化の意味を失います → エッジ冗長化
通常時は何もしません
plantpulse-ha デーモンは各プラットフォームに 常時起動 しておきますが、正常状態ではトラフィックを
代わりに流しません。 通常時の両側への供給は上記のエッジペアが行います。デーモンが動作するのは
片側がダウンして復旧した後 だけで、その際に2つのことを行います。
| 行うこと | 内容 |
|---|---|
| 時系列バックフィル | ピアの pp-batch トピックを購読し、ローカルが復旧すると ダウン中に取りこぼした区間のみ ローカルの pp-batch へ再発行します。既存の batch-web がそのまま取り込むため、宛先コードは変更不要です |
| PG メタ同期 | タグメタ・資産構造・アラーム定義など Kafka に載らない設定を、主クラスタの PG から待機クラスタが 60秒ごとに pull します |
PG メタ同期は現場タブレットアプリ(frontline)のものも移します — 作業者アカウントと担当範囲(PIN ハッシュを含む)、キオスク設定・設備ごとの停止理由リスト・登録したタブレット。AI キーは metasync.include.tokens=true のときだけ移し、セッション・監査ログ・ログインキャッシュはノードごとに別に持ちます(frontline 二重化)。
event ではなく pp-batch である理由pp-batch は CEP を既に通過した後 の形態です。そのため再生しても宛先で
CEP が再発火しません。 旧モデル (event トピック常時ファンアウト) は CEP 再発火と
常時二重伝送負荷のため廃止されました。
バックフィルはどのように行われるか
INIT → GUARD → WAIT_LOCAL_READY → CATCH_UP → TAIL
↑ ↓
PAUSED ←─────┘ (헬스 상실 / 회복)
| 状態 | 内容 |
|---|---|
GUARD | ピアとローカルの clusterId が同じ場合 起動を拒否 します — 自分自身をピアとして設定する事故を防止 |
WAIT_LOCAL_READY | ローカルの server-web のヘルスが安定するまで待機します |
CATCH_UP | 境界時刻 T_b より前の区間をローカルへ再発行します |
TAIL | T_b 以降は既にエッジがローカルにも取り込んだ区間のため 破棄 します |
PAUSED | ヘルスを失うと停止し、コミットしません |
INCIDENT | retention を超えて読み取れなかった区間がある場合は記録し、earliest から続行します |
切替基準はコンシューマ lag ではなく「レコードタイムスタンプ対固定境界 T_b」 です。
トラフィックが少ないパーティションで lag により判断すると誤判定とフラッピングが発生します。
TAIL でも 60秒遅延コミット を行います (commit.lag.seconds) — ヘルス喪失が検出される
直前の短いウィンドウを次の CATCH_UP が自然に再カバーさせるためです。
ループは構造的に防止されています
再発行するレコードに pp-ha-origin ヘッダーを付与します。そのヘッダーが付いたレコードは 再度
再発行されません — 元 (h=0) → 再発行 (h=1) までで、h=1 は停止します。
重複は吸収されます
境界付近で同じレコードが2回送られる可能性がありますが、Aster TSDB 再取り込みが
tag_id + timestamp 基準の冪等 のため、タグポイントの欠損も重複も残りません。
保証と限界
ピアの pp-batch 保存期間より長く停止していると、その分は自動では受信できません。
その区間は INCIDENT として記録され、復旧させるには backfill.force.from により 手動再再生
します。保存期間を想定最大ダウン時間より長く設定してください。
| 限界 | 内容 |
|---|---|
| 統計カウンタは非冪等 | タグポイント再取り込みは完全冪等ですが、count = count + ? 形式の統計カウンタは重複再生時に過大集計される可能性があります |
| 非対称障害は修正できません | エッジ1台だけが停止し片側供給のみが断たれた場合、ローカルプラットフォーム自体は健康なためヘルスゲートが検知できません → 手動再再生 |
| PG 同期は単方向 | 主 → 待機です。コンソール設定は常に主クラスタでのみ 変更してください — 待機側で変更すると次のサイクルで上書きされます |
エッジが1台のみのサイト — 二重送信クライアント
エッジを2台設置できない現場では、収集器が 両方のクラスタに直接二重送信 します。
plantpulse-api-ha が既存の APIClient と同じインターフェースを提供するため、生成部のみ交換 すれば
済みます。
- 書き込みは全ての宛先へファンアウトし即座に返します (宛先ごとにバウンドキュー + ワーカー + バックオフ)。
- 読み取りは リーダー優先 + フェイルオーバー + sticky で行います。
- トポロジは ha デーモンの
/api/clusterを10秒ごとにポーリングしてキャッシュします (last-known-good)。
運用
# 기동 · 정지 (각 플랫폼에서)
sh/start.sh [설정파일] # 기본 config/plantpulse-ha.properties, 이미 떠 있으면 WARN + exit 0
sh/stop.sh # SIGTERM → 15초 대기 → SIGKILL
sh/log-viewer.sh
デプロイレイアウトは <서비스 루트>/plantpulse-ha/{sh,lib,logs,config,etc} です。
障害発生時
| 状況 | 何が起こるか |
|---|---|
| クラスタ B ダウン | A はそのまま取り込みます。B が復旧すると B のデーモンが CATCH_UP により A にのみ蓄積された区間を読み取り B へ再発行 → 収束 |
| デーモンだけダウン | コミットオフセットはデーモンではなく ピアブローカー にあります。再起動すると最後のコミットから継続します (ヘルスゲートは再度通過する必要があります) |
| 主クラスタでの設定変更 | 待機クラスタが次のサイクル (60秒) で diff を反映します |