티스토리 뷰

저는 구글 프로토콜 버퍼를 좀 색다른 경로로 알게 되었지만, 포스팅을 작성하면서 검색한 결과 gRPC로부터 프로토콜 버퍼의 유입이 많다는 것을 알게 되었습니다.

 

구글 프로토콜 버퍼는 구글에서 오픈소스로 공개한 언어 및 플랫폼에 중립적이면서 프로토콜 통신, 데이터 저장 등에 대해 직렬화하여 사용할 수 있는 데이터 구조(data structure)입니다. 줄여서 protobuf, 더 줄여서 pb라고도 부릅니다.

 

구글에서는 프로토콜 버퍼를 다음과 같이 설명합니다 :

프로토콜 버퍼는 구조적 데이터(XML과 같은)를 직렬화하기 위한 유연하고 효율적인 자동화된 메커니즘이며, 작고 빠르고 간결합니다. 여러분이 여러분의 데이터가 어떻게 구조화되기를 원하는 지를 결정했다면, 다양한 언어를 사용하여 다양한 형태의 데이터 스트림으로부터 구조화된 데이터에 쉽게 읽고 쓰기 위해 특수 생성 코드(special generated source code)를 사용할 수 있습니다. 이전 형식으로 컴파일된 배포 프로그램을 중단하지 않고도 데이터 구조를 업데이트할 수 있습니다.

 

 

 

vs XML


구조적 데이터는 XML, JSON 같은 기존 형식 포맷이 존재합니다. 따라서 굳이 이것을 사용해야 하나는 의구심이 들 수도 있습니다. 구글에서는 XML보다 이 프로토콜 버퍼를 사용해야하는 이유를 다음과 같이 설명합니다 :

  • 더 간단합니다
  • 3 ~ 10배 더 작습니다
  • 20 ~ 100배 더 빠릅니다
  • 덜 모호합니다
  • 프로그래밍 방식으로 사용하기 더 쉬운 데이터 접근 클래스(data access classes)를 생성합니다.

 

그림으로 보면 다음과 같습니다 :

그림 1. XML 포맷과 읽어들이는 방식의 예
그림 2. 프로토콜 버퍼와 읽어들이는 방식의 예

 

그렇다고, 구글의 프로토콜 버퍼는 모든 경우에서 최적의 솔루션인 것은 아닙니다. 프로토콜 버퍼가 xml, json과 같이 값에 대한 메시지 정의를 포함하고 있을 때 의미 있는 결과를 낼 수 있으며, html과 같이 텍스트와 메시지 구조가 혼용되어 있는 마크업 문서와 같은 데서는 텍스트와 구조를 인터리브(interleave, 교차로 배치하다)하기 쉽지 않기 때문에 좋은 방법이 될 수 없습니다.

 

프로토콜 버퍼와 기존의 XML, JSON에 대해서 자세한 비교사항을 알고 싶다면 다음 링크를 참조하시면 도움이 될 것 같습니다.

 

 

 

사용법


프로토콜 버퍼를 사용하기 위해서는, .proto 파일에서 프로토콜 버퍼 메시지 타입을 정의하여 직렬화할 정보의 구조화 방법을 지정해야 합니다. 언어 중립적인 목적을 달성하기 위해 데이터 타입을 독특한 방식으로 정의합니다.

 

각 프로토콜 버퍼 메시지는 일련의 이름-값 쌍을 포함하는 단순한 정보 레코드입니다. .proto 파일을 작성하는 기본적인 예는 다음과 같습니다 :

// 개인 정보를 저장하는 메시지를 정의합니다.
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

 

 

필드 타입

위 정의에서 사용할 수 있는 타입의 종류는 일반적인 프로그래밍 언어에서 볼 수 있는 프리미티브 타입과 유사함을 알 수 있습니다. 사용가능한 타입은 다음과 같습니다(링크) :

  • double / float
  • int32 / int64 : 가변 길이 인코딩이지만, 음수 인코딩이 비효율적입니다. 음수를 사용할 것 같으면 sint계열을 사용하세요.
  • sint32 / sint64 : 가변 길이 인코딩이지만, int계열보다 효율적으로 음수를 인코딩합니다.
  • uint32 /uint64 : 가변 길이 인코딩입니다.
  • fixed32 / fixed64 : 항상 4바이트를 차지하지만, 2^28보다 큰 수에 대해 uint32에 비해 훨씬 효율적입니다.
  • sfixed32 / sfixed64 : 항상 4바이트를 차지합니다.
  • bool
  • string : 반드시 인코딩이 UTF-8이거나 7비트 아스키 코드여야 합니다.
  • bytes : 임의 길의의 바이트 시퀀스

 

 

필드 숫자

위에서 볼 수 있는 특이한 점은, 각각의 메시지 필드마다 정수값이 (=1,=2,=3) 형태로 붙어있는 것을 볼 수 있는데, 직렬화 및 역직렬화 시 순서를 매칭하는 데에 사용됩니다. 필드 넘버는 할당한 필드 번호에 따라 인코딩하는 방식이 달라질 수 있습니다. 이와 관련해서는 다음 링크를 참조하시기 바랍니다(링크).

 

 

필드 규칙

필드 규칙은 필드를 수식하는 한정자로써 3가지가 있습니다. 좀 더 기술적인 사항도 다루고 있으므로 궁금한 분들은 다음 링크를 참조하시기 바랍니다(링크) :

  • required : 필드 값을 제공해야합니다.
  • optional : 필드가 설정되거나 설정되지 않을 수 이
  • repeated : 메시지가 0개 이상의 이 필드를 반복할 수 있으며, 이 때 반복된 값의 순서는 유지됩니다.

 

이외에도 더 많은 규칙과 설명이 있지만, 그러한 부분은 아래 *_프로토 언어 가이드 *_섹션에서 살펴보시길 바라겠습니다.

그림 3. 프로토콜 버퍼

 

일단 작성한 후에는 이 포맷에 접근하기 위해 클래스를 만들어야 하는데 이 때 protocol buffer 컴파일러를 proto 파일을 대상으로 실행시켜서 생성할 수 있습니다. 그렇게 생성된 클래스(Person)은 다음과 같이 사용할 수 있습니다(c++ 언어 기준입니다) :

Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);

 

파일로부터 메시지를 읽어들일 때는 다음과 같이 사용할 수 있습니다 :

fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

 

좀 더 상세한 예시를 원하시는 분들은 https://corgipan.tistory.com/8 사이트에서 참조하시는 것도 도움이 될 것 같습니다.

 

 

프로토버퍼 언어 가이드


위 코드로 프로토콜 버퍼의 사용법을 다 알기는 쉽지 않습니다. 이 포스팅에서는 프로토콜 버퍼에 대해 간략히 알아보는 목적이므로, 프로토콜 버퍼에 대한 언어 가이드를 확인하고 싶다면 다음의 링크를 참조하시기 바랍니다:

 

 

 

protobuf 라이브러리 사용하기


다음 포스팅에서 구글 프로토콜 버퍼 라이브러리 사용하는 방법을 포스팅하였습니다. 관심 있으신 분들은 이 글을 참조하시면 좋을 것 같습니다.

 

 

 

 

참고 자료


문서 참조
Google - Protocol Buffers
조대협의 블로그 - 구글 프로토콜 버퍼
코기판 - protobuf란 무엇인가? (gRPC 시리즈 #2)
joinc - PROTOCOLBUFFER  
ZepehWave - 비주얼 스튜디오에서 구글 프로토콜 버퍼(porotobuf) 라이브러리 빌드하기

그림 참조
그림 3 - https://corgipan.tistory.com/8

댓글