
JSON vs Protocol Buffers: 게임 데이터 직렬화 속도 비교
JSON과 Protocol Buffers의 속도 차이를 단순 수치가 아닌 게임의 데이터 흐름과 병목 위치를 기준으로 살펴본다. 네트워크 메시지, 저장 데이터, 툴 파이프라인별로 실용적인 선택 기준도 정리한다.
게임에서 데이터 직렬화는 눈에 잘 띄지 않지만 로딩 시간, 네트워크 대역폭, 서버 비용에 영향을 준다. JSON은 읽기 쉽고 도구와 잘 맞지만 텍스트를 해석하는 비용이 있다. Protocol Buffers는 바이너리 형식과 스키마를 바탕으로 크기와 처리 비용을 줄이는 데 유리하다.
다만 ‘Protocol Buffers가 항상 몇 배 빠르다’는 식의 결론은 위험하다. 메시지 구조, 문자열 비중, 압축 여부, 런타임 구현, 측정 방식에 따라 결과가 크게 달라진다. 중요한 것은 직렬화 자체가 실제 병목인지 그리고 그 데이터를 누가 언제 읽는지다.
먼저 구분할 것: 직렬화와 전송
직렬화는 메모리의 객체를 파일이나 네트워크로 보낼 수 있는 바이트열로 바꾸는 과정이다. 역직렬화는 그 바이트열을 다시 프로그램의 데이터 구조로 복원하는 과정이다.
게임에서는 보통 다음 경로에서 이 비용이 나타난다.
- 클라이언트와 게임 서버 사이의 실시간 메시지
- 매치 결과, 인벤토리, 설정 같은 백엔드 API 응답
- 세이브 파일과 리플레이 파일
- 에디터가 만든 데이터를 런타임용 패키지로 변환하는 빌드 파이프라인
전송 시간이 길다고 해서 직렬화가 느린 것은 아니다. 작은 메시지가 자주 오가는 상황에서는 객체 할당과 파싱이 더 큰 비용일 수 있다. 반대로 대용량 맵 데이터처럼 한 번에 많은 내용을 내려받는 상황에서는 데이터 크기와 압축률이 더 중요해진다.

