InfraGuard

실시간 업데이트 (Reverb + 폴링)

화면이 자동으로 갱신되는 두 가지 방법 — Reverb WebSocket 즉시 푸시와 경량 폴링(30초 · rev 변경감지) — 과 WS가 꺼져도 끊기지 않는 graceful 폴백 원리.

개요 — 무엇을 하나

InfraGuard의 대시보드는 새로고침 없이 스스로 갱신됩니다. 이 자동 갱신은 두 가지 메커니즘으로 이뤄집니다.

메커니즘 방식 지연 사용처
Reverb WebSocket 서버 → 브라우저 즉시 푸시(이벤트 기반) 밀리초 진행률이 tick 단위로 흐르는 라이브 작업
경량 폴링 브라우저가 /…/live 를 주기적으로 GET 최대 30초 대시보드 대부분(Health·Servers·Traffic·Service·Security·Alerts …)

핵심 설계 원칙 하나로 요약됩니다:

라이브는 영속 데이터 위에 얹는 부가기능입니다. 모든 화면은 DB에 저장된 데이터를 먼저 렌더링하고, 실시간 갱신은 그 위에 얹힙니다. 따라서 Reverb(WebSocket)가 꺼져 있어도, 방화벽에 막혀도, 재시작 중이어도 화면은 절대 깨지지 않습니다. 푸시가 안 되면 폴링이 대신 채웁니다(graceful degradation).

실무적으로는 거의 모든 화면이 폴링으로 동작합니다. Reverb WebSocket은 설치·배선되어 있으며, 지금은 보안 점검 원격 에뮬레이트(원격 에뮬레이트 · 보안 진단) 한 곳에서 진행 tick을 실시간 스트리밍하는 데 쓰입니다. 나머지 라이브 화면은 30초 폴링 + 값싼 rev 변경감지 토큰으로 갱신합니다.


언제 쓰나

이 문서는 개념 참조입니다. 라이브 route를 여는 화면이 아니라, 다음을 이해하려는 운영자·엔지니어를 위한 것입니다.


화면 구성

두 경로 아키텍처

[영속 데이터]  server_status · infr_db_metric · blacklist_info · …
      │
      ▼ (페이지 최초 로드: 서버가 뷰에 data-* JSON 주입)
[브라우저 화면]  ← 즉시 렌더 (라이브 없이도 완결)
      ▲                         ▲
      │ (A) 폴링                │ (B) Reverb 푸시
      │  GET /…/live (≤30s)     │  WS 이벤트 'tick'
      │  rev 변경 시만 재도색     │  ShouldBroadcastNow
   [Laravel 컨트롤러]        [Reverb 서버]

Reverb 토폴로지

항목 값 출처
클라이언트 라이브러리 laravel-echo + pusher-js 동적 import(메인 번들 제외)

WebSocket 연결 정보는 설치 시 구성됩니다. 운영자는 별도 설정 없이 화면을 열기만 하면 됩니다.


사용법

운영자 — 아무것도 안 해도 됩니다

라이브는 자동입니다. 화면을 열어 두면 폴링이 30초마다 최신값을 가져오고, WebSocket이 켜져 있으면 이벤트 화면은 즉시 갱신됩니다. 절전을 위해 탭을 배경으로 두면 폴링이 멈추고, 다시 앞으로 오면 재개합니다.

Reverb WebSocket 켜기(선택)

WebSocket 푸시는 설치 시 도입 담당자가 구성합니다. 운영자가 콘솔에서 켜고 끄는 항목이 아닙니다. 실시간 푸시가 필요하시면 도입 담당자 또는 공급사에 문의해 주세요.

WebSocket 푸시가 꺼져 있어도 오류가 아닙니다. 의도된 off 스위치이며, 라이브 화면은 조용히 폴링만으로 동작합니다.

폴링이 실제로 하는 일

// 대표 패턴(service-overview 등)
poll_ms: 30000,
this._poll = setInterval(() => {
    if (this.live && !document.hidden) this.poll_tick();   // 탭 배경이면 skip
}, this.poll_ms);
// poll_tick → GET /service/live → 응답의 rev 가 바뀌었을 때만 재렌더

서버 측 rev 는 가장 값싼 변경 신호입니다. 예를 들어 서버 메트릭 화면은 레거시 server_status 테이블의 MAX(seq) 를 rev로 씁니다 — 수집기가 새 스냅샷을 넣을 때만 값이 증가합니다.

// App\Services\Metric\ServerMetricService::rev()
return (int) DB::connection('legacy')->table('server_status')->max('seq');

Reverb 푸시가 쓰이는 유일한 라이브 작업 — 원격 에뮬레이트

