JSON Toolkit

바이너리 JSON: MessagePack, CBOR, BSON 비교

핵심 요약: MessagePack, CBOR, BSON은 모두 JSON을 바이트로 압축합니다. 바이트 비용, int64와 바이너리 처리 방식을 비교하고, 전환할 만한 기준을 명확히 제시합니다.

중급약 10분 소요2026-10-31 발행2026-10-31 업데이트
MessagePack, CBOR, BSON 바이너리 JSON 형식 비교 표지

핵심부터 말하면, 외부 교환과 사람이 검사해야 하는 데이터에는 텍스트 JSON을 유지하고, 내부 링크에서만 바이너리 페이로드, 2^53을 넘는 정수, 정확한 십진수가 압축 크기나 타입 표현력을 진짜 문제로 만든다면 MessagePack이나 CBOR로 전환하세요. BSON은 MongoDB 생태계의 것입니다.

텍스트 JSON은 공백을 아끼고, 바이너리 형식은 구두점과 숫자의 텍스트 형태를 아낍니다. 필드 이름은 여전히 레코드마다 한 번씩 나타나므로, 전환 전에 실제 절감량을 따져 보세요.

1. 바이너리 JSON이 실제로 아끼는 것

기대치를 먼저 설정합니다. 이 형식들은 "또 다른 JSON 문법"이 아닙니다. 같은 데이터 모델을 바이트로 인코딩합니다. 객체, 배열, 문자열, 숫자, 불리언, null. 없애는 것은 구체적입니다:

  • 따옴표, 콜론, 쉼표, 중괄호 같은 구조 문자
  • 숫자의 십진 텍스트 형태. 그래서 32는 한 바이트가 됩니다
  • 문자열 경계를 다시 스캔하는 일. 고정 길이 접두사로 대체됩니다
  • 바이너리 페이로드의 Base64 인코딩. 바이트로 직접 저장할 수 있습니다

없애지 못하는 것도 똑같이 구체적입니다. 필드 이름은 여전히 모든 레코드에 문자열로 나타납니다. MessagePack 맵, CBOR 맵, BSON 문서 모두 항목마다 키를 반복합니다. 따라서 JSON이 같은 모양의 레코드 1만 개라면, 텍스트와 바이너리의 압축 크기 차이는 직관보다 훨씬 작습니다. gzip이 반복되는 필드 이름을 없애는 데 가장 능하기 때문입니다.

2. 세 형식의 유래

형식 명세와 연도 기원 대표 용도
MessagePack msgpack.org 명세, 2008년부터 미니멀리즘을 겨냥한 독립 설계 캐시 값, IPC, 로그 파이프라인
CBOR RFC 8949(STD 94), 2020년 MessagePack의 영향을 받아 IETF가 표준화 COSE, WebAuthn, CWT, 제약 장치
BSON bsonspec.org, MongoDB 생태계 데이터베이스 문서를 위해 설계 MongoDB 저장 및 통신 프로토콜

CBOR가 가장 표준화되어 있습니다. IANA 등록 application/cbor 미디어 타입, 결정적 인코딩 규칙, 설명을 위한 CDDL. MessagePack은 가장 가볍고 명세가 한 페이지에 들어갑니다. BSON은 가장 무겁습니다. ObjectId, Decimal128, 날짜, 바이너리 하위 타입을 타입 시스템에 내장하므로, MongoDB 생태계 안에서만 진짜 인기가 있습니다.

3. 같은 데이터, 네 가지 인코딩

{"name":"joe"}를 예로 봅니다. 바이트 비용은 쉽게 비교됩니다:

인코딩 바이트 바이트 시퀀스
JSON 텍스트 14 7b 22 6e 61 6d 65 22 3a 22 6a 6f 65 22 7d
MessagePack 9 81 a3 6e 61 6d 65 a3 6a 6f 65
CBOR 10 a1 64 6e 61 6d 65 63 6a 6f 65
BSON 19 4바이트 전체 길이 + 타입 바이트 + name\0 + 4바이트 값 길이 + joe\0 + 종료자

MessagePack의 아홉 바이트를 뜯어 봅니다:

  • 81: 상위 니블 1000은 맵, 하위 니블 0001은 키-값 쌍 하나
  • a3: 상위 세 비트 101은 길이 3의 고정 길이 문자열
  • 6e 61 6d 65: name의 ASCII
  • a3: 길이 3인 또 다른 문자열
  • 6a 6f 65: joe

