List
데이터 시스템의 규모가 커지면 직렬화(Serialization) 비용이 필연적으로 병목이 됩니다. 초당 수십만~수백만 건의 이벤트를 다루는 환경에서 JSON은 가독성이라는 장점이 있지만, CPU와 네트워크 부하가 빠르게 누적되기 때문입니다.
본격적으로 고성능 포맷(Protobuf, Arrow)을 다루기에 앞서, 데이터 엔지니어링의 기본기인 직렬화가 무엇인지 알아보려고 합니다.
1. 직렬화(Serialization)의 정의
프로그래밍 언어 안에서 데이터는 객체(Object)나 구조체 형태로 메모리에 올라가 있습니다. 하지만 이 데이터를 네트워크로 보내거나 디스크에 저장하려면, 결국 연속적인 바이트(Byte) 흐름으로 변환해야 합니다.
•
직렬화(Serialization): 메모리 속 객체 → 바이트 스트림
•
역직렬화(Deserialization): 바이트 스트림 → 메모리 속 객체
표현은 단순하지만, 이 과정이 데이터 파이프라인 전체 성능에 상당한 영향을 줍니다.
2. 직렬화가 만드는 시스템 병목
트래픽이 일정 수준을 넘으면 직렬화는 크게 세 가지 관점에서 시스템 자원을 잡아먹습니다.
2-1. CPU 비용: 파싱과 변환
JSON, XML 같은 텍스트 포맷은 기계가 바로 이해할 수 없습니다. 숫자를 문자열로 바꾸고, 각종 구문 기호(", :, {, })를 해석하는 과정에서 CPU 연산이 계속 발생합니다. 데이터 건수가 많아질수록 서버는 비즈니스 로직보다 문자열 파싱에 더 많은 시간을 쓰게 됩니다.
2-2. 네트워크 비용: 페이로드 크기
JSON은 데이터를 보낼 때마다 필드 이름(Key)을 같이 보냅니다.
{
"user_id": 12345,
"status": "active"
}
JSON
복사
실제 의미 있는 값은 12345, active 두 개뿐인데, user_id, status라는 문자열이 요청마다 반복해서 전송됩니다. 이 오버헤드는 트래픽이 커질수록 막대한 대역폭 비용으로 이어집니다.
2-3. 타입 안정성과 호환성
직렬화 포맷에 따라 스키마가 바뀌었을 때(필드 추가/삭제) 하위 호환성을 유지하는 난이도가 달라집니다. 이 부분을 잘못 설계하면, 특정 서비스나 파이프라인만 버전이 안 맞아 전체 플로우가 깨지는 상황이 자주 생깁니다.
3. Kafka와 Airflow에서 드러나는 직렬화
직렬화/역직렬화는 이론적인 개념이 아니라, 우리가 자주 쓰는 도구 안에 그대로 들어 있습니다.
•
Kafka: 프로듀서가 레코드를 보낼 때 객체를 byte[]로 직렬화해서 토픽에 올리고, 컨슈머는 다시 이를 역직렬화해서 사용합니다. 이때 JSON/Avro/Protobuf 중 무엇을 쓰느냐에 따라 메시지 크기, 처리 속도, 스키마 관리 방식이 모두 달라집니다.
•
Airflow: DAG 정의, XCom 값 등을 저장하고 주고받을 때 Python 객체를 JSON 같은 형태로 직렬화했다가 다시 역직렬화합니다. 태스크 간에 불필요하게 큰 객체를 주고받으면 웹서버·스케줄러·DB 모두에 부담이 쌓입니다.
어디까지나 “데이터를 어떻게 포장해서 넘길 것인가”의 문제이고, 이 포장이 비효율적이면 파이프라인 전체에 비용이 누적됩니다.
4. 텍스트에서 바이너리로
JSON은 범용성이 좋고 디버깅이 편해서 여전히 많이 쓰입니다. 다만, 고성능이 필요한 구간에서는 한계가 분명합니다.
•
JSON (텍스트 기반): 사람이 읽기 편하지만, 무겁고 느리다.
•
바이너리 포맷: 사람이 바로 읽을 수는 없지만, 훨씬 작고 빠르다.
시스템 규모가 커질수록, 가독성을 조금 포기하더라도 기계 입장에서 효율적인 바이너리 직렬화를 선택하게 됩니다.
마무리
직렬화는 나중에 천천히 최적화하면 되는 디테일이 아니라, 데이터 파이프라인의 성능·비용·안정성을 결정하는 기반입니다. 트래픽과 데이터량이 늘어나면 직렬화/역직렬화를 어디서 어떻게 처리할지 반드시 점검해야 합니다.
다음 글에서는 이 문제에 대해 Google이 내놓은 해답, Protobuf를 다룹니다.
필드 이름 대신 숫자 태그를 쓰고, 값을 가변 길이 정수로 인코딩하는 방식이 실제 바이트 단위에서 어떤 차이를 만들어내는지, Wire Format 관점에서 확인해보겠습니다.