gRPC 기초 : Protocol Buffers

gRPC에 사용되는 Protocol Buffers를 알아보겠습니다.

Protocol Buffers


Protocol Buffers는 구글에서 만든 이진 직렬화 포맷입니다. 줄여서 protobuf라고 부릅니다. JSON, YAML, XML과 같은 데이터를 표현하는 포맷입니다. 익숙한 포맷들과 가장 큰 차이는 데이터를 이진 데이터로 표현한다는 점입니다. protobuf의 장단점은 아래와 같습니다.

장점

단점

protobuf 설치


gRPC에 나와있는 설치 방법 페이지를 참고하여 설치를 진행합니다. 아래의 세 가지 방법을 추천합니다. 여기서는 python 모듈을 설치하도록 하겠습니다.

🚨 주의

apt를 통해 설치되는 버젼이 매우 낮을 수 있습니다. 반드시 최신의 버젼인지 확인 합니다. apt를 통한 설치는 추천하지 않습니다.

# 추천하지 않음
$ apt install protobuf-compiler

python 모듈 설치

$ pip install grpcio-tools
$ python -m grpc_tools.protoc --version

.proto 작성


우선 가장 먼저 .proto 라는 스키마 파일을 작성해야 합니다. 해당 파일을 컴파일 하여 각 언어별로 protobuf를 사용할 수 있는 파일을 만들 수 있습니다. 일종의 인터페이스 코드를 생성 한다고 생각하면 됩니다.

기본적인 JSON 형태의 데이터를 .proto로 비교하면서 작성해보겠습니다.

JSON

{
    "person": {
        "name": "tiaz0128"
    }
}

.proto 작성

protos 폴더 아래 person.proto라는 파일 작성하겠습니다.

syntax = "proto3" 3버전을 사용하겠다는 의미이며 주로 3버젼을 사용합니다. syntax 구문을 작성하지 않으면 2버젼을 기본으로 사용하기 때문에 반드시 작성해 줍니다.

protos/person.proto
syntax = "proto3";

message Person {
  string name = 1;
}

message Data {
  Person person = 1;
}

message 작성법


message는 protobuf의 핵심 구성 요소로, 데이터 구조를 정의하는 데 사용됩니다. 기본적인 작성법에 대해서 알아보겠습니다.

코드

protos/person.proto
syntax = "proto3";

message Person {
    string name = 1;
    int32 age = 2;
    enum Gender {
        UNKNOWN = 0;
        MALE = 1;
        FEMALE = 2;
    }
    Gender gender = 3;
    repeated string hobbies = 4;
    
    message Address {
        string street = 1;
        string city = 2;
    }
    Address address = 5;
}

기본 구조

string name = 1;

데이터 타입

repeated string hobbies = 4;

message Address {
    string street = 1;
    string city = 2;
}
Address address = 5;

필드 옵션

주석

.proto 컴파일


src 폴더

src 폴더를 만들고 해당 위치에서 아래의 컴파일 명령을 실행합니다.

project/
├── protos/
│   └── person.proto
└── src/
    └── (현재 위치)

protoc

protoc를 이용하여 .proto 파일을 컴파일해 파이썬 코드를 만들어 냅니다.

$ python -m grpc_tools.protoc -I../protos --python_out=. ../protos/person.proto

결과 파일

결과 파일의 이름은 원본 .proto 파일 이름에 _pb2가 추가됩니다. person.proto 파일을 이용해 person_pb2.py라는 파일이 생성됩니다.

project/
├── protos/
│   └── person.proto
└── src/
    └── *person_pb2.py (결과 파일)

_pb2.py

생성된 코드는 다음과 같은 내용을 포함합니다.

직렬화(Serialization)


main.py 파일을 추가하고 컴파일된 파일을 통해 메시지를 바이너리 형식으로 직렬화하는 방법을 알아보겠습니다.

project/
├── protos/
│   └── person.proto
└── src/
    └── person_pb2.py
    └── *main.py (추가 파일)

코드

src/main.py
import person_pb2

person = person_pb2.Person()
person.name = "tiaz0128"

serialized = person.SerializeToString()
print(isinstance(serialized, bytes))

직렬화한 데이터는 바이너리 형식으로 매우 효율적이며, 네트워크 전송이나 파일 저장에 적합합니다.

역직렬화(Deserialization)


바이너리 형식으로 직렬화한 데이터를 다시 protobuf 객체로 만드는 역직렬화 방법을 알아보겠습니다.

코드

새로운 protobuf 객체를 생성하고 ParseFromString 메서드를 통해서 역직렬화 할 수 있습니다.

new_person = person_pb2.Person()
new_person.ParseFromString(serialized)

print(new_person.name)

바이트 문자열을 파싱하여 각 필드의 값을 객체의 해당 필드에 할당합니다. 이 과정에서 protobuf의 인코딩 규칙을 따라 데이터를 해석합니다.

잘못된 형식의 데이터가 입력되면 예외가 발생하며, 불완전한 데이터가 입력되어도 가능한 만큼 파싱을 시도합니다. 파싱된 필드만 설정되고, 나머지는 기본값을 유지합니다.

다른 형식으로 직렬화


JSON, 딕셔너리 같은 다른 형식으로 직렬화하고 다시 protobuf 객체로 역직렬화 할 수 있습니다. 이외에도 파일로 저장하거나 스트림 형태를 활용 할 수 있습니다.

JSON 형식

from google.protobuf.json_format import MessageToJson, Parse

json_string = MessageToJson(person)
other_person = person_pb2.Person()
Parse(json_string, other_person)

딕셔너리 형식

from google.protobuf.json_format import MessageToDict, ParseDict

dict_data = MessageToDict(person)
other_person = person_pb2.Person()
ParseDict(dict_data, other_person)

정리


Protocol Buffers는 JSON, XML 같은 데이터를 표현하는 포맷 입니다. 문자열 기반인 다른 포맷과 달리 이진 데이터를 활용합니다.

gRPC에서 Protocol Buffers를 사용하기 때문에 기본적인 사용법을 이해하고 있으면 gRPC를 이해하는데 도움이 됩니다. 😊

gRPC protobuf