CBOR의 a1은 major type 5(맵)에 길이 1, 64는 major type 3(텍스트 문자열)에 길이 4라, 의미가 MessagePack과 거의 동형입니다. BSON은 더 큽니다. 모든 문서가 4바이트 전체 길이를 지니고, 모든 필드가 타입 바이트와 이름을 반복하며, 모든 문자열이 길이와 끝의 \0을 반복합니다. 작은 문서에서는 BSON이 JSON 텍스트보다 쉽게 커질 수 있습니다.

해당 바이트를 hex로 렌더링해 접두사를 직접 확인할 수 있습니다:

const messagePackBytes = [0x81, 0xa3, 0x6e, 0x61, 0x6d, 0x65, 0xa3, 0x6a, 0x6f, 0x65];
const hexText = messagePackBytes.map((byte) => byte.toString(16)).join(' ');
console.log(hexText); // 81 a3 6e 61 6d 65 a3 6a 6f 65

3.1 크기와 파싱 속도: 마케팅 수치를 믿지 말 것

바이너리가 "10배 빠르다"는 주장은 두 질문으로 나눠야 합니다.

크기. 압축 전에는 구두점과 공백이 사라져 바이너리가 더 작습니다. 하지만 실제 파이프라인은 거의 항상 압축합니다. gzip이나 brotli를 거치면 반복 필드 이름이 압축되어 사라지고, 바이너리의 이점은 크게 줄어듭니다. JSON 압축과 gzip 비교의 방법을 따르세요. 먼저 압축 바이트를 측정한 뒤 형식 변경이 값어치가 있는지 결정합니다.

파싱 속도. 바이너리는 렉싱(따옴표, 쉼표, 숫자 문자 스캔)을 건너뛰므로 이론상 빠릅니다. 그러나 JavaScript에서는 지배적인 비용이 객체와 메모리 할당이며, 어떤 디코더도 이를 피할 수 없습니다. 데이터 모양, 필드 수, 구현 품질이 모두 중요하므로, 믿을 만한 결론은 벤더의 배수 수치를 인용하는 것이 아니라 자체 페이로드를 벤치마킹해서만 나옵니다. 단일 페이로드가 한 번에 메모리에 올리기엔 너무 크다면 대용량 JSON 파싱과 스트리밍의 방법으로 잘라낸 뒤 인코딩 이득을 측정하세요.

4. int64와 바이너리 필드: 전환의 진짜 이유

크기는 형식을 바꾸는 이유가 아닌 경우가 많습니다. 타입 표현력이 이유입니다.

4.1 2^53을 넘는 정수

JSON 숫자는 JavaScript에서 더블로 귀결되므로 9007199254740993은 9007199254740992가 됩니다. 세 바이너리 형식은 이를 다르게 처리합니다:

형식 64비트 정수 부호 없는 64비트 임의 정밀도
MessagePack ✓ int64(0xd3), BigInt로 디코딩 가능 ✓ uint64(0xcf) ✗ (확장 타입이 필요)
CBOR ✓ major type 0/1의 64비트 형태 ✓ 같은 인코딩 공간 ✓ bignum 태그 2/3
BSON ✓ int64(0x12) ✗ 부호 있음만 ✗ (Decimal128는 고정 소수점이며 임의 정수가 아님)

임의 정밀도 정수에 대한 표준 표현이 있는 것은 CBOR뿐입니다. 64비트를 넘는 값은 바이트 문자열에 태그 2(음수 아님) 또는 태그 3(음수)로 감쌉니다. MessagePack과 BSON은 문자열이나 자체 확장 관례로 대체합니다.

4.2 바이너리 페이로드

JSON에는 바이너리를 표현하는 방법이 정확히 하나입니다. Base64. 페이로드를 대략 1/3 부풀리고 추가 디코드 복사를 요구합니다. MessagePack에는 bin 타입(0xc4–0xc6), CBOR에는 바이트 문자열(major type 2), BSON에는 바이너리 하위 타입이 있습니다. 이미지, 해시, 벡터, 압축 블록을 전송할 때 이 차이는 구두점 몇 바이트를 아끼는 것보다 훨씬 중요합니다.

5. 나란히 비교

