18.5 웹 브라우저 기반의 커스텀 시각화 (TypeScript 활용)

18.5 웹 브라우저 기반의 커스텀 시각화 (TypeScript 활용)

Grafana 가 훌륭하지만, 경영진은 “그라파나 화면” 이 아니라 “우리 회사의 로고가 박힌 자체 통제 웹사이트” 를 원한다. 또는 로봇의 전진/후진 버튼을 눌러야 하는 쌍방향(Interactive) 제어판이 보장되어야 한다면 더 이상 기존 툴에 기댈 수 없다.

이 장에서는 프론트엔드 제국의 표준 언어인 TypeScript 와 현대적인 웹 프레임워크(React/Vue) 를 조합하여, 브라우저가 직접 Zenoh 네트워크의 일원(Client) 으로 뛰어들어 데이터를 빨아들이고 WebGL 렌더링을 격발시키는 커스텀 관제탑 구축 런북을 완성한다.

1. Zenoh REST API와 TypeScript를 이용한 데이터 패칭

초당 1번만 변하는 숫자를 그리기 위해 무거운 WebSocket 과 Zenoh 라이브러리를 동원하는 것은 닭 잡는 데 소 잡는 칼을 쓰는 격이다.

1.0.1 [인스펙션] 라이트웨이트 폴링(Polling) 전술

가장 가볍고 범용적인 브라우저 fetch 스펙을 활용한다.

1. 라우터 내장 REST API 쿼리
Zenoh 라우터는 8000번 포트로 HTTP 를 항상 열어두고 있다.

// 브라우저 프론트엔드 코드
async function fetchRobotBattery() {
  // 로봇 배터리 상태를 HTTP Get 으로 단발성 조회
  const response = await fetch('http://router-ip:8000/factory/robot1/battery');
  const data = await response.json();
  // [{ key: 'factory/robot1/battery', value: '87' }]
  updateBatteryUI(data[0].value);
}
// 5초마다 호출
setInterval(fetchRobotBattery, 5000);

2. 캐시 보장(Caching) 과 CORS 우회
클라이언트(브라우저) 1만 명이 동시에 fetch 를 때리면 라우터 웹 서버가 죽는다. 클라우드에 Nginx 프록시를 둬서 proxy_cache 5s 를 걸어주면, 1만 명의 API 쿼리를 단 1개의 Zenoh 라우터 쿼리로 압축시킬 수 있다. (더불어 브라우저의 악명 높은 CORS 에러도 프록시 선에서 종결된다.)

2. Zenoh-TypeScript 라이브러리와 WebSocket 연동 기반 실시간 UI 구현

조이스틱을 움직였는데 로봇이 0.1초 만에 반응해야 한다면, HTTP 의 매번 연결/끊음(Handshake) 오버헤드는 사형 선고다.

2.0.1 [Runbook] 브라우저 직접 구동(Direct Binding) 런북

Zenoh 가 공식 지원하는 TypeScript API (@eclipse-zenoh/zenoh-ts) 를 npm 으로 설치해 브라우저에 구워버린다.

1. 라이브러리 초기화 (WASM 기반)
Zenoh 의 JS 코어는 사실 내부적으로 WebAssembly(Rust) 로 컴파일되어 돌아간다. 브라우저가 곧 C++ 프로그램을 돌리는 엔진이 된다는 뜻이다.

import { connect } from '@eclipse-zenoh/zenoh-ts';

// 웹소켓을 통해 클라우드 라우터로 징검다리 터널을 뚫는다.
const session = await connect({ locators: ['ws/router-ip:8000'] });

2. Event Listener 스트리밍 렌더링
REST API 가 “대답해 줘” 라면, 웹소켓은 “네가 밀어줘” 다.

// 로봇의 GPS 좌표가 변할 때마다 브라우저 콜백이 빛의 속도로 터진다.
await session.subscribe('robot/*/gps', (sample) => {
  const robotId = extractId(sample.keyExpr);
  const { lat, lng } = JSON.parse(sample.value);
  moveMarkerOnMap(robotId, lat, lng); // 지도 핀 이동
});

이 방식의 최대 강점은, 웹 애플리케이션 자체가 완벽한 “Zenoh Client Node” 의 신분을 획득한다는 것이다. 당신의 웹 브라우저가 Admin Space 토폴로지 그림에 당당히 잎사귀(Leaf) 노드로 렌더링 된다.

3. React 및 Vue.js 프레임워크에서의 Zenoh 데이터 상태 관리

데이터를 실시간으로 받아왔다. 그런데 이 데이터가 1초에 1,000번 들어온다고 치자.
React 나 Vue 같은 프론트엔드 프레임워크는 상태(State, useState) 가 바뀔 때마다 HTML 에 가상 돔(Virtual DOM) 을 다시 그린다. 1초에 HTML 을 천 번 그리면? 여러분의 노트북 팬이 비명을 지르며 뻗어버릴 것이다.

3.0.1 [인스펙션] 가상 돔 방어벽 구축 전술

고속 통신망과 느린 화면 사이의 완충 장치설계다.

