188 lines
10 KiB
Markdown
188 lines
10 KiB
Markdown
# fastTable fastRecord 필요성
|
||
|
||
- 현재 데이터 저장 간격(1분) 으로는 상세한 필드 데이터의 변동을 캐치 하기 힘듬.
|
||
- 정해진 시간동안 초 단위로 데이터를 받아서 평균을 넘어서는 데이터 분류 등에 분석을 위한 데이터 재료로 사용
|
||
|
||
## Expected Fucnctional Act Sequence
|
||
- UI : 에서 최대 8개 까지 태그명을 선정 및 수집기간(시간 , 일) 선정 하고 시작하면,
|
||
- 테이블 완성() 테이블은 미리 만들어 놓은 형태여도 좋고, 컬럼의 태그명만 바꾸는 방식도 좋다
|
||
- OPC UA 서버로 부터 받는 Realtime 테이블에서 선정된 태그값을 초 단위 (사용자 지정 가능)로 데이터 수집
|
||
- 정해진 시간이 만료되면 수집동작 종료
|
||
- 수집되고 있는 또는 수집된 데이터를 realtime trend graph로 표시
|
||
- UI : 사용자 필요에 따라 전체 테이블 내용 또는 부분 시간 구간을 Excel로 Export 할수있게
|
||
- UI : 테이블 삭제 또는 데이터로 보관 가능? 하게
|
||
|
||
### Claude 가 더 추가하거나 유용한 방식이 있으면 아래에 적어주세요
|
||
|
||
---
|
||
|
||
## 추가 아이디어 (Claude 제안, 2026-04-28)
|
||
|
||
### 핵심 설계 결정
|
||
|
||
#### 1. 데이터 출처 — 별도 OPC UA Subscription 신설 권장
|
||
- 기존 `ExperionRealtimeService`의 Subscription은 SamplingInterval 500ms / PublishingInterval 1000ms로 고정
|
||
- fastRecord는 **분해능이 핵심**이므로 별도 Subscription 생성:
|
||
- SamplingInterval: 100/250/500/1000ms 중 사용자 선택
|
||
- PublishingInterval: SamplingInterval과 동일하게
|
||
- 세션 종료 시 Subscription dispose → 평소엔 부하 없음
|
||
- 대안(기존 `_pendingUpdates` ConcurrentDictionary 폴링)은 분해능 한계(500ms) 때문에 부적합
|
||
|
||
#### 2. 스토리지 — 단일 hypertable + session_id 컬럼 (Long 형태)
|
||
```
|
||
fast_session (메타)
|
||
id, name, started_at, ended_at, status, sampling_ms,
|
||
duration_sec, tag_list (jsonb), row_count, retention_days, pinned
|
||
|
||
fast_record (TimescaleDB hypertable)
|
||
session_id, recorded_at, tagname, value
|
||
→ hypertable on recorded_at, chunk_time_interval = 1 day
|
||
→ index (session_id, tagname, recorded_at)
|
||
```
|
||
- **Long 형태(태그 1행/시점)** 권장 이유: 태그 개수 가변, 태그별 NULL 처리 단순, TimescaleDB 압축 효율
|
||
- 조회 시 서버 또는 클라이언트에서 PIVOT → Wide 변환 (그래프/Excel용)
|
||
|
||
#### 3. 데이터 규모 추정
|
||
|
||
| 시나리오 | 행수 | 비고 |
|
||
|----------|------|------|
|
||
| 8태그 × 1s × 1시간 | 28,800 | 즉시 처리 |
|
||
| 8태그 × 1s × 24시간 | 691,200 | TimescaleDB 무난 |
|
||
| 8태그 × 100ms × 1시간 | 288,000 | TimescaleDB 권장 |
|
||
| 8태그 × 100ms × 24시간 | 6,912,000 | retention/압축 필수 |
|
||
|
||
- 세션당 최대 행수 가드(예: 5,000,000) → 도달 시 자동 종료 + 상태 `RowLimitReached`
|
||
|
||
#### 4. 세션 상태 머신
|
||
```
|
||
Pending → Running → Completed
|
||
↘ Cancelled (사용자 중지)
|
||
↘ Failed (OPC 연결 끊김 등)
|
||
↘ RowLimitReached
|
||
```
|
||
- 동시 Running 세션 최대 N개 제한(권장 3개) — OPC UA Subscription 부하 고려
|
||
- 앱 재기동 시 Running 세션은 `Failed` 처리(중간값 보존, 재개 X — 단순화)
|
||
|
||
### 추가 기능 제안
|
||
|
||
#### 5. 실시간 트렌드 그래프 — uPlot 권장
|
||
- **Chart.js**: 친숙하지만 10만점 초과 시 버벅임
|
||
- **uPlot**: 시계열 특화, 100만점도 부드러움. CDN 단일 파일(~50KB)
|
||
- 다운샘플링: LTTB 알고리즘으로 화면 픽셀 폭에 맞춰 축소(예: 화면 1200px → 1200점)
|
||
- 라이브 갱신: 1~2초 간격 폴링으로 새 데이터만 append
|
||
|
||
#### 6. 통계 + 이상치 분석 (사용자가 언급한 "평균을 넘어서는 데이터 분류")
|
||
- 세션 종료 후 또는 실시간 패널에 표시:
|
||
- 태그별 mean / stddev / min / max / median / p95 / p99
|
||
- **이상치 강조**: `|value - mean| > k × stddev` (k 사용자 설정, 기본 3)
|
||
- **임계값 알람**: 태그별 상/하한 설정 → 초과 구간 그래프에 색상 강조
|
||
- **변화율(slope)**: Δvalue/Δt 급변 구간 표시
|
||
- DB 부하 없이 클라이언트 JS로 계산 가능 (8태그 × ~30만점 수준)
|
||
|
||
#### 7. Excel/CSV Export — 클라이언트 사이드
|
||
- `xlsx.full.min.js`가 이미 wwwroot에 추가되어 있음 → 즉시 활용
|
||
- Wide 포맷: `recorded_at | tag1 | tag2 | ...`
|
||
- 옵션: 전체 / 그래프 현재 줌 구간만 / 시간 슬라이더로 지정한 구간
|
||
- 행수 50,000 초과 시 CSV 권장 (Excel 시트당 1,048,576행 제한 고려)
|
||
- 큰 세션은 서버에서 스트리밍 응답(`text/csv`)으로 제공하는 엔드포인트 추가 권장
|
||
|
||
#### 8. 보관/정리 정책
|
||
- 세션별 `retention_days` 필드 (기본 30, 무한=NULL)
|
||
- `pinned` 플래그(불 표시) → 자동 정리 제외
|
||
- `ExperionFastCleanupService` BackgroundService — 일 1회 새벽 만료 세션 + 데이터 삭제
|
||
- TimescaleDB `drop_chunks` 활용 가능
|
||
|
||
#### 9. 사용성 개선
|
||
- **세션 템플릿**: 자주 쓰는 태그 조합 + 설정 저장 → 원클릭 시작
|
||
- **진행률 표시**: `(현재행수 / 예상행수) × 100`, 남은 시간 추정
|
||
- **다중 태그 단위 그룹**: 같은 단위 태그를 같은 Y축으로 묶고 다른 단위는 보조 Y축
|
||
- **태그별 색상 자동 할당** + 토글로 표시/숨김
|
||
- **그래프 위에 마우스 호버** → 모든 태그의 해당 시점 값 툴팁
|
||
- **시간 동기화 표시**: 서버 시각(UTC) ↔ 브라우저 KST 변환 (이력 조회와 동일 패턴)
|
||
|
||
#### 10. Subscription 동시성 / 안전성
|
||
- 같은 nodeId를 여러 fast 세션이 동시 구독해도 OPC SDK가 처리 — 단, 각 Subscription 별도 비용
|
||
- 세션 시작 시 노드 유효성 사전 검증(`Read` 단발) → bad이면 시작 거부
|
||
- OPC 연결 끊김 시 → 세션 자동 `Failed` + 그때까지 데이터 보존
|
||
- 메모리 보호: 콜백마다 직접 INSERT가 아니라 기존 패턴(ConcurrentDictionary 버퍼 + 1~2초 배치 INSERT)
|
||
|
||
#### 11. 라이브 vs 완료 화면 통합
|
||
- 동일 화면에서 상태에 따라 컨트롤만 다르게:
|
||
- Running: [중지] 버튼, 라이브 갱신 ON, 진행률
|
||
- Completed: [Excel] [CSV] [삭제] [고정/해제] 버튼, 통계 패널, 줌/팬
|
||
|
||
---
|
||
|
||
## 구현 플랜
|
||
|
||
### 전체 구조
|
||
```
|
||
[OPC UA Server]
|
||
│
|
||
├──(기존) Subscription 1 → realtime_table → history_table (60s)
|
||
│
|
||
└──(신규) Subscription per fastSession
|
||
├── 콜백 → ConcurrentDictionary 버퍼
|
||
└── 2s 배치 → fast_record (TimescaleDB hypertable)
|
||
```
|
||
|
||
### Task A — DB 스키마 + 엔티티
|
||
|
||
| 파일 | 작업 |
|
||
|------|------|
|
||
| `ExperionEntities.cs` | `FastSession`, `FastRecord` 엔티티 추가 |
|
||
| `ExperionDbContext.cs` | `DbSet<FastSession>`, `DbSet<FastRecord>`, 테이블 DDL, hypertable 생성(`SELECT create_hypertable('fast_record', 'recorded_at', if_not_exists => TRUE)`) |
|
||
| `IExperionServices.cs` | `IExperionFastService` 인터페이스 + `FastSessionStatus`/`FastSessionInfo`/`FastQueryResult` record |
|
||
|
||
### Task B — FastService (백그라운드 + 컨트롤러)
|
||
|
||
| 파일 | 작업 |
|
||
|------|------|
|
||
| `Infrastructure/OpcUa/ExperionFastService.cs` (신규) | `IHostedService` + `IExperionFastService` 구현. 세션별 Subscription 관리, 콜백 → 버퍼, FlushLoop 2s, 자동 종료(만료/행수초과/외부중지) |
|
||
| `ExperionDbContext.cs` | `BatchInsertFastRecordsAsync(IEnumerable<FastRecord>)`, `GetFastSessionsAsync()`, `GetFastRecordsAsync(sessionId, from?, to?)`, `DeleteFastSessionAsync(sessionId)` 등 |
|
||
| `Web/Controllers/ExperionControllers.cs` | `ExperionFastController` 추가:<br>`POST /api/fast/start` (tags, samplingMs, durationSec, name, retentionDays)<br>`POST /api/fast/{id}/stop`<br>`GET /api/fast/sessions`<br>`GET /api/fast/{id}` (세션 메타)<br>`GET /api/fast/{id}/records?from&to&format=long\|wide`<br>`GET /api/fast/{id}/csv` (스트리밍)<br>`DELETE /api/fast/{id}`<br>`POST /api/fast/{id}/pin` |
|
||
| `Web/Program.cs` | `ExperionFastService` Singleton + HostedService 등록 |
|
||
| `Web/appsettings.json` | `Fast` 섹션 — `MaxConcurrentSessions:3`, `MaxRowsPerSession:5000000`, `FlushIntervalMs:2000` |
|
||
|
||
### Task C — UI: 09 fastRecord 탭
|
||
|
||
| 파일 | 작업 |
|
||
|------|------|
|
||
| `wwwroot/index.html` | 사이드바 09번 `pane-fast` 섹션 추가:<br>- 좌측: 세션 목록(상태/이름/태그수/시작시각/진행률)<br>- 우측 상단: [신규 세션] 버튼 → 모달(태그 선택 8개, 샘플링 select, 기간 select, 이름, retention)<br>- 우측: 선택 세션의 트렌드 그래프 + 통계 + 이상치 패널 + Export 버튼 |
|
||
| `wwwroot/lib/uPlot.iife.min.js` | uPlot 라이브러리 추가 (CDN에서 다운로드한 파일) |
|
||
| `wwwroot/lib/uPlot.min.css` | uPlot 스타일 |
|
||
| `wwwroot/js/app.js` | `fastSessionsLoad()`, `fastStart()`, `fastStop(id)`, `fastDelete(id)`, `fastPin(id)`, `fastSelect(id)`, `fastRenderChart()`, `fastRenderStats()`, `fastExportXlsx()`, `fastExportCsv()`, `fastLivePollStart/Stop` |
|
||
| `wwwroot/css/style.css` | `.fast-session-list`, `.fast-progress`, `.fast-stats-grid`, `.fast-outlier`, 모달 스타일 |
|
||
|
||
### Task D — 정리/보관 백그라운드
|
||
|
||
| 파일 | 작업 |
|
||
|------|------|
|
||
| `Infrastructure/OpcUa/ExperionFastCleanupService.cs` (신규) | `BackgroundService` — 일 1회(03:00) 만료된 세션 + record 삭제. `pinned=true` 제외 |
|
||
| `Web/Program.cs` | HostedService 등록 |
|
||
|
||
### Task E — 안정성 / QA
|
||
|
||
- 노드 유효성 사전 검증(시작 시 Read 1회) — bad이면 400 반환
|
||
- 동시 세션 수 제한 검사 — 초과 시 409
|
||
- 세션 시작 시 OPC UA 연결 상태 확인 — 연결 안되어 있으면 400
|
||
- 앱 종료 시 Running 세션 graceful 마무리(현재 버퍼 flush 후 status=`Cancelled`)
|
||
- 앱 시작 시 Running 상태 잔류 세션 → `Failed` 마킹
|
||
- 단위/통합 테스트는 기존 패턴 따름(현 프로젝트엔 테스트 없음 — 수동 QA 시나리오 문서화)
|
||
|
||
### Task F — 문서화
|
||
|
||
- `CLAUDE.md`에 작업 이력 항목 추가
|
||
- `appsettings.json` 신규 키 설명
|
||
|
||
---
|
||
|
||
## 우선순위 추천
|
||
|
||
1. **MVP**: Task A + B(start/stop/sessions/records 엔드포인트 4개) + C(목록/시작/중지/단순 그래프) — 핵심 가치 검증
|
||
2. **분석**: 통계 패널 + 이상치 강조 + 임계값
|
||
3. **Export**: xlsx + csv 스트리밍
|
||
4. **운영**: Task D 정리, retention/pinned, 동시성 제한, 진행률
|
||
5. **고급**: 템플릿, 다중 Y축, LTTB 다운샘플링 최적화
|
||
|