차원 MessagePack CBOR BSON
명세 커뮤니티 명세 IETF RFC 8949 MongoDB 생태계
미디어 타입 application/msgpack (IANA 표준 아님) application/cbor (IANA 등록) 범용 미디어 타입 없음
타입 시스템 여섯 JSON 타입 + bin, ext 여섯 JSON 타입 + bin, 태그, 단순 값 더 광범위: ObjectId, Decimal128, 날짜, 정규식, 타임스탬프
확장 메커니즘 확장 타입(예: timestamp -1) 태그(날짜, bignum, 내장 CBOR) 고정 타입 코드, 생태계 관례에 의한 확장
결정적 인코딩 표준 요구 없음 명시적 규칙, 서명에 사용 가능 없음
자기 서술 가능하나 필드 수준 스키마 없음 가능하며 CDDL로 설명 가능 가능하나 문서마다 필드 이름 반복
작은 문서 오버헤드 낮음 낮음 높음(길이 접두사, 필드 이름, 종료자)
생태계 캐시, 로그, 일부 RPC COSE, WebAuthn, IoT MongoDB 전용

6. 브라우저와 Node 지원

Node 24에는 이 코덱이 없어 라이브러리를 설치합니다. 흔한 선택:

런타임 MessagePack CBOR BSON
Node.js @msgpack/msgpack, msgpackr cbor-x, borc bson
브라우저 @msgpack/msgpack cbor-x 브라우저 빌드, cbor-web bson

두 계열 모두 Uint8Array에서 동작하고 Buffer를 필요로 하지 않습니다. 버전 차이를 주의하세요:

import { encode, decode } from '@msgpack/msgpack';

/**
 * 객체를 MessagePack 바이트로 인코딩하고 검증을 위해 즉시 디코딩합니다.
 * @param payload 인코딩할 일반 객체
 * @returns 디코딩된 값과 인코딩된 바이트 길이
 */
function roundTrip(payload: Record<string, unknown>): { decoded: unknown; byteLength: number } {
  const bytes = encode(payload);
  return { decoded: decode(bytes), byteLength: bytes.byteLength };
}

대부분 라이브러리는 큰 정수를 기본적으로 number로 디코딩해 안전 범위를 넘으면 정밀도를 잃습니다. 정확한 값이 중요하다면 BigInt 옵션을 명시적으로 켜세요(@msgpack/msgpack의 useBigInt64, cbor-x의 동등 스위치). 그렇지 않으면 JSON의 옛 문제를 새 형식으로 옮긴 것에 불과합니다.

7. 디버깅 가능성과 프로토콜 호환성

7.1 디버깅 가능성: 가장 큰 숨은 비용

텍스트 JSON의 가장 큰 이점은 크기가 아니라 언제든 열어 볼 수 있다는 것입니다. 바이너리로 전환하면:

  • grep, awk, jq가 모두 동작을 멈추고, 로그 파이프라인에 디코드 단계가 추가로 필요합니다
  • 운영 디버깅에는 올바른 버전의 올바른 디코더가 필요하며, 아니면 보이는 필드 이름이 틀립니다
  • 캡처한 트래픽이 브라우저 개발자 도구에 읽을 수 없는 바이트 덩어리로 나타납니다
  • 코드 리뷰 diff에 16진 문자열이 표시됩니다

CBOR에는 진단 표기법이 있어 바이트를 JSON에 가까운 읽을 수 있는 텍스트로 렌더링합니다(예: 맵을 {1: 2}로). MessagePack에도 몇 가지 명령행 디코더가 있습니다. 그러나 모두 추가 설치가 필요하고 표준 작업 경로 밖에 있습니다.

결론: 사람이 데이터를 자주 검사해야 한다면 바이너리 형식의 숨은 비용이 절감한 대역폭을 넘어섭니다.

7.2 프로토콜 호환성과 확장

  • CBOR는 가장 완전한 태그 메커니즘을 갖습니다. 날짜/시간 문자열에는 태그 0, Unix 타임스탬프에는 태그 1, bignum에는 태그 2와 3, 내장 CBOR에는 태그 24. 결정적 인코딩과 결합되어 COSE, WebAuthn, CWT처럼 서명과 구현 간 일관성이 필요한 프로토콜이 사용합니다.
  • MessagePack은 확장 타입을 구현자에게 맡깁니다. 실무에서 흔한 것은 timestamp(확장 타입 -1) 하나뿐입니다. 팀 간에는 모든 확장 타입을 프로토콜 문서에 적어야 하며, 아니면 상대가 디코딩할 수 없습니다.
  • BSON은 고정 타입 코드를 가지며, ObjectId, Decimal128, MinKey, MaxKey는 MongoDB 특유의 의미입니다. BSON을 범용 교환 형식으로 쓰면 쓰지 않는 타입과 길이 접두사가 한 무더기 따라옵니다.

