CDC-Pipeline 공부하려다가 자주 등장하는 Protobuf에 대해서 어느정도는 알고 넘어가야 싶어서 정리해본다.
"서비스 간에 데이터를 주고받을 때 어떤 형식을 써야 하지?"
JSON이 익숙하고 편하지만,
대규모 파이프라인에서는 직렬화 형식 하나가 처리 속도, 저장 비용, 스키마 관리 전반에 영향을 미칩니다.
Protocol Buffers(Protobuf)를 개념부터 실제 .proto 문법, 그리고 JSON·Avro와의 비교까지 정리한다.
프로그램 내부의 객체(Object)를 네트워크 전송이나 파일 저장이 가능한 바이트(byte) 스트림으로 변환하는 과정.
반대 과정은 역직렬화(Deserialization)라고 한다.
[ Python Object ] → 직렬화 → [ bytes / string ] → 역직렬화 → [ Java Object ]
우리가 매일 쓰는 JSON도 직렬화 포맷의 하나이고, 다만 텍스트 기반이라 사람이 읽기 쉬운 대신, 용량이 크고 파싱 비용이 높다.
Protocol Buffers는 Google이 내부 서비스 통신을 위해 개발하고 2008년 오픈소스로 공개한 이진(binary) 직렬화 포맷.
핵심 특징을 세 가지로 요약하면:
| 특징 | 설명 |
|---|---|
| 스키마 우선(Schema-first) | .proto 파일에 데이터 구조를 먼저 정의 |
| 이진 인코딩 | 텍스트가 아닌 바이트로 저장 → 용량 ↓, 속도 ↑ |
| 언어 중립 | 하나의 .proto로 Python, Java, Go 등 다양한 언어 코드를 자동 생성 |
💡 Kafka, gRPC, Flink 등 대부분의 현대 데이터 인프라에서 Protobuf를 기본 또는 권장 포맷으로 지원.
Protobuf를 쓰려면 먼저 스키마 파일(.proto)을 작성해야 함.
syntax = "proto3";
package com.example;
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
| 요소 | 설명 |
|---|---|
syntax = "proto3" | 사용할 Protobuf 버전 선언 (현재 표준은 proto3) |
message | 하나의 데이터 구조 단위 (JSON의 Object와 유사) |
int32, string | 필드 타입 |
= 1, = 2 | 필드 번호 — 이진 인코딩의 핵심 (이름 대신 번호로 식별) |
JSON은 키 이름("name", "email")을 바이트 그대로 전송.
Protobuf는 필드 번호(1, 2, 3…)만 전송하므로 페이로드가 훨씬 작아짐.
JSON → {"name":"Alice","email":"alice@example.com"} → 45 bytes
Protobuf → [0x0a, 0x05, 0x41, 0x6c, ...] → ~18 bytes
⚠️ 필드 번호는 한번 정하면 변경 금지! 번호를 바꾸면 기존 데이터를 역직렬화 불가.
syntax = "proto3";
message Order {
int64 order_id = 1;
string user_name = 2;
repeated string items = 3; // 배열(List)
OrderStatus status = 4; // Enum
double total_price = 5;
}
enum OrderStatus {
PENDING = 0;
CONFIRMED = 1;
SHIPPED = 2;
}
repeated : 배열/리스트를 표현enum : 열거형 상수 정의| 항목 | JSON | Avro | Protobuf |
|---|---|---|---|
| 인코딩 방식 | 텍스트 | 이진 | 이진 |
| 스키마 필요 여부 | 불필요 | 필요 (JSON Schema) | 필요 (.proto) |
| 가독성 | ✅ 사람이 읽기 쉬움 | ❌ 바이너리 | ❌ 바이너리 |
| 직렬화 속도 | 느림 | 빠름 | 가장 빠름 |
| 데이터 크기 | 큼 | 작음 | 가장 작음 |
| 스키마 진화(Evolution) | 자유롭지만 위험 | 제한적이지만 안전 | 필드 번호 기반으로 안전 |
| 주요 사용처 | REST API, 설정 파일 | Kafka(Confluent), Hadoop | Kafka, gRPC, Flink |
동일한 데이터를 세 포맷으로 직렬화했을 때의 일반적인 비교 결과.
직렬화 크기 (낮을수록 좋음)
────────────────────────────────────────
JSON ████████████████████ 100% (기준)
Avro ████████████ ~60%
Protobuf ██████ ~30%
직렬화 속도 (높을수록 좋음)
────────────────────────────────────────
JSON ██████ 1x
Avro ████████████ ~2x
Protobuf ████████████████████ ~3-5x
📌 실제 수치는 데이터 구조·환경에 따라 달라지니 참고.
┌─────────────────────────────────────────────────────┐
│ 사람이 직접 읽고 써야 하는 경우 │
│ → JSON (REST API, 설정 파일, 디버깅) │
├─────────────────────────────────────────────────────┤
│ Kafka + Schema Registry, Hadoop/Hive 생태계 │
│ → Avro (Confluent 표준, 동적 스키마 로딩에 유리) │
├─────────────────────────────────────────────────────┤
│ 고성능 스트리밍, gRPC, 서비스 간 통신 │
│ → Protobuf (속도·크기 최우선, 강타입 보장) │
└─────────────────────────────────────────────────────┘
Kafka 메시지의 Value를 Protobuf로 직렬화하면 두 가지 이점이 있다.
| 허용 | 금지 |
|---|---|
| 새 필드 추가 (새 번호 사용) | 기존 필드 번호 변경 |
optional 필드 삭제 | 기존 필드 타입 변경 |
| 필드 이름 변경 (번호는 유지) | 삭제된 필드 번호 재사용 |
Protobuf는 처음엔 .proto 파일을 따로 관리해야 하는 번거로움이 있지만, 규모가 커질수록 그 장점이 커짐.