Protocol Buffers

김유경·2025년 7월 29일
0

들어가며

시스템 간 통신이 중요한 웹 서비스나 마이크로서비스 환경에서, JSON은 가장 널리 사용되는 데이터 포맷입니다. 하지만 Google의 Protocol Buffers는 더 높은 성능을 제공하는 대안으로 점점 더 많이 사용되고 있습니다.

이 글에서는 Protocol Buffers가 무엇인지, 왜 사용해야 하는지 정리해봅니다 :)


Protocol Buffers란?

Protocol Buffers는 Google이 개발한 언어 및 플랫폼 중립적인 직렬화 포맷입니다. (공식 문서 설명 🫠)

쉽게 말해, 구조화된 데이터를 바이너리 형태로 작고 빠르게 저장하고 전송하기 위한 도구로, JSON이나 XML에 비해 더 고성능인 대안이라 볼 수 있습니다.

사용 목적

  • 네트워크 통신을 위한 메시지 정의
  • 다양한 언어(Java, Go, Python ...) 간 공통 데이터 구조 공유
  • 마이크로서비스에서 빠른 데이터 교환을 위한 포맷

성능 비교

동일한 데이터를 각각 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

.proto 파일 구조 이해하기

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
}

⚠️ 필드 번호는 한 번 정의 후 절대 변경하면 안 됩니다. 이 번호는 직렬화 시 데이터 구분에 사용되므로 변경 시 역직렬화 오류가 발생합니다.


Protocol Buffers 사용하기

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이 유리합니다.


참고 링크

🔗 공식 문서

0개의 댓글