[실시간 트레이딩 대시보드 만들기 - 01] WebSocket 파이프라인 설계와 Blob 파싱

2026. 2. 12. 11:52·Frontend Development/[실습] 실시간 트레이딩 대시보드 만들기
728x90
반응형

업비트 Public WebSocket API를 사용해서 실시간 암호화폐 트레이딩 대시보드를 만들어본 기록입니다. 이번 1편에서는 WebSocket 연결부터 React 훅으로 래핑하는 과정까지 다룹니다.

전체 소스코드는 GitHub에 공개되어 있습니다. 직접 클론해서 돌려보실 수 있습니다.

git clone https://github.com/kimkuns91/upbit-realtime-dashboard.git
cd upbit-realtime-dashboard
npm install && npm run dev


1. 왜 WebSocket인가?

실시간 암호화폐 데이터를 프론트엔드에서 보여주려면 선택지가 몇 가지 있습니다. Polling, SSE(Server-Sent Events), WebSocket.

호가창은 초당 10~20회 업데이트됩니다. 이걸 polling으로 처리하면 서버와 클라이언트 모두 힘들어집니다.

SSE도 고려했지만, 트레이딩 대시보드 특성상 양방향 통신이 필요합니다. 사용자가 구독 종목을 바꾸거나, 차트 타임프레임을 전환하거나, 호가창 필터를 변경하면 클라이언트에서 서버로 메시지를 보내야 합니다. SSE는 서버→클라이언트 단방향이라 이런 요청을 별도의 HTTP 호출로 처리해야 합니다. WebSocket은 하나의 연결에서 양방향 메시지를 주고받을 수 있으므로 이런 구조에 훨씬 자연스럽습니다.

다만 업비트 WebSocket에는 한 가지 특이한 점이 있습니다.

2. 업비트 WebSocket의 특이점: Blob 응답

대부분의 WebSocket은 텍스트 형태로 데이터를 보내줍니다. JSON.parse(event.data) 하나면 끝납니다. 그런데 업비트는 Blob 형태로 보내줍니다.

처음에 이걸 몰라서 JSON.parse(event.data)가 왜 안 되는지 한참 찾았습니다. 실제로 필요한 파이프라인은 이렇습니다.

Blob → .text() → JSON.parse()

구현 코드입니다.

// Blob → Text → JSON 파싱 파이프라인
private async handleMessage(data: unknown): Promise<void> {
  try {
    let jsonStr: string;

    if (data instanceof Blob) {
      // 업비트 WS 응답은 Blob으로 온다
      jsonStr = await data.text();
    } else if (typeof data === 'string') {
      jsonStr = data;
    } else {
      return;
    }

    const parsed: UpbitSocketResponse = JSON.parse(jsonStr);
    this.dispatchMessage(parsed);
  } catch {
    // 파싱 실패 시 무시
  }
}

Blob.text()는 비동기입니다. 그래서 handleMessage 자체가 async여야 합니다. 이 부분을 동기로 처리하려다 삽질하는 경우가 꽤 있습니다.

3. 싱글톤 매니저 설계

WebSocket 연결을 어디에 둘 것인가가 중요한 설계 포인트입니다. 컴포넌트 안에서 new WebSocket()을 직접 호출하면 문제가 생깁니다.

  • React StrictMode에서 useEffect가 두 번 실행되면 연결이 2개 생깁니다
  • 종목을 바꿀 때마다 연결을 끊고 새로 만들면 비용이 큽니다
  • 차트, 호가창, 현재가 컴포넌트가 같은 데이터를 구독하는데 연결을 따로 만들 이유가 없습니다

그래서 React 렌더링 사이클 바깥에서 살아가는 싱글톤 클래스로 설계했습니다.

class UpbitSocketManager {
  private static instance: UpbitSocketManager | null = null;
  private ws: WebSocket | null = null;
  private listeners: Set<SocketCallbacks> = new Set();

  private constructor() {} // 외부 생성 차단

