P0 결정론 메트릭(마스크·적산·리셋)의 온라인 확장 스펙. 핵심: 로직이 causal이라 온라인 결과(T) ≡ 배치(시작~T), 로직 변경 없이 스트리밍 가능. - A. history_1s 1초 링버퍼: Timescale 하이퍼테이블 + 보존정책(14일, 청크DROP) + 압축. 디스크 = 태그수×윈도×행크기로 상한 고정. 큐레이션 태그(~200) 1초 append. - B. history_1min 연속집계: evict 전 롤업(집계lag ≪ 보존윈도) → 무손실 장기보존. 2계층. - C. Hc900LiveKpiService 온라인 누적기: 러닝 상태로 P0와 동일 마스크/양증분/리셋, live_kpi 적재, 인과성 동치검증, 버퍼 리플레이 복구. - D. kpi_alert 실시간 알람: cleaning/drawdown 진입·폐합 베이스라인 이탈(계량드리프트/이송)·생산정지. 큐레이션 태그셋·P0 연계(source 추가)·단계(P1a~d)·수용기준 포함. MVP 문서에 링크. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.6 KiB
P1 스펙 — 온라인 히스토리안 + 실시간 KPI/알람
2026-06-14. 상위:
작업플랜-셀프서비스-분석리포트-MVP.md· 기반:작업플랜-셀프서비스-분석리포트-MVP-P0-상세설계.md. 목표: P0의 결정론 메트릭(마스크·적산·리셋)을 온라인(실시간) 으로 확장. 핵심 근거 = 메트릭 로직이 전부 causal/incremental 이라, 시각 T의 온라인 결과 = [시작~T] 배치 결과와 동일 → 로직 변경 없이 스트리밍 가능.
0. 4대 컴포넌트
gateway(1s poll) ─► Hc900RealtimeService
├─ realtime_table (현재값 upsert, 기존)
└─ history_1s (A. 1초 append — 큐레이션 태그, 링버퍼 보존 N일)
└─(B. 연속집계)─► history_1min (장기·무손실 롤업)
Hc900LiveKpiService (C. 온라인 누적기) ─► live_kpi ─► (D. 알람엔진) ─► kpi_alert
메트릭엔진(P0): source = { history_1s | history_1min | history_table(60s) | fast_record }
A. 1초 링버퍼 (history_1s) — 디스크 상한 고정
상시 1초 누적은 디스크 무한증가 → 윈도 고정 링버퍼로 상한을 못박는다(시스템을 1년 돌려도 디스크 동일).
CREATE TABLE hc900.history_1s (
tagname text NOT NULL, recorded_at timestamptz NOT NULL,
value text, controller_id text
);
SELECT create_hypertable('hc900.history_1s','recorded_at', chunk_time_interval => INTERVAL '1 hour');
SELECT add_compression_policy('hc900.history_1s', INTERVAL '6 hours');
SELECT add_retention_policy('hc900.history_1s', INTERVAL '14 days'); -- ← 윈도 = 디스크 예산 손잡이
CREATE INDEX ON hc900.history_1s (tagname, recorded_at DESC);
- 보존정책 = 청크 DROP(행 DELETE 아님)이라 비용 ≈ 0. 14일 후 자동 증발.
- 디스크 = 태그수 × 윈도(초) × 행크기. 예: 200태그 × 14일 × 1초 ≈ 2.4억 행, 압축(10
20×) 후 수십 GB.
적재: Hc900RealtimeService(또는 신규 Hc900FastHistoryService)가 매 폴(1s)마다 큐레이션 태그셋만 배치 append(기존 500행/배치 패턴 재사용). realtime_table upsert와 병행.
B. 연속집계 (history_1min) — 버퍼 evict 전 롤업, 무손실
1초가 버퍼에서 밀려나기 전에 60초로 materialize → 장기 이력 보존.
CREATE MATERIALIZED VIEW hc900.history_1min WITH (timescaledb.continuous) AS
SELECT time_bucket('1 min', recorded_at) bucket, tagname,
last(value::float, recorded_at) value, last(controller_id, recorded_at) controller_id
FROM hc900.history_1s GROUP BY 1,2;
SELECT add_continuous_aggregate_policy('hc900.history_1min',
start_offset => INTERVAL '3 hours', end_offset => INTERVAL '10 minutes',
schedule_interval => INTERVAL '5 minutes');
- 불변식: 집계 lag < 보존 윈도 (3h ≪ 14d) → raw 삭제 시 이미 집계 완료. 손실 0.
- 2계층:
history_1s(최근 14일, 온라인·동특성·정밀 cleaning) +history_1min/기존history_table(장기, 일·월·연 롤업).
C. 온라인 KPI 누적기 (Hc900LiveKpiService)
배치처럼 윈도 재계산 대신 러닝 상태 유지(부하↓, 실시간↑). 기존 Hc900HistoryService BackgroundService 패턴.
// 컬럼×태그별 러닝 상태 (P0 QvDeltaAsync의 스트리밍 버전)
record QvState { double Accum; double? PrevValue; bool PrevClean; }
// 매 1s 샘플(또는 1min tick)마다:
foreach (col)
cleaning = vac>VacMax || product<ProductMin || feed<FeedMin; // P0와 동일 마스크
foreach (tag in [feed, product, lights, heavies, steam])
if (!cleaning && !st.PrevClean && st.PrevValue is double p && v>=p && v-p<5e4)
st.Accum += v - p; // P0와 동일 양증분합산
st.PrevValue = v; st.PrevClean = cleaning;
closure = 100 * (prodAccum+lightsAccum+heaviesAccum) / feedAccum;
upsert live_kpi(col, 'closure'|'production'|'yield'|'energy', value, window_start, state, updated_at);
CREATE TABLE hc900.live_kpi (
column_id text, kpi text, value double precision,
window_start timestamptz, state text, -- normal|cleaning|drawdown|transfer
excluded_min int, updated_at timestamptz,
PRIMARY KEY (column_id, kpi, window_start));
- 윈도 정책: 일(日) 리셋(KST 자정 window_start 갱신) — 기존 누적은
history_1min/배치로 보존. (또는 trailing-24h 롤링 옵션.) - 크래시 복구: 기동 시
history_1s버퍼에서 현재 윈도를 리플레이해 상태 재구성(causal이라 동일 결과). - 인과성 보장: 상태기반 = 매 샘플 현재값만 사용 → 누적기 결과(T) ≡ 배치(시작~T). 수용기준 §아래에서 동치 검증.
D. 실시간 알람 (kpi_alert)
| 규칙 | 조건 | 의미 |
|---|---|---|
| cleaning 진입 | 진공>300 또는 제품<10 지속 | 세정 시작 |
| drawdown 진입 | feed<10 & 제품>0 | 인벤토리 인출/컬럼간 이송 |
| 폐합 이탈 | |closure−100|>2% 지속 M분 (정상구간서) | 계량 드리프트/누설/미계량 이송 |
| 생산 정지 | production Δ≈0인데 비-cleaning | 트립/이상 |
→ 기존 event_history_table/알림 경로 재사용. (마스크 자체가 cleaning/drawdown 판정을 이미 제공.) |
큐레이션 태그셋 (1초 버퍼 대상)
컬럼당(7컬럼): 유량 FICQ-*01/13/14/16/18·FIQ-*15 의 .PV + .QV, 진공 PICA-*, 민감단 TI-*C, 하부 TICA-*A(.PV/.SP/.OP), 핵심 압력/레벨. ≈ 25~30/컬럼 × 7 ≈ ~200 태그. (전체 900 아님 — 메트릭·진단 관련만.) config 목록.
P0 연계 (변경 최소)
- 메트릭엔진
source에history_1s/history_1min추가 — 마스크 QvDelta SQL은 테이블만 교체(이미 source 파라미터화). cleaning config 동일. - 웹 summary에 live 모드(live_kpi 직독) + source 셀렉터.
리스크 / 단서
- 윈도(14일)보다 오래된 건 1초로 없음 → 60초만. 1초가 필요한 분석(전환 포렌식)은 최근 구간 한정. 일·월 리포트는 60초로 충분.
- 불변식 집계 lag < 보존 윈도 반드시 유지(아니면 장기 손실).
- 태그셋 규율(900 전부 금지). 윈도 = 디스크 예산.
- 진행 중 캠페인/이송(C-9111↔C-10111) 시 일중 폐합 ≠100%는 정상(KPI가 transfer 상태로 표시).
realtime_table은 upsert(시계열 아님) → 버퍼/메트릭 소스는history_1s.
단계
- P1a:
history_1s버퍼 + 보존/압축 + 큐레이션 적재(Hc900FastHistoryService). - P1b: 연속집계
history_1min(장기 무손실). - P1c:
Hc900LiveKpiService누적기 +live_kpi+ summary live 모드. - P1d: 알람엔진 +
kpi_alert. - (병행)
dynamics메트릭(fast/1s 전용: FOPDT·stiction), 월/연 롤업, C-9111↔C-10111 시스템 통합수지.
수용 기준
history_1s가 정확히 최근 14일치 1초 보유, 디스크 상한 고정; 그 이전은history_1min에 보존(손실 0).- 동치 검증:
live_kpi의 당일 closure == 같은 윈도 배치summary값(causal 동치). - 합성 cleaning/drawdown 주입 시 상태 전이 + 알람 발화.
- 누적기 강제 재기동 → 버퍼 리플레이로 상태 동일 복구.
근거: P0 결정론 마스크/적산이 causal → 온라인 동치. TimescaleDB 하이퍼테이블/보존/연속집계(기존 history_table 동일 인프라), BackgroundService 패턴(Hc900RealtimeService/HistoryService). 관련 메모리: qv-cleaning-mask-and-column-links, product-pivot-selfservice-reporting.