Files
HC900-Crawler/docs/작업플랜-셀프서비스-분석리포트-P1-온라인히스토리안-스펙.md
windpacer e2b1b8f6e0 docs(report): P1 온라인 히스토리안 스펙 — 1초 링버퍼·연속집계·실시간 KPI/알람
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>
2026-06-14 18:40:33 +09:00

7.6 KiB
Raw Blame History

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억 행, 압축(1020×) 후 수십 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 인벤토리 인출/컬럼간 이송
폐합 이탈 |closure100|>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 연계 (변경 최소)

  • 메트릭엔진 sourcehistory_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 시스템 통합수지.

수용 기준

  1. history_1s가 정확히 최근 14일치 1초 보유, 디스크 상한 고정; 그 이전은 history_1min에 보존(손실 0).
  2. 동치 검증: live_kpi의 당일 closure == 같은 윈도 배치 summary 값(causal 동치).
  3. 합성 cleaning/drawdown 주입 시 상태 전이 + 알람 발화.
  4. 누적기 강제 재기동 → 버퍼 리플레이로 상태 동일 복구.

근거: P0 결정론 마스크/적산이 causal → 온라인 동치. TimescaleDB 하이퍼테이블/보존/연속집계(기존 history_table 동일 인프라), BackgroundService 패턴(Hc900RealtimeService/HistoryService). 관련 메모리: qv-cleaning-mask-and-column-links, product-pivot-selfservice-reporting.