두 형식의 차이
JSON은 사람이 읽을 수 있는 텍스트 형식이다. 필드 이름과 값이 함께 들어가므로 디버깅과 로그 확인이 편하다.
{
"entityId": 42,
"position": {
"x": 12.5,
"y": 3.0
},
"hp": 87
}
Protocol Buffers는 .proto 파일에 데이터 구조를 정의하고 이를 바탕으로 언어별 코드를 생성하는 방식이 일반적이다. 실제 메시지에는 보통 필드 이름 대신 필드 번호와 타입 정보가 기록된다.
syntax = "proto3";
message Position {
float x = 1;
float y = 2;
}
message EntityState {
uint32 entity_id = 1;
Position position = 2;
uint32 hp = 3;
}
Protocol Buffers는 필드 이름을 반복해서 담지 않고 정수와 실수도 바이너리로 표현한다. 따라서 구조화된 숫자 데이터가 많은 메시지에서는 JSON보다 작아지는 경우가 많다. 파서도 텍스트를 숫자로 변환하고 키 문자열을 비교할 필요가 적어 역직렬화에 유리할 수 있다.
하지만 문자열이 대부분이거나 메시지가 매우 작다면 이점이 기대보다 작을 수 있다. HTTP 응답에 이미 gzip이나 Brotli 압축을 적용했다면 전송 크기 차이도 줄어든다. 이때는 CPU 사용량, 구현 복잡도, 운영 편의성까지 함께 봐야 한다.
속도를 비교하는 올바른 방법
벤치마크는 실제 메시지 형태를 최대한 닮게 구성해야 한다. 예를 들어 2D 액션 게임의 상태 동기화라면 좌표, 속도, 체력, 상태 플래그, 엔티티 ID의 분포를 그대로 반영한다. 이름과 설명문이 긴 상점 데이터로 물리 상태 메시지를 대신하면 결과를 일반화할 수 없다.
최소한 아래 항목을 나눠 측정하는 편이 좋다.
| 측정 항목 | 확인할 내용 |
|---|---|
| 직렬화 시간 | 게임 객체를 메시지로 만드는 CPU 비용 |
| 역직렬화 시간 | 수신 메시지를 게임 로직이 쓰는 형태로 바꾸는 비용 |
| 페이로드 크기 | 압축 전과 압축 후의 바이트 수 |
| 할당량과 GC | 프레임 드롭으로 이어질 수 있는 임시 객체 생성량 |
| 처리량 | 초당 처리 가능한 메시지 수 |
측정할 때는 디버그 빌드 대신 실제 배포와 가까운 최적화 빌드를 사용하고 첫 실행에만 발생하는 초기화 비용은 워밍업으로 분리한다. 평균만 보지 말고 높은 백분위 지연 시간도 확인해야 한다. 한 프레임에 메시지가 몰리는 게임에서는 드문 긴 파싱 시간이 더 문제일 수 있다.
간단한 측정 골격
아래 예시는 JavaScript 환경에서 반복 측정을 구성하는 형태다. 실제 프로젝트에서는 사용하는 JSON 라이브러리와 Protobuf 생성 코드에 맞춰 바꾸면 된다.
function measure(label, fn, iterations = 100_000) {
for (let i = 0; i < 5_000; i += 1) fn();
const started = performance.now();
for (let i = 0; i < iterations; i += 1) fn();
const elapsedMs = performance.now() - started;
console.log(`${label}: ${(elapsedMs / iterations).toFixed(4)} ms/op`);
}
const state = {
entityId: 42,
position: { x: 12.5, y: 3.0 },
hp: 87
};
const jsonBytes = new TextEncoder().encode(JSON.stringify(state));
measure("JSON serialize", () => JSON.stringify(state));
measure("JSON parse", () => JSON.parse(new TextDecoder().decode(jsonBytes)));
// EntityState는 생성된 Protocol Buffers 코드라고 가정한다.
measure("Protobuf encode", () => EntityState.encode(state).finish());
measure("Protobuf decode", () => EntityState.decode(protoBytes));
이 코드만으로 결론을 내리면 안 된다. 텍스트 인코딩과 디코딩, 버퍼 재사용 여부, 네트워크 프레이밍, 메시지 큐 처리까지 실제 경로에 맞춰 포함하거나 분리해 측정해야 한다.
게임 상황별 선택
실시간 상태 동기화
초당 여러 번 전송하는 위치, 입력, 스킬 상태처럼 작고 빈번한 메시지에는 Protocol Buffers가 잘 맞을 수 있다. 특히 모바일 네트워크나 동시 접속이 많은 서버에서는 메시지 크기와 파싱 비용을 줄이는 효과가 누적된다.
그러나 상태 동기화의 핵심은 형식만이 아니다. 전송 빈도 제한, 델타 압축, 관심 영역 관리, 신뢰성 채널 구분이 대개 더 큰 영향을 준다. JSON을 Protobuf로 바꿨는데도 서버가 모든 엔티티 상태를 매 틱 전송한다면 근본적인 문제는 남는다.
백엔드 API와 운영 도구
계정, 결제 검증, 라이브 이벤트 설정, 운영자 도구처럼 개발과 점검이 잦은 API는 JSON이 편리하다. 브라우저 개발자 도구와 HTTP 클라이언트에서 바로 내용을 확인할 수 있고 장애 대응 시 로그를 읽기도 쉽다.
이 경로가 실제 병목으로 확인되지 않았다면 JSON을 유지하는 편이 전체 개발 비용을 낮출 수 있다. 필요한 일부 고빈도 API만 바이너리 메시지로 바꾸는 혼합 전략도 현실적이다.
세이브와 콘텐츠 데이터
사람이 직접 수정하거나 버전 관리에서 변경 내용을 검토해야 하는 데이터는 JSON이 강하다. 퀘스트 정의, 밸런스 테이블, 로컬라이즈 설정처럼 기획과 개발이 함께 다루는 데이터가 대표적이다.
반면 배포용 런타임 데이터는 빌드 과정에서 Protobuf나 프로젝트 전용 바이너리 형식으로 변환할 수 있다. 원본의 가독성과 배포 데이터의 효율을 분리하는 방식이다.
스키마 관리에서 생기는 차이
Protocol Buffers를 도입하면 스키마 규칙을 운영해야 한다. 필드 번호는 배포 뒤에 함부로 재사용하면 안 되며 삭제한 번호는 예약 처리하는 습관이 필요하다. 클라이언트와 서버의 버전이 한동안 섞여 있는 라이브 게임에서는 특히 중요하다.
message PlayerProfile {
uint64 player_id = 1;
string display_name = 2;
uint32 level = 3;
reserved 4;
reserved "legacy_rank";
}
JSON도 호환성 문제가 없는 것은 아니다. 필드 누락, 타입 변경, 알 수 없는 값 처리는 명시적으로 정해야 한다. 다만 JSON은 스키마가 코드와 문서에 흩어지기 쉬운 반면, Protocol Buffers는 스키마 파일을 계약의 중심에 둘 수 있다는 차이가 있다.
도입 전 점검 목록
- 프로파일러에서 JSON 직렬화나 파싱이 실제 병목으로 나타나는가?
- 같은 데이터를 더 적게 보내도록 메시지 설계를 먼저 개선할 수 있는가?
- 대상 플랫폼에서 사용하는 Protobuf 런타임의 바이너리 크기와 성능은 허용 가능한가?
- 스키마 생성 코드와 호환성 검사를 CI에 넣을 수 있는가?
- 운영 중 사람이 메시지와 로그를 쉽게 확인할 방법이 있는가?
결론
Protocol Buffers는 숫자 중심의 구조화된 메시지를 자주 주고받는 게임에서 유력한 선택지다. 대역폭과 파싱 비용을 줄일 가능성이 있으며 명확한 스키마 계약도 제공한다. 반면 JSON은 관찰 가능성, 도구 호환성, 콘텐츠 제작 편의성에서 여전히 강하다.
가장 실용적인 결론은 전면 교체가 아니라 경로별 선택이다. 고빈도 실시간 메시지는 Protocol Buffers 후보로 측정하고 운영 API와 사람이 관리하는 콘텐츠는 JSON을 유지한다. 실제 게임 메시지와 배포 환경으로 측정한 결과가 그 경계를 정하게 해야 한다.