  static getInstance(): UpbitSocketManager {
    if (!UpbitSocketManager.instance) {
      UpbitSocketManager.instance = new UpbitSocketManager();
    }
    return UpbitSocketManager.instance;
  }

  // 여러 컴포넌트가 각자 콜백을 등록
  addListener(callbacks: SocketCallbacks): void {
    this.listeners.add(callbacks);
  }

  removeListener(callbacks: SocketCallbacks): void {
    this.listeners.delete(callbacks);
  }
  // ...
}

export const upbitSocket = UpbitSocketManager.getInstance();

하나의 WebSocket 연결을 여러 컴포넌트가 공유합니다. 차트 컴포넌트는 onTrade만, 호가창은 onOrderbook만 받으면 됩니다.

4. 재연결 전략: Exponential Backoff

네트워크가 불안정하면 WebSocket은 끊어집니다. 이때 즉시 재연결을 시도하면 서버에 부담을 주고, 한 번만 시도하면 복구가 안 됩니다.

Exponential backoff를 적용했습니다. 1초 → 2초 → 4초 → 8초 → ... → 최대 30초까지 간격을 늘려가며 재연결을 시도합니다.

const INITIAL_RECONNECT_DELAY = 1000;  // 1초
const MAX_RECONNECT_DELAY = 30000;     // 최대 30초
const RECONNECT_MULTIPLIER = 2;

private scheduleReconnect(): void {
  this.setStatus('reconnecting');

  this.reconnectTimer = setTimeout(() => {
    this.createConnection();
  }, this.reconnectDelay);

  // 1초 → 2초 → 4초 → 8초 → ... → 최대 30초
  this.reconnectDelay = Math.min(
    this.reconnectDelay * RECONNECT_MULTIPLIER,
    MAX_RECONNECT_DELAY
  );
}

연결에 성공하면 딜레이를 1초로 리셋합니다. 그리고 이전에 구독하던 종목을 자동으로 다시 구독합니다. 사용자 입장에서는 끊겼다 붙었을 때 자연스럽게 복구되어야 하니까요.

5. React 커스텀 훅으로 래핑

싱글톤 매니저를 React에서 쓸 때는 lifecycle 관리가 핵심입니다.

export const useUpbitSocket = ({ market, types, enabled = true }: UseUpbitSocketOptions) => {
  const { setTicker, setOrderbook, setConnectionStatus } = useRealtimeStore();
  const callbacksRef = useRef<SocketCallbacks | null>(null);

  useEffect(() => {
    if (!enabled) return;

    const callbacks: SocketCallbacks = {
      onTicker: (data) => {
        if (data.code === market) setTicker(data);
      },
      onOrderbook: (data) => {
        if (data.code === market) setOrderbook(data);
      },
      onStatusChange: (status) => setConnectionStatus(status),
    };

    callbacksRef.current = callbacks;

    // 리스너 등록 → 연결 → 구독
    upbitSocket.addListener(callbacks);
    upbitSocket.connect();
    upbitSocket.subscribe([market], types);

    // cleanup: 리스너만 해제 (연결은 유지)
    return () => {
      if (callbacksRef.current) {
        upbitSocket.removeListener(callbacksRef.current);
        callbacksRef.current = null;
      }
    };
  }, [market, enabled]);
  // ...
};

여기서 주의할 점이 두 가지 있습니다.

cleanup에서 disconnect()를 호출하지 않습니다. 컴포넌트가 언마운트돼도 다른 컴포넌트가 같은 연결을 쓰고 있을 수 있습니다. cleanup에서는 리스너만 해제합니다.

callbacksRef로 콜백 참조를 관리합니다. cleanup 시점에 등록한 콜백과 동일한 참조를 제거해야 하므로 ref에 저장해둡니다.

6. Next.js SSR 트러블슈팅

Next.js는 서버에서 먼저 렌더링합니다. WebSocket이나 window 객체에 접근하는 코드가 서버에서 실행되면 에러가 납니다.