프로토콜이 "같은 데이터는 같은 바이트를 생성해야 한다"를 요구하면 표준 답을 제공하는 것은 CBOR뿐입니다. MessagePack과 BSON은 필드 순서와 인코딩 디테일에 대한 추가 합의가 필요합니다.

8. 전환할 만한 경우

전환을 정당화하는 신호:

  • 페이로드가 크고 양쪽 끝을 모두 바꿀 수 있으며, 압축 크기가 여전히 의미 있게 다름
  • 바이너리 콘텐츠를 전송하고 Base64 팽창과 디코드 비용이 측정 가능함
  • JSON 더블이 표현할 수 없는 64비트 정수나 정확한 십진수가 필요함
  • 링크가 서버 간이며 진단 도구를 표준화할 수 있음

반대 신호:

  • gzip이나 brotli가 이미 적용되어 있고 이득이 한 자릿수 퍼센트임
  • API가 공개되어 서드파티가 범용 HTTP 클라이언트만 가짐
  • 데이터가 사람이 직접 읽는 로그나 설정임
  • 팀에 디코더와 버전 호환성을 유지할 예산이 없음

중간 경로도 유효합니다. 외부에는 JSON을 유지하고 내부 캐시에는 MessagePack을 쓰는 것입니다. 캐시 값은 자체 서비스만 읽으므로 크기와 파싱 비용이 모두 개선되고, 캐시를 지우면 계약을 건드리지 않고 문제를 해결할 수 있습니다.

9. 체크리스트

  • [ ] 실제 데이터의 압축 바이트 수를 비교하고, 압축 전 수치는 보지 않습니다
  • [ ] 큰 정수 정책을 결정합니다. BigInt 옵션, 문자열 전송, CBOR bignum 중 하나입니다
  • [ ] 바이너리 필드를 Base64로 감싸지 말고 bin이나 바이트 문자열로 보냅니다
  • [ ] 확장 타입과 태그를 프로토콜 문서에 적고 디코더 버전을 고정합니다
  • [ ] 운영 장애 중 켤 수 있는 텍스트 JSON 디버그 경로를 유지합니다
  • [ ] 구두 주장 대신 벤치마크를 실행해 재현 가능한 수치를 기록합니다

MessagePack과 CBOR의 차이는?

인코딩이 매우 비슷합니다. 맵, 배열, 문자열이 모두 간결한 타입+길이 접두사를 씁니다. 주된 차이는 표준화입니다. CBOR는 IANA 미디어 타입, 태그 메커니즘, 결정적 인코딩 규칙을 갖춘 IETF RFC이고, MessagePack은 확장 타입이 사적 합의에 의존하는 커뮤니티 명세입니다. 서명, 구현 간 일관성, 제약 장치 프로토콜이 필요하면 CBOR를 고르세요.

BSON이 범용 형식으로 JSON을 대체할 수 있나?

권장하지 않습니다. BSON은 데이터베이스 문서를 위해 설계되었습니다. 모든 문서가 4바이트 전체 길이를 지니고, 모든 필드가 타입과 이름을 반복하며, 작은 문서는 JSON 텍스트보다 커집니다. ObjectId, MinKey 같은 MongoDB 특유의 타입도 포함합니다. MongoDB 밖에서는 타입 시스템의 대부분이 쓰이지 않는데 크기 패널티는 남습니다.

바이너리 형식을 쓰면 페이로드가 얼마나 작아지나?

데이터 모양에 달려 있습니다. 필드 이름이 심하게 반복되면 압축된 텍스트 JSON과 바이너리 형식이 비슷해집니다. 이름이 짧고 데이터가 구조 문자와 숫자로 가득하면 바이너리의 이점이 더 분명합니다. 일반 배수로 추정하지 말고, 실제 샘플을 압축 전과 gzip 후로 모두 측정해 결정하세요.

바이너리 데이터를 jq로 아직 디버깅할 수 있나?

아니요. jq는 텍스트 JSON만 처리하며 MessagePack이나 CBOR 바이트에서는 즉시 실패합니다. 먼저 해당 라이브러리로 JSON으로 디코딩한 뒤 jq에 넘겨야 하고, CBOR는 진단 표기 도구를 거쳐 읽을 수 있는 텍스트로 바꿀 수도 있습니다. 텍스트 디버그 경로를 유지하는 것이 필요할 때 도구를 설치하는 것보다 저렴합니다.