TL;DR
- NDJSON 파일을 256KiB 단위의 독립 프레임으로 나눈
zstd로 압축하면, 64개 스레드에서 초당 40GB를 파싱할 수 있음. - 5백만 레코드, 총 812MB의 합성 NDJSON 파일에서 활성 레코드 수와 특정 조건에 맞는 점수 합계를 계산하는 벤치마크임.
gzip은 이전 출력의 32KiB가 필요한 단일 스트림이므로 압축 해제는 한 스레드에서 순차 실행해야 하며, 파싱을 병렬화해도 속도는 초당 2.5GB에 그침.zstd와lz4는 독립 프레임을 스레드별로 처리할 수 있어, 64개 스레드에서 각각 초당 40GB, 초당 34GB를 기록함.zstd파일은gzip보다 6% 크지만, JSON 파일 작성 방식을 제어할 수 있다면 다중 프레임 압축으로 훨씬 빠른 병렬 처리가 가능함.
NDJSON 파일 파싱
- JSON 데이터를 문서 하나씩 한 줄에 저장하는 형식은 NDJSON(JSON Lines)이며, 로그 파일, 데이터베이스 내보내기, 머신러닝 데이터셋에서 자주 사용됨.
- 대용량 파일은 압축할 수 있으며, 벤치마크에서는 812MB의 NDJSON 파일에 든 5백만 개 레코드를 처리함.
- 각 레코드에서 일부 필드를 읽고, 활성 레코드 수를 세며, 사용자에게
admin태그가 있는 활성 레코드의 점수를 합산함.
청크 단위 압축 해제와 파싱
- 파일 전체를 먼저 압축 해제하는 대신, 압축된 파일에서 청크를 압축 해제하고 완전한 줄을 파싱한 뒤 마지막의 미완성 줄을 버퍼 앞쪽으로 옮기는 방식임.
- NDJSON 문서 안에는 줄바꿈 문자가 그대로 들어갈 수 없으며, 문자열 안의 줄바꿈은
\\n으로 이스케이프됨. 따라서 버퍼의 마지막 줄바꿈 뒤에서 안전하게 자를 수 있음. simdjson의iterate_many기능으로 버퍼에 담긴 여러 문서를 순회하고, 완전한 줄을 처리한 뒤 남은 바이트를 다음 청크와 이어 붙임.
압축 형식별 병렬 처리
- 테스트 환경은 Intel Xeon Gold 6548N(Emerald Rapids) 서버로, 소켓 2개, 코어 64개, 스레드 128개를 갖추며 컴파일러는 GCC 14임.
gzip은 하나의 긴 압축 스트림이며, 다음 데이터를 해독할 때 이전 압축 해제 결과 중 32KiB가 필요함. 따라서 파일을 처음부터 한 스레드로 압축 해제해야 함.- 압축 해제 스레드 하나가 청크를 큐에 넣고 다른 스레드들이 파싱하는 방식으로 병렬화하면
gzip처리량은 초당 2.5GB로 두 배가 됨. zstd와lz4는 서로 독립적인 프레임을 이어 붙여 파일을 구성할 수 있으며, 일반적인.zst와.lz4파일 형식 및 명령줄 도구와도 호환됨.- 프로그램은 JSON 256KiB마다, 항상 줄바꿈 뒤에서 새 프레임을 생성함. 각 프레임에는 압축 해제 크기와 체크섬이 포함됨.
- 프레임 경계는 일부 블록 헤더만 읽어 찾을 수 있어 전체 압축 해제가 필요하지 않으며, 각 스레드가 프레임을 가져와 압축 해제와 파싱을 모두 수행할 수 있음.
처리량과 파일 크기
- 단일 스레드에서는
zstd와lz4모두gzip과 비슷한 초당 약 1.1GB를 기록함. - 64개 스레드에서는
zstd가 초당 40GB,lz4가 초당 34GB를 기록하며,gzip에서 얻은 최고 속도보다 16배 빠름. - 벤치마크 파일 크기는 NDJSON 812.0MB,
gzip단일 스트림 56.9MB,zstd256KiB 프레임 60.6MB,lz4256KiB 프레임 108.5MB임. - 작은 프레임은 각 프레임이 처음부터 압축을 시작하므로 압축 효율이 다소 낮지만,
zstd파일은gzip파일보다 6% 더 큼.
결과 해석과 자료
- 데이터는 합성 데이터이며 레코드가 매우 반복적이어서 압축 해제가 빠름. 실제 데이터에서는 압축 해제 속도가 더 느릴 수 있음.
- JSON 파일을 작성하는 방식을 제어할 수 있다면 다중 프레임
zstd를 고려할 수 있으며,gzip과 비슷한 파일 크기로 여러 스레드에서 훨씬 빠르게 읽을 수 있음. - 소스 코드: https://github.com/simdjson/simdjson_compressed_demo
- 벤치마크 수치와 그래프 작성 스크립트는 블로그 저장소에 있음.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요