개요 — 무엇을 하나
InfraGuard의 대시보드는 새로고침 없이 스스로 갱신됩니다. 이 자동 갱신은 두 가지 메커니즘으로 이뤄집니다.
| 메커니즘 | 방식 | 지연 | 사용처 |
|---|---|---|---|
| Reverb WebSocket | 서버 → 브라우저 즉시 푸시(이벤트 기반) | 밀리초 | 진행률이 tick 단위로 흐르는 라이브 작업 |
| 경량 폴링 | 브라우저가 /…/live 를 주기적으로 GET |
최대 30초 | 대시보드 대부분(Health·Servers·Traffic·Service·Security·Alerts …) |
핵심 설계 원칙 하나로 요약됩니다:
라이브는 영속 데이터 위에 얹는 부가기능입니다. 모든 화면은 DB에 저장된 데이터를 먼저 렌더링하고, 실시간 갱신은 그 위에 얹힙니다. 따라서 Reverb(WebSocket)가 꺼져 있어도, 방화벽에 막혀도, 재시작 중이어도 화면은 절대 깨지지 않습니다. 푸시가 안 되면 폴링이 대신 채웁니다(graceful degradation).
실무적으로는 거의 모든 화면이 폴링으로 동작합니다. Reverb WebSocket은 설치·배선되어 있으며, 지금은 보안 점검 원격 에뮬레이트(원격 에뮬레이트 · 보안 진단) 한 곳에서 진행 tick을 실시간 스트리밍하는 데 쓰입니다. 나머지 라이브 화면은 30초 폴링 + 값싼 rev 변경감지 토큰으로 갱신합니다.
언제 쓰나
이 문서는 개념 참조입니다. 라이브 route를 여는 화면이 아니라, 다음을 이해하려는 운영자·엔지니어를 위한 것입니다.
- 대시보드가 어떻게 자동으로 최신 상태가 되는지
- WebSocket이 연결되지 않은 환경(내부망·프록시·mixed content)에서 무슨 일이 일어나는지
- 폴링이 배터리/서버를 어떻게 아끼는지(탭 배경화 시 일시정지)
화면 구성
두 경로 아키텍처
[영속 데이터] server_status · infr_db_metric · blacklist_info · …
│
▼ (페이지 최초 로드: 서버가 뷰에 data-* JSON 주입)
[브라우저 화면] ← 즉시 렌더 (라이브 없이도 완결)
▲ ▲
│ (A) 폴링 │ (B) Reverb 푸시
│ GET /…/live (≤30s) │ WS 이벤트 'tick'
│ rev 변경 시만 재도색 │ ShouldBroadcastNow
[Laravel 컨트롤러] [Reverb 서버]
- (A) 폴링 — 프론트가
setInterval로 30초마다/{화면}/live를 호출합니다. 응답에는 값싼 변경감지 토큰rev가 들어 있고, 값이 바뀌었을 때만 차트·카드를 다시 그립니다. 탭이 백그라운드면(document.hidden) 호출을 건너뜁니다. - (B) Reverb 푸시 — 서버가 이벤트를 즉시 브로드캐스트(
ShouldBroadcastNow— 큐 미경유)하고, 구독 중인 브라우저가 WebSocket으로 받습니다. 지금은 원격 에뮬레이트 진행 tick 전용.
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을 브로드캐스트합니다.
- 이벤트:
App\Events\InfrRemoteEmulateTick(ShouldBroadcastNow) - 채널: 공개 채널
infr.emulate.{runId}—runId는 추측 불가 랜덤 토큰(capability). 공개 채널이라 세션 기반/broadcasting/auth배선이 필요 없습니다. - 이벤트명:
.tick· payload는 경량(호스트 요약 + 진행 카운트만) - 폴백: 프론트는 구독과 동시에 2초 폴백 폴링(
/asset/remote/emulate-status/{runId})을 돌려, WS가 없거나 초기 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를 켰는데 브라우저에서 안 붙습니다
- mixed content —
echo.js는forceTLS를 런타임 페이지 프로토콜로 결정합니다(https:면 wss). 빌드 env로 강제하지 않는 이유가 이것 — https로 서빙된 번들이ws://를 시도해 브라우저가 차단하는 사고를 막습니다. 프론트가 https면 Reverb도 TLS(wss)로 뜨거나 프록시가 종단해야 합니다. - CSP/CORS —
echo.js는enabledTransports: ['ws','wss']+disableStats: true로 pusher-js의 sockjs CDN 폴백·통계 호출을 차단합니다(자체 호스팅 CSP 위반 방지).
Q. 대시보드가 서버·DB에 부담을 주지 않나?
- 폴링 주기는 30초이고, 탭이 백그라운드면 멈춥니다(
document.hidden). rev변경감지 토큰(예:MAX(seq))으로, 새 데이터가 없으면 무거운 재렌더를 건너뜁니다.- 라이브 빌더 서비스는 대부분 20~30초 캐시를 둡니다.
Q. 실시간 데몬을 스케줄러에 걸어도 되나?
절대 안 됩니다.
reverb:start·queue:work·pulse:check같은 무한 루프 데몬을 스케줄러(Schedule::command(...))로 spawn 하지 말 것. 종료되지 않는 프로세스가 매 tick 누적되어 공유 MySQL 연결을 고갈시킵니다(2026-08-06pulse:check실측: 16시간 동안 169개 누적 →[1040] Too many connections, 프로덕션 DB까지 영향). 데몬은 런처의 단일 상시 pane(dev) 또는 supervisor/systemd(prod)로만 실행합니다. 관측·데몬 배치는 개발 · 관측 참고.
Q. 코드 변경 후 Reverb를 재시작하려면?
프로덕션은 무중단으로 php artisan reverb:restart (또는 supervisor 재시작). 개발은 런처 pane을 재시작하면 됩니다. 재시작 중 잠깐 WS가 거부해도 Realtime::fire() 가 예외를 흡수하고 폴링이 메웁니다.
관련 항목
- 원격 에뮬레이트 — Reverb 푸시를 실제로 쓰는 유일한 라이브 작업
- 보안 진단 — 원격 에뮬레이트 진행 tick이 흐르는 화면
- 개발 · 관측(Pulse) — 데몬 실행 규율 · Realtime 예외가 남는 곳
- 인프라 · 서버 — 30초 폴링 +
MAX(seq)rev 토큰의 대표 예 - 서비스 개요 — 라이브 보드 폴링 패턴
- RBAC 레벨 — 채널·폴링 엔드포인트 권한 게이트
