시스템 간 통신이 중요한 웹 서비스나 마이크로서비스 환경에서, JSON은 가장 널리 사용되는 데이터 포맷입니다. 하지만 Google의 Protocol Buffers는 더 높은 성능을 제공하는 대안으로 점점 더 많이 사용되고 있습니다.
이 글에서는 Protocol Buffers가 무엇인지, 왜 사용해야 하는지 정리해봅니다 :)
Protocol Buffers는 Google이 개발한 언어 및 플랫폼 중립적인 직렬화 포맷입니다. (공식 문서 설명 🫠)
쉽게 말해, 구조화된 데이터를 바이너리 형태로 작고 빠르게 저장하고 전송하기 위한 도구로, JSON이나 XML에 비해 더 고성능인 대안이라 볼 수 있습니다.
사용 목적
동일한 데이터를 각각 JSON과 Protocol Buffers 형식으로 직렬화하고, 결과 크기를 비교하는 코드입니다.
package main
import (
"encoding/json"
"fmt"
"log"
"google.golang.org/protobuf/proto"
pb "github.com/Kim-Yukyung/grpc-kafka-chat/proto"
)
func main() {
// JSON 예시
jsonData := map[string]interface{}{
"message_id": "12345",
"user_id": "user123",
"username": "John",
"message": "안녕하세요!",
}
// JSON 직렬화
// Go Date를 JSON 형식으로 변환
jsonBytes, err := json.Marshal(jsonData)
if err != nil {
log.Fatal(err)
}
// Protocol Buffers 예시
protoMessage := &pb.ChatMessage{
MessageId: "12345",
UserId: "user123",
Username: "John",
Message: "안녕하세요!",
}
// Protocol Buffers 직렬화
// Go Date를 Protocol Buffers 형식으로 변환
protoBytes, err := proto.Marshal(protoMessage)
if err != nil {
log.Fatal(err)
}
// 결과 비교
fmt.Printf("JSON 크기: %d bytes\n", len(jsonBytes))
fmt.Printf("JSON 데이터: %s\n", string(jsonBytes))
fmt.Printf("\nProtocol Buffers 크기: %d bytes\n", len(protoBytes))
fmt.Printf("Protocol Buffers 데이터: %x\n", protoBytes)
}
실행
go run comparison.go
결과
JSON 크기: 89 bytes
JSON 데이터: {"message":"안녕하세요!","message_id":"12345","user_id":"user123","username":"John"}
Protocol Buffers 크기: 40 bytes
Protocol Buffers 데이터: 0a0531323334351207757365723132331a044a6f686e2210ec9588eb8595ed9598ec84b8ec9a9421
Protocol Buffers를 사용하려면 가장 먼저 .proto 파일의 구조와 문법을 이해해야 합니다. 이 파일은 데이터를 어떤 구조로 직렬화할지 정의하는 스키마 파일입니다.
기본 구조 예시
syntax = "proto3"; // proto3 문법 사용
package chat; // 패키지 이름
option go_package = "/proto"; // Go 코드 생성 시 패키지 경로 설정
메시지 정의 예시
message ChatMessage {
string message_id = 1; // 필드 번호 1
string user_id = 2; // 필드 번호 2
string username = 3; // 필드 번호 3
}
⚠️ 필드 번호는 한 번 정의 후 절대 변경하면 안 됩니다. 이 번호는 직렬화 시 데이터 구분에 사용되므로 변경 시 역직렬화 오류가 발생합니다.
1) .proto 파일 작성
먼저 데이터를 정의하는 .proto 파일을 작성합니다.
syntax = "proto3";
package example;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
}
2) 코드 생성
protoc 컴파일러를 사용해 언어별 코드로 변환합니다.
# Go 코드 생성
protoc --go_out=. person.proto
# Java 코드 생성
protoc --java_out=. person.proto
# Python 코드 생성
protoc --python_out=. person.proto
3) 직렬화 및 역직렬화
생성된 코드를 활용해 데이터를 처리합니다.
// Go 예시
person := &pb.Person{
Name: "John",
Id: 1234,
Email: "john@example.com",
}
// 직렬화
data, err := proto.Marshal(person)
if err != nil {
log.Fatal(err)
}
// 역직렬화
newPerson := &pb.Person{}
err = proto.Unmarshal(data, newPerson)
if err != nil {
log.Fatal(err)
}
Protocol Buffers는 매우 효율적인 직렬화 도구지만, 모든 환경에 항상 적합한 것은 아닙니다. 아래와 같은 상황에서는 다른 포맷을 고려하는 것이 더 나을 수 있습니다.
매우 큰 데이터 (수백 MB 이상)
→ 전체 메시지를 메모리에 로드해야 하므로, 메모리 사용량이 급증할 수 있습니다.
이미지, 동영상 등 대용량 바이너리 데이터
→ JPEG, MP4 등 전용 포맷을 사용하는 것이 효율적입니다.
웹 브라우저와의 직접 통신
→ 브라우저는 기본적으로 JSON을 사용하므로, 호환성과 디버깅 측면에서 JSON이 유리합니다.
참고 링크