티스토리 뷰

golang

Protocol Buffers - overview

사용자 fistful 2019. 6. 6. 22:25
반응형

개요

 

공식 사이트의 소개를 둘러 보았음.

특히나 Encoding 부분이 재미있었음

- https://developers.google.com/protocol-buffers

 

Overview

 

"Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data."

- 구조화된 데이터를 직렬화하는, 확장가능한 매커니즘이며, 프로그래밍 언어, 플랫폼에 무관하게 사용할 수 있다.

 

Developer Guide

 

링크: https://developers.google.com/protocol-buffers/docs/overview

Serialize 하고픈 정보를 .proto 파일에 정의하면 된다. 아래 예시를 보라.

 

보기만 해도 무슨 소리인지 알겠다.

 

사람의 정보를 메시지에 담아 보내는데

 

name, id 는 꼭 들어가야 하고

email 은 생략 가능하며

phone 은 반복될 수 있다. (= 여러 개의 전화번호)

 

PhoneNumber

- number 는 꼭 들어가야 하고

- type 은 생략가능하다. 생략하면 default HOME

 

 

그리고 이걸 protoc 로 컴파일 하면 사용하는 프로그래밍 언어에 맞게 data access classes 가 생성된다.

가장 중요한건 나중에 새로운 field 를 추가해줘도 된다는 것이다. 이전 binary 들은 추가된 걸 그냥 무시해버린다.

 

XML 보다 좋은 점은

- 씸플하고,

- 3 to 10 배 작고

- 20 to 100 배 빠르며

- 모호하지 않으며

- data access class 들을 생성해줘서 프로그래밍 하기 쉽다.

 

Protocol buffers 의 현재

Protocol buffers are now Google's lingua franca for data at time of writing, there are 306,747 different message types defined in the Google code tree across 348,952 .proto files. They're used both in RPC systems and for persistent storage of data in a variety of storage systems.

Protocol buffers 는 구글의 데이터를 다루는 일종의 공용어가 되었다.  대표적인 쓰임새는

RPC 시스템, 그리고 다양한 저장 시스템에서 데이터를 영구 저장하는 용도이다.

 

Encoding

 

링크: https://developers.google.com/protocol-buffers/docs/encoding

데이터가 어떻게 byte array 로 저장되는가?

몰라도 되는 것이겠지만 알고나니 재미있다.

 

Varints

 

하나 이상의 바이트로 정수를 직렬화하여 표현하는 것이다.

 

1 1 바이트로 표현된다. 0000 0001

 

300은 어떨까? 1010 1100 0000 0010 이다. 두 바이트.

1) 각 바이트의 첫 비트는 다음 바이트가 있는가를 의미한다.

- 첫 바이트 1010 1100 은 첫 비트가 1 이므로 다음 바이트인 0000 0010 까지가 하나의 정수인거다

- 두번째 바이트 0000 0010 은 첫 비트가 0 이므로 더 이상 이어지는 바이트가 없는 것을 알 수 있다.

2) 그러면 남은 각 바이트의 7비트씩을 이어 붙여보자.

- 010 1100 000 0010 이걸 두 7비트를 순서를 바꿔준다. (least significant group first) 000 0010 010 1100 이다.

- 다시 적어보면 1 0010 1100 이 되며 십진수로는 300 이 된다.

 

 

Message Structure

 

protocol buffer message key-value 쌍이 쭈욱 이어진거다.

serialize 해서 binary 로 만들면, key 는 각 field 에 할당한 숫자이다.

아래 그림의 name = 1, id = 2, email = 3 같은 것이 각각 field key 가 된다.

그래서 정확한 field 의 이름과 타입은 decoding 되어야만 알 수 있다. (.proto 파일에 정의된걸 참조하는 거다)

 


 

만약 decoding 하는데 모르는 key 가 나온다면? 그냥 패쓰해버린다.

이 덕분에 프로그램을 새로 짜지 않고도 새로운 필드를 추가할 수 있게 된다.

 

key 에 대해 좀 더 정확하게 말하자면

 

위에 언급한 field 에 할당한 숫자에 더해 wire type 정보도 들어있다.

wire type 을 통해 실제 데이터의 길이를 알 수 있게 되며, wire type 의 종류는 아래와 같다.

 


 

한 바이트에 어떻게 field number wire type 을 구겨넣을까?

앞의 5비트는 field number 이고, 뒤의 3비트가 wire type 이 된다.

 

예를 들어보자. 08 96 01 라고 serialize 되었다면

08 = 0000 1000 이므로

- field number = 00001 = 1

- wire type = 000 = 0

따라서 field number 1 이고, wire type 0 이된다. 

96 01 = 150 이 된다.


 

Signed Integers

 

int32 int64 의 경우 음수를 표현하려면 무조건 10 바이트가 필요해진다.

sint32 sint64 의 경우는 ZigZag 라는 방식을 이용해서 음수를 표현하는데 이 경우 작은 음수는 작은 바이트를 소모하는 효과가 있다.

아래의 오른쪽을 보면 무슨 말인지 이해가 갈 것이다. -1 1, -2 3 과 같이 표현한다.

 


 

Non-varint Numbers

 

double 이나 fixed64 wire type 1 이다. 이 경우는 그냥 64비트 (= 8바이트) 를 무조건 떼어내면 된다.