해결법은 간단합니다.

  • WebSocket을 사용하는 컴포넌트에 'use client' 디렉티브를 붙입니다
  • useEffect 안에서만 WebSocket 관련 로직을 실행합니다
  • 싱글톤 매니저는 import만으로는 WebSocket 인스턴스를 만들지 않습니다. connect()를 호출해야 생성되므로 안전합니다

정리

이번 글에서 구현한 것은 다음과 같습니다.

  • UpbitSocketManager: React 외부의 싱글톤 WebSocket 매니저
  • Blob → JSON 파싱: 업비트 특유의 응답 포맷 처리
  • Exponential backoff: 안정적인 재연결 전략
  • useUpbitSocket: React lifecycle에 맞춘 커스텀 훅

다음 글에서는 이 파이프라인 위에 TradingView Lightweight Charts로 실시간 캔들 차트를 올리는 과정을 다룹니다.

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'Frontend Development > [실습] 실시간 트레이딩 대시보드 만들기' 카테고리의 다른 글

[실시간 트레이딩 대시보드 만들기 - 04] 회고 — 연습용 프로젝트와 실전의 거리  (0) 2026.02.12
[실시간 트레이딩 대시보드 만들기 - 03] 초당 20회 업데이트되는 호가창, 렌더링 횟수를 6,900배 줄인 방법  (0) 2026.02.12
[실시간 트레이딩 대시보드 만들기 - 02] TradingView Charts로 실시간 캔들 차트 구현  (0) 2026.02.12
'Frontend Development/[실습] 실시간 트레이딩 대시보드 만들기' 카테고리의 다른 글
  • [실시간 트레이딩 대시보드 만들기 - 04] 회고 — 연습용 프로젝트와 실전의 거리
  • [실시간 트레이딩 대시보드 만들기 - 03] 초당 20회 업데이트되는 호가창, 렌더링 횟수를 6,900배 줄인 방법
  • [실시간 트레이딩 대시보드 만들기 - 02] TradingView Charts로 실시간 캔들 차트 구현
Kun Woo Kim
Kun Woo Kim
안녕하세요, 김건우입니다! 웹과 앱 개발에 열정적인 전문가로, React, TypeScript, Next.js, Node.js, Express, Flutter 등을 활용한 프로젝트를 다룹니다. 제 블로그에서는 개발 여정, 기술 분석, 실용적 코딩 팁을 공유합니다. 창의적인 솔루션을 실제로 적용하는 과정의 통찰도 나눌 예정이니, 궁금한 점이나 상담은 언제든 환영합니다.
  • Kun Woo Kim
    WhiteMouseDev
    김건우
  • 깃허브
    포트폴리오
    velog
  • 전체
    오늘
    어제
  • 공지사항

    • [인사말] 이제 티스토리에서도 만나요! WhiteMouse⋯
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 분류 전체보기 (151) N
      • Frontend Development (68) N
        • [실습] 실시간 트레이딩 대시보드 만들기 (4) N
      • Backend Development (28)
      • AI · ML (4)
        • Computer Vision (4)
      • Algorithm (35)
        • 백준 (11)
        • 프로그래머스 (18)
        • 알고리즘 (5)
      • Infra (1)
      • 자료구조 (4)
      • Language (6)
        • JavaScript (6)
      • 자격증 공부 (1)
        • GCP Developer (1)
      • Tools (1) N
  • 링크

    • Github
    • Portfolio
    • Velog
  • 인기 글

  • 태그

    컴퓨터비전
    mlops
    tailwindcss
    객체탐지
    AI개발
    Human-in-the-Loop
    backend
    claudecode
    Qwen2.5-VL
    flat layout
    AI
    MSA
    YOLO
    CNN
    AgentTeams
    개발자도구
    모델비교
    Nginx
    Vision-Language-Model
    Object Detection
    multiagent
    transformer
    SRC
    딥러닝
    API Gateway
    src layout
    Infra
    Nextjs
    frontend development
    rt-detr
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Kun Woo Kim
[실시간 트레이딩 대시보드 만들기 - 01] WebSocket 파이프라인 설계와 Blob 파싱
상단으로

티스토리툴바