1. 쓰로틀링(Throttling) 과 렌더링 분리
프론트엔드 상태 머신(Redux/Zustand 등) 에 데이터를 넣는 속도와 그리는 속도를 분리하라.

// Bad: 데이터 올 때마다 UI 그림
session.subscribe('data', (val) => setReactState(val)); 

// Good: 1초에 모인 데이터를 한 번에 그림 (Batching)
let buffer = [];
session.subscribe('data', (val) => buffer.push(val));
setInterval(() => {
  if (buffer.length > 0) {
    setReactState(buffer); // UI 업데이트는 초당 10번(100ms) 으로 제한
    buffer = [];
  }
}, 100);

2. 불변성(Immutability) 무시를 통한 렌더링 해킹
정말로 초당 60 프레임의 부드러운 아날로그 바늘 계기판을 돌려야 한다면, 프론트엔드 프레임워크의 상태 관리를 아예 우회하라.
React 상태(setState) 를 거치지 않고, useRef 로 브라우저 기본 HTML div 태그를 직접 잡아챈 다음, divRef.current.style.transform = ... 처럼 직접 CSS (Hardware-accelerated) 를 건드리는 것이 Web 렌더링 부하를 회피하는 타짜들의 해킹 기술이다.

4. 고주파수(High-frequency) 센서 데이터의 브라우저 렌더링 최적화 방안

IoT 시계열 센서나 진동 모터(1KHz) 가 보내는 선형 데이터 그래프 표출 전략.

4.0.1 [Runbook] 캔버스(Canvas) 드로잉 최적화

수십만 개의 점(Point) 을 잇는 거대한 선 그리기는 HTML DOM 구조의 천적이다.

1. DOM 포기 및 Canvas 2D 침투
점 하나 그릴 때마다 <svg><path> 의 텍스트가 갱신되게 만들면 브라우저는 무너진다.
Chart.js 는 버리고 uPlot 이나 SciChart 처럼 아예 브라우저의 픽셀(Pixel) 에 직접 물감을 칠하는 <canvas> 전용 차트 엔진을 채택해야 한다. 이들은 수백만 개의 포인트를 그려내도 CPU 사용량을 5% 미만으로 틀어막는다.

2. Web Worker(워커 스레드) 격리 연산
Zenoh 로부터 들어오는 방대한 바이너리를 JSON 으로 풀고(JSON.parse) 가공하는 연산 지연시간이 생기면 곧장 마우스 커서가 뚝뚝 끊기는 프리징(Freezing) 이 발생한다.
메인 JS 쓰레드는 오직 점을 “그리는” 행위만 하게 둔다.
그리고 배경 백그라운드 스레드(Web Worker) 쪽에 Zenoh 클라이언트를 처박아 넣고, 그곳에서 압축을 풀게 만든 뒤 예쁘게 가공된 [x,y] 좌표 배열만 메인 쓰레드로 토스(postMessage) 하는 멀티 스레드 아키텍처가 프론트엔드 최적화의 끝판왕이다.

5. 3D 데이터 시각화: Three.js 및 WebGL과의 통합 연동

공장 바닥 2D 지도 위에서 점이 움직이는 건 장난감 수준이다. 디지털 트윈(Digital Twin) 의 로망은 로봇 관절의 미세한 떨림과 라이다 레이저 빔이 관제탑 화면에 3D 로 똑같이 복제되는 것이다.

5.0.1 [인스펙션] 디지털 트윈(Digital Twin) 동기화 전술

Three.js 렌더링 루프와 Zenoh 네트워크의 궁극적 하모니(Harmony) 구조 설계다.

1. 프레임 렌더 루프(requestAnimationFrame) 편승
3D 엔진은 1초에 60번 배경을 지우고 다시 그린다(render loop).
Zenoh 쪽에서 데이터가 들어올 때 화면을 그리는 것이 아니라, 데이터가 들어오면 전역 메모리 변수(예: robotArmAngle = 45) 에 숫자만 살포시 바꿔 놓는다. 그리고 Three.js 의 랜더 루프가 빙글빙글 돌다가 무심결에 그 숫자를 집어넣고 화면 뼈대(Bone) 의 트랜스폼 값을 갱신하게 하여 스레드 경합(Jitter) 을 제거하라.

2. 쿼터니언(Quaternion) 네트워크 동기
로봇이 “회전” 했다는 정보(오일러 각도 x, y, z) 를 그냥 보내면 짐벌 락(Gimbal Lock) 이나 중간 렌더링의 버벅거림(Interpolation 에러) 이 발생한다.
로봇(C++) 은 무조건 계산이 완료된 4차원 쿼터니언 값 [w, x, y, z] 을 바이너리 팩(MsgPack) 으로 Zenoh 에 쏘고, 브라우저의 Three.js 도 object.quaternion.set(x, y, z, w) 함수를 호출하여 중간 수학 계산(CPU) 을 하나도 남김없이 증발시키는 것이 완벽히 반응하는 리얼타임 디지털 트윈의 비밀이다.