본문으로 건너뛰기

이중화 (HA) 설정

플랫폼 두 벌(A · B) 을 나란히 두고, 한쪽이 통째로 죽어도 운영 데이터를 한 건도 잃지 않게 하는 구성입니다. 발전소 · 대형 플랜트 · 24/365 무중단 설비를 위한 것입니다.

플랫폼 HA 화면

물리적 안전은 PLC 가 책임집니다

HA 는 데이터의 가용성 · 일관성 · 무유실을 담당합니다. 설비의 안전 상태는 어떤 소프트웨어 실패에서도 PLC / 안전 PLC(SIS) 가 독립적으로 보장합니다 (IEC 61508 / 61511 SIL). 플랫폼은 안전을 보장하는 주체가 아닙니다.


두 가지 약속​

1 · 데이터는 잃지 않는다​

한쪽이 죽어 있던 동안 상대에만 쌓인 구간을, 복구된 뒤 놓친 만큼만 되돌려 받습니다. 시간을 계산하거나 추측하지 않습니다 — Kafka 커밋 오프셋 커서가 어디까지 받았는지를 정확히 알고 있기 때문입니다.

2 · 제어는 한 곳으로만 나간다​

제어 명령은 데이터와 정반대입니다. 양쪽에 보내지 않고 건강한 단일 리더에만 갑니다. 권한자가 모호하면 보내지 않습니다(fail-closed).

타임아웃은 재시도하지 않습니다

연결이 거부된 것은 전송 전이므로 안전하게 다른 쪽으로 넘깁니다. 그러나 타임아웃은 「갔는지 안 갔는지 모르는」 상태라 재시도하지 않습니다 — 재시도가 곧 이중 작동입니다.


구성 — 엣지-플랫폼 쌍​

PLC 하나를 엣지 두 대가 각자 읽어 서로 다른 플랫폼에 보냅니다. 그래서 양쪽 플랫폼이 같은 데이터를 라이브로 받습니다.

«PLC 당 엣지 2대» 와 «플랫폼당 엣지 1대» 는 같은 말입니다

한 PLC 를 두 엣지가 독립 수집해 엣지#1은 A 에만, 엣지#2는 B 에만 보냅니다. 플랫폼 하나에 엣지 두 대를 붙이는 것과는 다릅니다 — 그건 중복 저장입니다.

두 엣지는 물리적으로 다른 스위치를 타야 합니다

같은 스위치에 물리면 그 스위치가 단일 장애점이라 이중화가 뜻을 잃습니다 → 엣지 이중화


평소에는 아무것도 하지 않습니다​

plantpulse-ha 데몬은 각 플랫폼에 상시 기동해 두지만, 정상 상태에서는 트래픽을 대신 흘리지 않습니다. 평상시 양쪽 공급은 위의 엣지 쌍이 합니다. 데몬이 일하는 것은 한쪽이 다운됐다가 복구된 뒤뿐이고, 그때 두 가지를 합니다.

하는 일무엇
시계열 백필피어의 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 이전 구간을 로컬로 재발행합니다
TAILT_b 이후는 이미 엣지가 로컬에도 넣었을 구간이라 폐기합니다
PAUSED헬스를 잃으면 멈추고 커밋하지 않습니다
INCIDENTretention 을 넘겨 못 읽은 구간이 있으면 기록하고 earliest 부터 이어 갑니다

전환 기준은 컨슈머 lag 이 아니라 «레코드 타임스탬프 대 고정 경계 T_b» 입니다. 트래픽이 적은 파티션에서 lag 으로 판단하면 오판과 플래핑이 납니다.

TAIL 에서도 60초 지연 커밋을 합니다(commit.lag.seconds) — 헬스 상실이 감지되기 직전의 짧은 창을 다음 CATCH_UP 이 자연히 다시 덮게 하려는 것입니다.

루프는 구조적으로 막혀 있습니다​

재발행하는 레코드에 pp-ha-origin 헤더를 답니다. 그 헤더가 붙은 레코드는 다시 재발행하지 않습니다 — 원본(h=0) → 재발행(h=1)까지만이고 h=1 은 멈춥니다.

중복은 흡수됩니다​

경계 부근에서 같은 레코드가 두 번 갈 수 있지만, Aster TSDB 재적재가 tag_id + timestamp 기준 멱등이라 태그 포인트 유실도 중복도 남지 않습니다.


보장과 한계​

무유실의 조건 — 피어 retention ≥ 최대 다운시간

피어의 pp-batch 보존 기간보다 오래 죽어 있으면 그만큼은 자동으로 못 받습니다. 그 구간은 INCIDENT 로 기록되고, 되살리려면 backfill.force.from 으로 수동 재재생 합니다. 보존 기간을 예상 최대 다운시간보다 길게 잡으십시오.

한계내용
통계 카운터는 비멱등태그 포인트 재적재는 완전 멱등이지만 count = count + ? 형태의 통계 카운터는 중복 재생 시 과대 집계될 수 있습니다
비대칭 장애는 못 고칩니다엣지 한 대만 죽어 한쪽 공급만 끊긴 경우, 로컬 플랫폼 자체는 건강하므로 헬스 게이트가 감지하지 못합니다 → 수동 재재생
PG 동기화는 단방향주 → 대기입니다. 콘솔 설정은 항상 주 클러스터에서만 바꾸십시오 — 대기에서 바꾸면 다음 사이클에 덮어써집니다

엣지가 한 대뿐인 사이트 — 이중 전송 클라이언트​

엣지를 두 대 놓을 수 없는 현장에서는 수집기가 양쪽 클러스터에 직접 이중 전송합니다. 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 를 반영합니다

함께 보기​

엣지 이중화(HA) · 백업 · 복원 · 클러스터 설치