From 68758f1bb83d32b4800bd97f3d70323be096b9d0 Mon Sep 17 00:00:00 2001 From: windpacer Date: Tue, 14 Apr 2026 09:56:37 +0000 Subject: [PATCH] =?UTF-8?q?Realtime=20DB=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20Historical=20DB=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 365 +++++++++++++++ src/Core/Application/DTOs/ExperionDtos.cs | 11 + .../Interfaces/IExperionServices.cs | 34 ++ src/Core/Domain/Entities/ExperionEntities.cs | 22 + .../Database/ExperionDbContext.cs | 190 ++++++++ .../OpcUa/ExperionHistoryService.cs | 63 +++ src/Infrastructure/OpcUa/ExperionOpcClient.cs | 15 +- .../OpcUa/ExperionRealtimeService.cs | 434 ++++++++++++++++++ src/Web/Controllers/ExperionControllers.cs | 154 +++++++ src/Web/Program.cs | 8 + .../net8.0/linux-x64/ExperionCrawler.dll | Bin 148992 -> 217088 bytes .../net8.0/linux-x64/ExperionCrawler.pdb | Bin 46700 -> 58804 bytes .../linux-x64/ExperionCrawler.AssemblyInfo.cs | 4 +- .../ExperionCrawler.AssemblyInfoInputs.cache | 2 +- ...rionCrawler.csproj.CoreCompileInputs.cache | 2 +- .../net8.0/linux-x64/ExperionCrawler.dll | Bin 148992 -> 217088 bytes .../net8.0/linux-x64/ExperionCrawler.pdb | Bin 46700 -> 58804 bytes .../net8.0/linux-x64/ref/ExperionCrawler.dll | Bin 41472 -> 57344 bytes .../linux-x64/refint/ExperionCrawler.dll | Bin 41472 -> 57344 bytes src/Web/wwwroot/css/style.css | 12 + src/Web/wwwroot/index.html | 174 ++++++- src/Web/wwwroot/js/app.js | 267 +++++++++-- todo.md | 33 ++ 23 files changed, 1743 insertions(+), 47 deletions(-) create mode 100644 src/Infrastructure/OpcUa/ExperionHistoryService.cs create mode 100644 src/Infrastructure/OpcUa/ExperionRealtimeService.cs create mode 100644 todo.md diff --git a/CLAUDE.md b/CLAUDE.md index d9e7b7d..bbcf9df 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,10 @@ # ExperionCrawler — 작업 이력 +## 작업 규칙 +- 복잡한 작업은 항상 todo 목록 먼저 생성 +- 각 단계 시작 전 todo 목록 확인 +- 단계 완료 후 즉시 completed 표시 + ## 완료된 작업 ### 노드맵 대시보드 구현 (2026-04-14) @@ -27,6 +32,7 @@ node_map_master 테이블을 조회·탐색할 수 있는 웹 대시보드를 ### 이름 필터 드롭다운 OR 조건 검색 (2026-04-14) + 노드맵 대시보드의 이름 검색을 텍스트 입력에서 `name` 컬럼 고유값 풀다운 메뉴 4개로 교체, OR 조건 최대 4개 동시 선택 가능하도록 확장했다. #### 수정된 파일 @@ -42,3 +48,362 @@ node_map_master 테이블을 조회·탐색할 수 있는 웹 대시보드를 #### 빌드 결과 - 경고 3건 (기존 경고 동일), **에러 0건** — 빌드 성공 + +--- + +## 구현 완료 (2026-04-14, todo.md 전항목) + +### 빌드 결과 +- 경고 6건 (기존 3건 + 신규 3건 OPC SDK deprecated API 경고), **에러 0건** — 빌드 성공 + +--- + +## 버그 수정 이력 (2026-04-14) + +### 버그 1 — OPC UA 연결 시 OS TCP 타임아웃(최대 127초) 문제 + +#### 증상 +- 접속 테스트 버튼을 눌렀을 때 수분간 응답 없는 것처럼 보임 +- `ExperionRealtimeService`: "연결 오류, 30초 후 재시도" 로그가 매우 늦게 출력됨 +- 오류: `System.Net.Sockets.SocketException (110): Connection timed out` + +#### 원인 +Linux에서 OPC UA 서버 IP가 응답 없음(firewall/unreachable)이면 OS TCP SYN 재전송 타임아웃이 최대 127초까지 걸림. `TransportQuotas.OperationTimeout`은 OPC UA 프로토콜 레벨 타임아웃이라 TCP connect 단계에는 적용되지 않음. + +#### 수정 파일 + +| 파일 | 수정 내용 | +|------|----------| +| `ExperionOpcClient.cs` | `SelectEndpointAsync`에 `CancellationTokenSource(10초)` 추가 — DiscoveryClient 생성 시 10초 타임아웃 적용 | +| `ExperionRealtimeService.cs` | 동일하게 `SelectEndpointAsync` 10초 타임아웃 적용 | + +#### 결과 +서버 미응답 시 127초 대기 → **10초 이내 실패** 처리 + +--- + +### 버그 2 — PostgreSQL `sorry, too many clients already` (SQLSTATE 53300) + +#### 증상 +구독 시작 후 실시간 값 수신 시 터미널에 다량의 에러: +``` +Npgsql.PostgresException (0x80004005): 53300: sorry, too many clients already +at ExperionDbService.UpdateLiveValueAsync(...) +at ExperionRealtimeService.<b__0>d.MoveNext() +``` + +#### 원인 +`OnNotification` 콜백이 포인트마다 `Task.Run` → 새 DI 스코프 → 새 `DbContext` → 새 DB 커넥션을 열었음. 2000여개 포인트가 동시에 값 변경 콜백을 받으면 순식간에 PostgreSQL `max_connections`(기본 100) 초과. + +``` +값 변경 콜백 × 2000개 → Task.Run × 2000개 → DB 커넥션 × 2000개 → 💥 +``` + +#### 수정 파일 + +| 파일 | 수정 내용 | +|------|----------| +| `IExperionServices.cs` | `BatchUpdateLiveValuesAsync(IEnumerable)` 인터페이스 추가, `LiveValueUpdate` record 추가 | +| `ExperionDbContext.cs` | `BatchUpdateLiveValuesAsync` 구현 — 단일 DbContext에서 순차 ExecuteUpdateAsync | +| `ExperionRealtimeService.cs` | `OnNotification`에서 `Task.Run` 제거 → `ConcurrentDictionary`에 최신값만 기록. 별도 `FlushLoopAsync` 태스크가 500ms마다 단일 DbContext로 배치 업데이트 | + +#### 수정 후 구조 +``` +값 변경 콜백 × N개 → ConcurrentDictionary[nodeId] = 최신값 + ↓ 500ms마다 + 단일 DbContext → BatchUpdateLiveValuesAsync → DB 커넥션 1개 +``` + +#### 결과 +- DB 커넥션 동시 사용 수: 2000개 → **최대 1개** +- 500ms 내 중복 변경은 최신값 1건만 DB에 반영 (deduplication) +- 빌드: 경고 6건(기존 동일), **에러 0건** + +--- + +### 버그 3 — 대시보드 탭 진입 시 자동 API 호출로 인한 CPU/브라우저 버벅임 + +#### 증상 +- **노드맵 대시보드** 탭 진입 시 CPU 과부하, 페이지 버벅임 +- **포인트빌더** 탭 진입 시 동일 증상 +- **이력 조회** 탭 진입 시 한참 동안 열리지 않음 + +#### 원인 (항목별) + +| 탭 | 자동 호출 API | 무거운 이유 | +|----|--------------|------------| +| 노드맵 대시보드 | `/api/nodemap/stats` + `/api/nodemap/names` + `/api/nodemap/query` | stats: 5가지 집계 쿼리(COUNT×4, MAX, DISTINCT). 결과로 전체 조회까지 자동 실행 | +| 포인트빌더 | `/api/nodemap/names` + `/api/nodemap/stats` | stats 집계 쿼리 (포인트빌더 dataType 드롭다운 채우기 용도) | +| 이력 조회 | `/api/history/tagnames` → 드롭다운 8개에 2000개 옵션 삽입 | 8 × 2000 = 16,000개 DOM `