보안 진단의 원격 에뮬레이트는 호스트 하나가 끝날 때마다 진행 tick을 브로드캐스트합니다.


데이터 소스

파일 역할
resources/js/echo.js 앱 유일의 Echo 빌더(SOT) — 동적 import · config 게이트 → null · 런타임 forceTLS
app/Support/Realtime.php 브로드캐스트 안전장치(up() fsockopen 프로브 · fire() 예외 흡수)
routes/channels.php 채널 인증 콜백(현재 기본 App.Models.User.{id} 만)
app/Events/InfrRemoteEmulateTick.php 유일한 실사용 브로드캐스트 이벤트
각 화면 컨트롤러 …/live + …Service::rev() 폴링용 라이브 payload + 변경감지 토큰

Realtime — 2단 방어(WS가 죽어도 원 작업은 안 죽습니다)

// (1) up() — 150ms fsockopen 프로브(요청당 메모이즈). 이벤트 broadcastWhen()이 이걸로 게이팅.
//     Reverb 다운 시 ShouldBroadcastNow 가 connect-timeout 을 물고 인라인 throw 하는 사고를 막음.
public static function up(): bool  // 설정값으로 프로브 (하드코딩 금지)

// (2) fire() — Reverb 가 살아있는데 거부(413·재시작 5xx)해도 caller 를 보호.
//     실패는 report() 로 Pulse Exceptions 에 남기고, 원 작업(저장된 스캔 결과)은 그대로 성공.
public static function fire(object $event): void

이벤트의 broadcastWhen() 은 Realtime::up() 을 반환합니다 — Reverb가 꺼져 있으면 송신 자체를 건너뜁니다.


권한

채널 인증

채널 유형 인증 방식
공개 채널(infr.emulate.{runId}) 인증 없음 — runId capability 토큰이 곧 접근권(추측 불가)
private / presence 채널(설계 로드맵) routes/channels.php 콜백이 세션 user_level · 역할 스코프로 검사

CLAUDE.md의 실시간 채널 표(private-infr.server.{id}·private-infr.threats·presence-infr.ops 등)는 설계 로드맵입니다. 오늘 구현된 브로드캐스트는 공개 채널 infr.emulate.{runId} 하나이며, routes/channels.php 에는 프레임워크 기본 App.Models.User.{id} 프라이빗 채널만 등록돼 있습니다. private/presence 채널을 추가할 때는 반드시 user_level·스코프로 인가합니다(예: infr.threats 는 user_level>=2 && scope∋security).

폴링 엔드포인트 권한

/{화면}/live 폴링 엔드포인트는 해당 화면 컨트롤러의 읽기 레벨 게이트를 그대로 계승합니다(데모=1 · 스태프=5 · 관리자=10, 일부 영역은 infr.level:N 미들웨어). 예: 원격 에뮬레이트의 상태 폴백 폴링(emulate-status)은 쓰기 권한(level≥2)을 요구합니다. 자세한 레벨 정책은 RBAC 레벨을 참고.


문제해결 · FAQ

Q. WebSocket이 연결되지 않는데 화면은 계속 갱신됩니다?

정상입니다. 라이브 화면은 WS 없이도 폴링으로 완전히 동작합니다. WS는 원격 에뮬레이트 같은 이벤트성 작업의 지연만 줄여 줍니다.

Q. Reverb를 켰는데 브라우저에서 안 붙습니다

Q. 대시보드가 서버·DB에 부담을 주지 않나?

Q. 실시간 데몬을 스케줄러에 걸어도 되나?

절대 안 됩니다. reverb:start·queue:work·pulse:check 같은 무한 루프 데몬을 스케줄러(Schedule::command(...))로 spawn 하지 말 것. 종료되지 않는 프로세스가 매 tick 누적되어 공유 MySQL 연결을 고갈시킵니다(2026-08-06 pulse:check 실측: 16시간 동안 169개 누적 → [1040] Too many connections, 프로덕션 DB까지 영향). 데몬은 런처의 단일 상시 pane(dev) 또는 supervisor/systemd(prod)로만 실행합니다. 관측·데몬 배치는 개발 · 관측 참고.

Q. 코드 변경 후 Reverb를 재시작하려면?

프로덕션은 무중단으로 php artisan reverb:restart (또는 supervisor 재시작). 개발은 런처 pane을 재시작하면 됩니다. 재시작 중 잠깐 WS가 거부해도 Realtime::fire() 가 예외를 흡수하고 폴링이 메웁니다.


관련 항목

헤더의 LIVE 표시 — 화면이 스스로 갱신되는 중
헤더의 LIVE 표시 — 화면이 스스로 갱신되는 중