gRPC 기초 : Protocol Buffers
gRPC에 사용되는 Protocol Buffers
를 알아보겠습니다.
Protocol Buffers
Protocol Buffers
는 구글에서 만든 이진 직렬화 포맷입니다. 줄여서 protobuf
라고 부릅니다. JSON, YAML, XML과 같은 데이터를 표현하는 포맷입니다. 익숙한 포맷들과 가장 큰 차이는 데이터를 이진 데이터로 표현한다는 점입니다. protobuf
의 장단점은 아래와 같습니다.
장점
- 직렬화와 역직렬화가 매우 빠름
- 데이터 크기가 작아 네트워크 전송이 효율적
- 다양한 프로그래밍 언어(Java, Python, C++, Go 등)에서 사용 가능
- 컴파일 시점에 타입 체크를 수행하여 런타임 오류
단점
- 이진 형식이라 사람이 직접 읽기 어렵고 디버깅이 어려움
- .proto 파일 작성과 protobuf 사용법을 학습
- 웹 브라우저에서 직접 사용하기 어렵움
- 간단한 데이터 교환의 경우, JSON이나 XML에 비해 과도하게 복잡
protobuf 설치
gRPC에 나와있는 설치 방법 페이지를 참고하여 설치를 진행합니다. 아래의 세 가지 방법을 추천합니다. 여기서는 python
모듈을 설치하도록 하겠습니다.
- 각 언어별 모듈 설치
- brew 이용
- 컴파일된 바이너리 다운로드
🚨 주의
apt
를 통해 설치되는 버젼이 매우 낮을 수 있습니다. 반드시 최신의 버젼인지 확인 합니다. apt
를 통한 설치는 추천하지 않습니다.
# 추천하지 않음
$ apt install protobuf-compiler
python 모듈 설치
- Python
3.7
이상 - pip version 9.0.1 이상
$ 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버젼을 기본으로 사용하기 때문에 반드시 작성해 줍니다.
syntax = "proto3";
message Person {
string name = 1;
}
message Data {
Person person = 1;
}
message 작성법
message
는 protobuf의 핵심 구성 요소로, 데이터 구조를 정의하는 데 사용됩니다. 기본적인 작성법에 대해서 알아보겠습니다.
코드
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;
- 중괄호
{}
안에 필드들을 정의 - message 키워드로 시작하고, 메시지 이름을 지정
- 각 필드는
타입 이름 = 번호;
형식으로 정의 - 각 필드에는 고유한 번호를 할당 (1-536,870,911 범위).
- 1-15 범위의 번호는 인코딩 시 1바이트만 사용하므로, 자주 사용되는 필드에 권장
데이터 타입
repeated string hobbies = 4;
message Address {
string street = 1;
string city = 2;
}
Address address = 5;
- 문자열(string), 정수(int32, int64), 부동소수점(float, double), 불리언(bool) 등
- 열거형(Enum) 정의 : message 내부에 enum을 정의 가능
- 반복 필드 : repeated 키워드를 사용하여 배열이나 리스트와 같은 반복 필드를 정의
- 중첩 메시지 : message 안에 다른 message를 정의 가능
필드 옵션
optional
: proto3에서는 기본적으로 모든 필드가 optional로 취급required
: proto2에서 사용. proto3에서는 사용되지 않음
주석
//
: 한 줄 주석/* */
:여러 줄 주석
.proto 컴파일
src 폴더
src
폴더를 만들고 해당 위치에서 아래의 컴파일 명령을 실행합니다.
project/
├── protos/
│ └── person.proto
└── src/
└── (현재 위치)
protoc
protoc
를 이용하여 .proto
파일을 컴파일해 파이썬 코드를 만들어 냅니다.
$ python -m grpc_tools.protoc -I../protos --python_out=. ../protos/person.proto
I../protos
: proto 파일을 찾을 디렉토리를 지정-python_out=.
: 생성된 Python 코드를 현재 디렉토리에 출력../protos/person.proto
: 컴파일할 proto 파일의 경로를 지정
결과 파일
결과 파일의 이름은 원본 .proto 파일 이름에 _pb2
가 추가됩니다. person.proto 파일을 이용해 person_pb2.py라는 파일이 생성됩니다.
project/
├── protos/
│ └── person.proto
└── src/
└── *person_pb2.py (결과 파일)
_pb2.py
생성된 코드는 다음과 같은 내용을 포함합니다.
- 메시지 클래스 정의
- 필드 정의 및 메타데이터
- 열거형(Enum) 정의
- 직렬화/역직렬화 메서드
- 디스크립터(Descriptor) 정보
직렬화(Serialization)
main.py
파일을 추가하고 컴파일된 파일을 통해 메시지를 바이너리 형식으로 직렬화하는 방법을 알아보겠습니다.
project/
├── protos/
│ └── person.proto
└── src/
└── person_pb2.py
└── *main.py (추가 파일)
코드
Person
클래스는 person.proto 파일에 정의된 Person 메시지를 의미합니다.- protobuf 객체의 name 필드에 값을 할당합니다.
SerializeToString
메시지를 바이너리 형식으로 직렬화합니다. 결과는 바이트 문자열입니다.
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 같은 데이터를 표현하는 포맷 입니다. 문자열 기반인 다른 포맷과 달리 이진 데이터를 활용합니다.
- 크기가 작고 빠르지만, 읽거나 디버깅이 어렵다
- 스키마 파일을 작성 →
.proto
- 컴파일 결과 파일을 구현에 사용 →
_pb2
- 직렬화 →
SerializeToString
- 역직렬화 →
ParseFromString
- 다른 포맷 직렬화, 역직렬화 지원
gRPC에서 Protocol Buffers를 사용하기 때문에 기본적인 사용법을 이해하고 있으면 gRPC를 이해하는데 도움이 됩니다. 😊