float fixed32 wire type 5 이다. 이 경우는 32 비트 (= 4바이트) 를 무조건 떼어내서 계산하면 된다.

둘 다 little-endian 이다.

 

 

String

 

wire type 2 이며 length-delimited 이다. 길이가 얼마인지를 별도 명시하는 것이다.

 

 

위와 같은 메시지가 있고, b "testing" 이라는 문자열을 넣었다면 아래와 같이 저장된다.

 

 

1) 빨간색 부분은 testing ASCII code 이고

2) 12 0001 0010 이니깐

- field number 00010 = 2

- wire type 010 = 2 가 된다.

3) 07 은 문자열의 길이가 7 이라는 말

 

Embedded Messages

 

 

 

메시지 Test3 안에 Test1 이 들어가 있다.

이때 Test1 a 150 을 넣으면 아래와 같이 serialize 된다.

 

 

1) 08 96 01 은 이미 위에서 한 번 설명한대로

- field number = 1, wire type = 0

- 값은 150 이다.

2) 1a = 0001 1010 이으로

- field number = 00011 = 3

- wire type = 010 = 2 가 되며

3) 03 은 그 길이가 3 이라는 것이다. 08 96 01

 

 

Optional And Repeated Elements

 

proto3 만 챙겨 보겠음

 

repeated fields packed encoding 을 사용한다. 반복되는 field 간의 공간이 없도록 패킹 하겠다는 의미

repeated fields 가 아닌 경우는 key-value 쌍이 있거나 없을 수 있다.

- 두 쌍 이상이 존재할 수 없지만, 존재하더라도 parser 가 처리한다.

- 숫자의 경우 맨 마지막꺼를 쓴다.

- embedded message field 의 경우 parser 가 같은 field 의 값들을 merge 해버린다.

- repeated fields 의 경우는 합쳐 버린다. (concatenate)

 

Packed Repeated Fields

 

proto3 만 챙겨 보겠음

 

스칼라 숫자값을 가지는 repeated fields 의 경우는 default packed 된다.

실제 예시를 보면 이해가 빠르겠다.

 

 

위 메시지의 d 3 ,270 86,942 를 넣었다면 encoding 은 아래와 같이 된다.

 


1) field number = 00100 = 4

2) wire type = 010 = 2

3) 각각 한 바이트, 두 바이트, 세 바이트 씩을 차지한다.

 

03 = 0000 0011 이니 첫 비트 0 을 보고 1 바이트 크기만 자르고

8E 02 = 1000 1110 0000 0010 이니 첫 비트 1 0 을 보고 2바이트 크기만 자르고

9E A7 05 의 경우는 아래와 같으니 3 바이트 크기만큼 잘라서 읽는다

1001 1110

1010 0111

0000 0101

 

오직 primitive numeric types packed 될 수 있다. varint, 32-bit, 64-bit wire types

 

 

Field Order

 

어느 field 를 먼저 Serialize 하는가는 정해진게 없다. 맘대로 구현해도 된다.

 

parsing 하면 같아지겠지만 serialize byte 들은 같다는 보장이 없다. 아래는 모두 false 이다.

 


 

Techniques

링크: https://developers.google.com/protocol-buffers/docs/techniques

1. 여러 개의 메시지를 스트리밍으로 보내려면

 

메시지간의 구분을 어떻게 할 수 있을까?

각 메시지의 처음에 메시지 길이를 명시해줄 수 있겠다.

 

1) 메시지 사이즈을 읽고

2) 사이즈만큼 읽어서 별도의 버퍼에 넣어둔다음

3) 버퍼를 파싱한다.

 

2. Large Data Sets

 

애초에 Protocol buffers 는 큰 사이즈의 메시지용이 아니다.

메시지 하나에 1메가바이트가 넘어간다면 다른 방법을 고민해볼 때다.

 

사실 큰 데이터라는 건 작은 녀석들을 모아둔 경우가 많다.

그 작은 녀석들을 Protocol buffers 로 각개격파를 해주면 되는거다.

 

3. Self-describing Messages

 

Protocol buffers 메시지 자체로는 파싱 정보가 부족하다. .proto 파일이 있어야만 한다.

... .proto 파일 자체를 protocol buffers 로 표현할 수 있다!

 

1) 소스코드의 src/google/protobuf/descriptor.proto 는 포함된 메시지 타입을 정의한다.

2) protoc FileDescriptorSet 을 생성해낼 수 있다.

- 이것은 .proto 파일들을 표현해주는 것이며

- --descriptor_set_out 옵션을 줘서 생성한다.

 


 

C++ Java 에서 쓸 수 있는 DynamicMessage 클래스 같은 걸 이용하면

이러한 SelfDescribingMessage 를 다루는 툴을 만들 수 있다.

 

하지만 왜 이런걸 Protocol Buffer library 에 안넣었을까? 를 생각해보자.

구글에서 protocol buffer 를 사용할때 이런걸 (혹은 이딴걸) 써본 적이 없기 때문이다.


반응형

'golang' 카테고리의 다른 글

gRPC - Go Quick Start  (1) 2019.06.10
Protocol Buffers - Golang Tutorial  (0) 2019.06.07
Protocol Buffers - overview  (1) 2019.06.06
justforfunc #30: The Basics of Protocol Buffers  (0) 2019.06.04
Staircase Problem  (0) 2019.05.30
General Egg Problem  (0) 2019.05.24
댓글
댓글쓰기 폼