JSON을 80% 줄이는 바이너리 포맷, 직접 만들었다
Packing Binary Is Fun
트위터에서 JSON은 Real™ 개발자가 쓰는 게 아니라는 말을 듣고 바이너리 포맷을 직접 만들기로 했다. 단순히 wb 모드로 파일을 여는 것에서 시작했지만, 결국 타입·길이·필드 번호를 varint 하나에 압축하는 스키마 언어 jBin을 개발했다. JSON 대비 페이로드를 80% 줄일 수 있으며, protobuf처럼 스키마를 사용해 타입 정보를 매번 저장하지 않아도 된다.
“내 바이너리 포맷을 만드는 게 얼마나 어려울까?” 분명 wb 하나면 될 줄 알았다. ( ˶ˆᗜˆ˵ ) 안타깝게도 wb 하나로는 끝나지 않았다. 결국 JSON 페이로드를 80%까지 줄여주는 바이너리 스키마 언어를 통째로 만들게 됐다.
- trashb
제가 생각하기에 어떤 (바이너리) 포맷이든 반드시 있어야 한다고 주장할 만한 중요한 부분들이 빠져 있네요.
- 매직 헤더
- 버전 번호
- crc 또는 데이터 손상 검사
추가로, 이 경우에는 바이너리 포맷을 파싱하는 것보다 커스텀 텍스트 파서를 작성하는 게 더 낫다고 주장하고 싶습니다. 이는 유닉스 스타일 설정 대 윈도우 regedit 논쟁과 비슷하죠. 저는 json이 아닌 다른 걸 선호하지만 여전히 txt로 읽을 수 있는 걸 원합니다.
심지어 맨 아래에 제공된 json 예시도 필드 이름을 한 글자로 바꾸고 공백을 제거하면 418자에서 166자로 축소할 수 있습니다. 이 포맷에서 절약되는 거의 모든 부분은 필드 이름을 포함하지 않고 위치 의존적 레이아웃을 갖는 데서 나옵니다. 구분자를 선택하거나 바이트가 타입(+크기)을 나타내도록 배열할 수 있습니다. 예를 들어 다음 문자열은 예시 데이터를 거의 (85바이트 대 80바이트)만큼 효율적으로 인코딩하면서도 여전히 읽을 수 있고 utf-8 해석을 지원합니다.
123456789;LeroyJenkins;60;alliance;p,100,200,300;i,999,1,1;i,45,100,0;a,s,120;a,a,45;
반복되는 항목을 허용하고 정의된 기본값에서 가정된 값을 허용하며 델타만 전송하도록 하면 더 최적화할 수 있습니다. 이는 예상하는 데이터 유형에 따라 달라질 수 있습니다.
{ "itemId": 999, "quantity": 1, "isSoulbound": false }
i,999,1,0
이렇게 될 수 있습니다:
default = { "itemId": 0, "quantity": 1, "isSoulbound": false }
{ "itemId": 999}
i,999
- ErikHuisman
저도 진짜 개발자가 되고 싶어서 그냥 JSON을 GZIP으로 압축해서 바이너리로 만듭니다.
- Skwid
이전 직장에서 제가 가장 좋아했던 삽질 모험 중 하나는 약 1MB의 데이터를 처리하기 위해 거의 같은 양의 여유 메모리를 가진 기기에서 제자리 파싱(in-place) UBJSON 디코더를 작성하는 것이었습니다. 키로 (충분히) 빠르게 접근하고, 대부분 숫자인 페이로드에 몇 가지 지름길을 둔 이진 검색을 사용했습니다.
속도를 높이기 위해 약 30바이트의 인덱스를 구축했지만, 동시에 새 메시지가 시작 부분을 덮어쓰는 동안에도 이전 메시지의 꼬리 부분에서 계속 작동할 수 있게 했습니다.
시스템 설계가 4MB 메모리를 가진 기기에 이 모든 것을 요구했어야 할까요? 아마 아닐 겁니다. 하지만 작동했고, 정말 즐거웠습니다