~/web-app-dev.md
WEB_APP_DEV

Supabase Realtime binary payload는 “JSON 최적화”가 아니라 클라이언트 호환성 계약이다

10분 읽기·2026.07.26·출처 4·00
오늘의 질문

실시간 기능에서 payload를 binary로 바꾸면 서버 비용만 줄고 끝일까?

web-app-dev.md
Realtime binary contract

Supabase Realtime binary payload는 “JSON 최적화”가 아니라 클라이언트 호환성 계약이다

1. 왜 지금 봐야 하나

Supabase의 2026년 7월 Developer Update에는 Realtime Broadcast가 JSON뿐 아니라 binary payload도 주고받을 수 있다는 소식이 들어갔다. Changelog는 sensor telemetry, GPS 좌표, accelerometer 값, live screenshot streaming처럼 숫자가 많거나 이미 binary인 데이터를 JSON text로 감싸지 않고 보낼 수 있다고 설명한다. REST API, client library의 WebSocket, database 함수 realtime.send_binary()까지 세 경로에서 binary broadcast를 지원한다는 점도 중요하다.

하지만 이 기능을 “더 빠른 실시간 메시지” 정도로만 보면 위험하다. 공식 문서는 최소 버전을 분명히 적는다. WebSocket 수신은 supabase-js 2.91.0 이상, supabase-swift 2.44.0 이상에서 자동 처리된다. REST httpSend()의 binary payload는 supabase-js 2.107.0 이상이 필요하다. Realtime server는 2.103.2 이상이 필요하다. 그리고 가장 운영적으로 중요한 문장: 지원하지 않는 구버전 SDK 또는 아직 지원하지 않는 Dart, Kotlin, Python client로 보낸 binary 메시지는 조용히 drop될 수 있다.

웹앱 개발자에게 이건 단순 성능 옵션이 아니다. payload encoding을 바꾸는 순간 서버, 브라우저, 모바일 앱, SDK 버전, REST endpoint, DB 함수, fallback format이 동시에 맞아야 한다. 오늘의 핵심 문장: 실시간 binary payload는 “데이터를 작게 보내는 기술”이 아니라 “누가 어떤 형식을 이해하는지 명시하는 호환성 계약”이다.

2. 핵심 개념

Realtime Broadcast는 보통 “topic에 event를 뿌린다”로 이해한다. JSON만 쓸 때는 payload가 객체인지 문자열인지 정도만 신경 쓰면 된다.

zsh — 생존확인.sh
await channel.send({
  type: 'broadcast',
  event: 'cursor-pos',
  payload: { x: 120, y: 240 },
})

binary를 쓰면 질문이 바뀐다.

zsh — 생존확인.sh
await channel.send({
  type: 'broadcast',
  event: 'cursor-pos',
  payload: new Uint8Array([120, 0, 240, 0]).buffer,
})

이제 receiver는 { x, y } 객체를 받는 것이 아니라 byte 배열을 해석해야 한다. 첫 2바이트가 x인지, little-endian인지, version byte가 있는지, 좌표 단위가 pixel인지 normalized value인지 알아야 한다. 즉 payload format은 API 스키마가 된다.

Supabase Realtime Protocol 문서를 보면 이 변화가 더 분명하다. protocol v2.0.0은 text frame뿐 아니라 binary WebSocket frame을 지원한다. binary broadcast frame에는 payload encoding byte가 있으며 0 = binary, 1 = JSON으로 구분된다. USER_BROADCAST_PUSH, USER_BROADCAST 같은 타입 코드와 topic, event, metadata, user payload가 정해진 순서로 들어간다.

여기서 개발자가 가져갈 개념은 “binary는 JSON의 압축판이 아니다”이다. JSON은 self-describing에 가깝다. key 이름이 있고, 사람이 로그에서 읽을 수 있고, schema drift를 어느 정도 눈으로 발견할 수 있다. binary는 작고 빠를 수 있지만, 해석 규칙을 코드 밖에서 합의해야 한다. 그래서 binary payload를 도입할 때는 다음 세 가지를 같이 설계해야 한다.

  1. format version: payload 첫 byte나 metadata에 버전을 둔다.
  2. decoder ownership: web, iOS, backend worker가 같은 decoder 규칙을 공유한다.
  3. fallback path: binary를 못 받는 client가 JSON event나 capability check로 빠질 길을 둔다.

3. 최신 이슈와 연결

공식 Changelog는 binary payload가 모든 전송 경로에서 가능하다고 말한다.

  • Client libraries over WebSocket: ArrayBuffer 또는 ArrayBufferView를 payload로 전달
  • REST API: single-message endpoint에서 Content-Typeapplication/json인지 application/octet-stream인지로 payload type 결정
  • Database: realtime.send_binary()bytea payload 전송

Broadcast 문서는 REST 예시도 바꿨다. 단일 메시지는 topic과 event가 path에 들어간다.

zsh — 생존확인.sh
curl -v \
  -H 'apikey: <SUPABASE_TOKEN>' \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @payload.bin \
  'https://<PROJECT_REF>.supabase.co/realtime/v1/api/broadcast/test/events/event?private=true'

반면 batch endpoint POST /realtime/v1/api/broadcast는 JSON body의 messages 배열을 받으며 JSON payload 전용으로 남아 있다. 이 차이는 실무에서 중요하다. 운영자가 “batch로 여러 binary frame을 보내자”고 생각하면 공식 경로와 어긋난다. binary는 single-message endpoint, WebSocket send, database send_binary()처럼 지원되는 경로별 제약을 확인해야 한다.

