콘텐츠로 이동

실시간 이벤트 (SignalR)

라이브 업데이트는 **/hub/realtime**의 단일 SignalR 허브를 통해 전달됩니다. 인증은 REST API와 동일한 JWT를 사용하며, ?access_token=<jwt>(또는 Authorization 헤더)로 전달합니다.

고빈도 스트림은 그룹별로 선택 가입(opt-in)하며, 이를 소비하는 화면이 마운트된 동안에만 가입되고 재연결 시 자동으로 재가입됩니다:

그룹전달 내용소비 주체
sensors라이브 센서 판독 배치Realtime Monitor
predictions새 예측 배치대시보드 차트
logs전체 로그 스트림(모든 레벨)LogViewer 페이지 전용
alertsWarning/Error 로그 항목만알림 벨, 앱 전역

alerts 그룹은 전체 logs 소방호스(firehose)를 모든 대시보드로 스트리밍하지 않고도 경고가 어디서나 표면화되도록 하기 위해 존재합니다.

허브는 12개의 이벤트를 발행하며, 아래 표가 그 전체 목록입니다. 필드 이름은 camelCase로 전달됩니다 — JSON 허브 프로토콜이 서버 측 페이로드 레코드에 ASP.NET Core의 camelCase 정책을 적용하기 때문입니다. 별도 표기가 없는 한 타임스탬프는 Unix epoch 밀리초입니다.

이벤트전달 대상페이로드
Connected접속한 해당 클라이언트에게만connectionId, timestamp
SensorReadingBatchsensors 그룹ticks — 마지막 플러시 이후 합쳐진 센서 틱(형상은 아래 참조)
NewPredictionBatchpredictions 그룹predictions — 마지막 플러시 이후 합쳐진 예측 레코드(형상은 아래 참조)
LogEntrylogs 그룹id, timestamp(ISO-8601 문자열), source, level, message, logger(nullable), correlationId(nullable. 한 틱의 모든 줄에 같은 id가 붙으며, 입력 읽기부터 각 출력 쓰기까지 이어짐), inputDatasourceId, inputDatasourceName, outputDatasourceId, outputDatasourceName(모두 nullable. 해당 줄이 추론 틱에 속할 때 설정됨 — 이름은 줄이 기록된 시점의 이름이며, id는 이름을 바꿔도 유지됨)
AlertLogEntryalerts 그룹LogEntry와 동일한 형상이며, Warning 및 Error 항목만 전달
InferenceStateChanged모든 클라이언트state ∈ idle · ready · running, activeInputDatasourceId(nullable), loadedModelId(nullable), loadedModelVersion(nullable), timestamp — 모든 수명 주기 전환마다 방출
InferenceFaulted모든 클라이언트reason, datasourceId(nullable), timestamp, severity(기본값 error), code(nullable) — 차단성 장애 배너도 함께 표시
HealthMetricsUpdate모든 클라이언트status, timestamp, gpu(nullable 객체), cpu, memory, storage(nullable 객체), uptimeSeconds(추론 엔진), backendUptimeSeconds, engineRestartCount, lastEngineRestartAt(nullable)(중첩 형상은 아래 참조)
ModelActivated모든 클라이언트modelId, version, inputShape, outputShape, fileSizeMb
ModelUploadProgress모든 클라이언트modelId, version, phase, isError, errorMessage(nullable)
ModelUploadFailed모든 클라이언트modelId, version, reason
OutputWriteFailed모든 클라이언트datasourceId, reason, code(nullable), severity(warning), timestamp, count — 싱크별로 합쳐짐: 첫 실패는 즉시 경고하고, 이후에는 30초 윈도우당 최대 한 번의 후속 이벤트에 count만큼의 반복이 접혀 들어감

이벤트 이름은 서버 허브 클라이언트 인터페이스의 메서드 이름이므로, 위 이름들이 클라이언트가 구독하는 실제 와이어 이름입니다.

SensorReadingBatch.ticks의 각 항목은 하나의 틱 — 단일 생산 시점에 찍힌 모든 채널 값입니다:

필드의미
timestamp해당 틱 전체의 생산 시점
values채널당 값 하나; values[i]는 채널 인덱스 i

HealthMetricsUpdate는 다음 객체를 중첩합니다:

객체필드
gpuutilization, memoryUsedMb, memoryTotalMb, memoryUsagePercent, temperatureCelsius — GPU가 없는 호스트에서, 그리고 첫 폴링이 성공하기 전에는 객체 전체가 null
gpu.memoryKindshared(Jetson: GPU가 시스템 RAM을 사용하므로 GPU 메모리 필드는 호스트 값과 같음), dedicated(독립 VRAM), 알 수 없으면 null
cpuusagePercent, temperatureCelsius(호스트에 CPU 온도 센서가 없으면 null)
memoryusagePercent, usedMb, totalMb — 호스트의 RAM
memory 스왑swapUsedMb, swapTotalMb, swapUsagePercent — 호스트에 스왑이 없거나 읽을 수 없으면 null
memory.inferenceusedMb, limitMb, usagePercent, source(nullable) — 추론 컨테이너의 메모리 사용량과, 컨테이너가 중지되는 한도(RAM + 스왑). 한도가 없거나 읽을 수 없으면 객체 전체가 null
storagetotalGb, usedGb, freeGb, usagePercent, databaseMb — 각각 nullable. 디스크를 읽을 수 없으면 객체 전체가 null

memoryKind, 스왑, inference 필드는 추가된 필드입니다. 추론 엔진이 보고하는 값이므로, 이전 버전의 엔진에서는 null로 도착하며 대시보드는 보고되지 않음으로 표시합니다. storage는 백엔드가 직접 측정합니다.

NewPredictionBatch.predictions의 각 항목은 하나의 예측 레코드입니다:

필드의미
requestId해당 레코드를 만든 추론 요청의 식별자
predictions모델 출력, 출력 채널당 값 하나
confidenceScores출력 채널당 신뢰도 값 하나
modelId예측을 생성한 모델
inferenceTimeMs모델 실행 시간
timestamp, windowStartTimestamp, windowEndTimestamp, emittedAt다음 절 참조

예측은 입력의 과거 윈도우를 서술합니다. 모든 타임스탬프는 하나의 시계에서 나온 Unix epoch 밀리초입니다 — 백엔드가 판독 시점에 각 샘플을 한 번만 찍고, 그 값이 파이프라인 전체로 반향되며 결코 재생성되지 않습니다:

필드의미
windowStartTimestamp윈도우에서 가장 오래된 입력 샘플
windowEndTimestamp가장 최신 입력 샘플 — 예측이 유효한 “기준 시점”
timestampwindowEndTimestamp를 미러링(호환성)
emittedAt방출 시점의 백엔드 벽시계 — emittedAt − windowEndTimestamp ≈ 엔드투엔드 지연 시간

대시보드에서 예측 트레이스는 실제 파이프라인 지연 시간만큼 센서 트레이스보다 눈에 띄게 뒤따릅니다. 그 간격은 렌더링 결함이 아니라 의도된 운영 신호입니다.

느린 소비자가 추론을 결코 스로틀하지 않습니다: 대시보드 브로드캐스트는 발사 후 망각 방식이며, 따라잡지 못하는 클라이언트로 향하는 이벤트는 드롭되고 카운트됩니다(/api/inference/backpressure). 알림 대상 이벤트는 중복 제거되고 속도 제한되어 벨을 유용하게 유지합니다 — 알림을 참조하세요.