또 하나의 연결점은 private channel이다. 문서는 database broadcast의 is_private flag가 누가 subscribe할 수 있는지를 제어한다고 설명한다. binary broadcast도 public/private matching rule을 따른다. binary를 쓰더라도 auth, topic naming, RLS 기반 private channel 검증은 사라지지 않는다. payload가 byte가 되면 오히려 로그와 디버깅이 어려워지므로 권한 경계는 더 명시적이어야 한다.

4. 개발자 관점 해석

실시간 웹앱에서 binary payload가 매력적인 순간은 분명하다.

  • 협업 캔버스에서 cursor, brush stroke, viewport matrix를 초당 여러 번 보낼 때
  • IoT dashboard에서 수천 개 기기의 numeric reading을 계속 받을 때
  • 고객 지원 도구에서 작은 screenshot frame이나 binary diff를 broadcast할 때
  • 게임/멀티플레이 UI에서 위치, 입력, tick 정보를 촘촘히 보낼 때

하지만 대부분의 제품은 “모든 것을 binary로 바꾸자”가 답이 아니다. JSON으로 충분한 이벤트는 JSON으로 남기는 편이 낫다. 예를 들어 user_joined, toast, notification, document_renamed 같은 이벤트는 빈도도 낮고 디버깅 가능성이 중요하다. 반대로 high-frequency numeric stream은 binary 후보가 된다.

실무 기준은 이렇게 잡을 수 있다.

질문JSON 유지binary 검토
사람이 로그에서 바로 읽어야 하나?아니오
payload가 초당 많이 반복되나?아니오
숫자/좌표/프레임처럼 고정 폭 구조인가?아니오
모든 client SDK가 최소 버전을 만족하나?불확실확인됨
구버전 client drop을 감당할 수 있나?아니오fallback 있음

AI 코딩 도구를 쓰는 팀이라면 더 조심해야 한다. “Supabase Realtime binary payload로 최적화해줘”라고 지시하면 에이전트는 Uint8Array 예시만 만들고 decoder, versioning, SDK minimum, fallback, observability를 빠뜨릴 수 있다. 좋은 지시는 다음처럼 더 구체적이어야 한다.

cursor-pos event만 binary v1으로 바꾸되, payload 첫 byte는 version, 다음 2바이트는 little-endian x, 다음 2바이트는 little-endian y로 한다. receiver가 binary를 지원하지 않거나 decoder error가 나면 cursor-pos-json event를 사용한다. web client는 supabase-js >= 2.91.0을 package.json과 runtime check에 반영한다.

5. 내 프로젝트에 적용할 체크포인트

  1. 이벤트별 payload inventory를 만든다

    • cursor-pos, typing, presence, chat-message, screen-frame처럼 event를 나눈다.
    • 빈도, 평균 크기, 사람이 읽어야 하는지, 유실 가능 여부를 표시한다.
  2. binary 후보는 format spec을 먼저 쓴다

    • byte order, field width, version, optional field 처리 방식을 문서화한다.
    • TypeScript decoder와 encoder를 같은 테스트 fixture로 검증한다.
  3. SDK 최소 버전을 배포 조건으로 건다

    • web: @supabase/supabase-js >= 2.91.0 for WebSocket receive
    • REST httpSend() binary: @supabase/supabase-js >= 2.107.0
    • iOS: supabase-swift >= 2.44.0
    • Dart/Kotlin/Python client가 끼어 있으면 binary event를 필수 기능으로 만들지 않는다.
  4. silent drop을 장애로 관측한다

    • sender ack만 보고 성공으로 판단하지 않는다.
    • receiver가 일정 시간 안에 “binary-capable” heartbeat나 app-level ack를 보내는지 본다.
    • 구버전 client 비율을 feature flag rollout 지표에 넣는다.
  5. 권한 경계를 JSON 때와 동일하게 둔다

    • private channel 여부, topic naming, auth token refresh, RLS 정책을 그대로 점검한다.
    • binary payload 안에 민감 정보를 숨겼다고 생각하지 않는다. binary는 암호화가 아니다.

6. 오늘 10분 액션

오늘은 코드를 크게 바꾸지 말고 “binary 도입 가능성 표”만 만들어보자.

zsh — 생존확인.sh
| event | 현재 format | 초당 빈도 | 평균 크기 | 유실 허용 | binary 후보 | fallback |
| --- | --- | ---: | ---: | --- | --- | --- |
| cursor-pos | JSON {x,y} | 20/user | 18~40B+overhead | 예 | 예 | cursor-pos-json |
| chat-message | JSON object | 낮음 | 다양 | 아니오 | 아니오 | 유지 |
| screen-frame | base64 JSON | 5/viewer | 큼 | 예 | 예 | JPEG URL 또는 낮은 FPS |

그다음 package.json에서 @supabase/supabase-js 버전을 확인하고, 모바일/서버 worker가 같은 event를 구독하는지 적어라. binary 전환은 성능 작업처럼 보이지만 실제로는 release coordination 작업이다.

7. 더 볼 자료

중복 회피 메모

로컬 content/generated와 Supabase 최근 web_app_dev 글을 확인했다. 최근 글은 Hono Object.create(null) 입력 객체 경계, Next.js Partial Prefetching/App Shell, Supabase Pipelines/CDC 분석 분리, Next.js 월간 보안 릴리스, SWC WASM/lockfile, React Router Web Streams, TypeScript 7, pg_graphql introspection, PostgREST JWT kid, Supabase Auth URL 라우팅을 다뤘다. 2026-06-13 글에서 Supabase Realtime과 ETL을 비교한 적은 있지만, 이번 글은 Realtime Broadcast의 binary payload 지원을 payload encoding, SDK 최소 버전, silent drop, REST/database/WebSocket 전송 경로 차이, fallback 설계라는 클라이언트 호환성 계약 관점으로 다룬다.

오늘 10분 액션+5%

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)