gRPC Encoding 및 주의 사항

mohadang·2023년 9월 24일
1

Road to Backend

목록 보기
19/21
post-thumbnail

Encoding 기본 원리

message 들은 일련의 Key-Value 쌍으로 이루어진 binary data로 인코딩된다.

Key는 Field Number 뿐만 아니라, 해당 Field의 data type을 지시하는 "Wire Type"을 표현.

Key 구성

  • Key = (field_number << 3) | wire_type
  • key(1byte) : Filed Number(5bit) + Wire Type(3bit)

Wire Type

Ex) string query = 1

  • field_number ==> 1
  • wire_type ==> 2

Key = (field_number << 3) | wire_type
Key = 00001000 | 00000010 = 00001010 = 0x0A

Ex) int32 result_per_page = 3

  • field_number ==> 3
  • wire_type ==> 0

Key = 00011_000
Key = 0x18

Varients Type(wire type 0)

정수를 Serializing 하는 Varints Encoding.

Varints에 포함되는 정수형 타입들은 첫 byte의 1bit를 무조건 뒷 byte에 대한 지시 역할을 하는 msb(most significant bit)를 가지고 있음.

  • 이 값이 1이면 뒷 데이터가 더 있다는 것이고, 0이라면 이어지는 byte stream과는 분리된다
  • “least significant group first”

Field number는 정수형이므로 Varints Encoding 법칙을 따른다.

  • 따라서 1byte key에서 wire type 부분을 제외한 5bit 중 1bit가 msb로 사용.
  • key가 1byte라도 실제 필드 번호 값을 담는 크기는 4bit이기 때문에 1~15까지만 표현 가능.

Ex) 300 Encoding

300을 이진수로 표현(2byte 필요)
==> 256 + 32 + 8 + 4 = 1|00101100

Varints 직렬화를 위해선 byte 당 msb가 포함돼야 하므로 7bit 단위로 구분.
==> 10|0101100
==> X0000010_X0101100

least significant group first 룰에 맞게 byte를 역순으로 나열.
==> X0101100_X0000010

msb 설정
==> '1'0101100_'0'0000010
1 : 뒤에 데이터 있음.
0 : 뒤에 데이터 없음.

디코딩은 거꾸로 수행한다.

Signed Integers

만약 protocol buffer에서 음의 정수를 표기한다면 signed int(sint) 형을 사용하는게 효율적.

일반 int형에서는 음수로 사용 시 절댓값에 관계없이 항상 고정된 byte 크기를 잡는데, signed int형은 ZigZag Encoding으로 이미 부호있는 정수를 부호없는 정수로 매핑시켜 두었기 때문. ??

https://stackoverflow.com/questions/765916/is-there-ever-a-good-time-to-use-int32-instead-of-sint32-in-google-protocol-buff

  • use uint32 if the value cannot be negative
  • use sint32 if the value is pretty much as likely to be negative as not (for some fuzzy definition of "as likely to be")
  • use int32 if the value could be negative, but that's much less likely than the value being positive (for example, if the application sometimes uses -1 to indicate an error or 'unknown' value and this is a relatively uncommon situation).

If you use int32 or int64 as the type for a negative number, the resulting varint is always ten bytes long - it is, effectively, treated like a very large unsigned integer. If you use one of the signed types, the resulting varint uses ZigZag encoding, which is much more efficient.

  • int32/int64를 음수의 유형으로 사용하는 경우 결과 varint의 길이는 항상 10바이트. 사실상 매우 큰 부호 없는 unsgined integer 처리. sint32/sint64 하나를 사용하는 경우 결과 varint는 훨씬 더 효율적인 ZigZag 인코딩을 사용합니다.

ZigZag encoding maps signed integers to unsigned integers so that numbers with a small absolute value (for instance, -1) have a small varint encoded value too.

  • ZigZag 인코딩은 부호 있는 정수를 부호 없는 정수로 매핑하므로 절대값이 작은 숫자(예: -1)도 작은 varint 인코딩 값을 갖게 됩니다.

It does this in a way that "zig-zags" back and forth through the positive and negative integers, so that -1 is encoded as 1, 1 is encoded as 2, -2 is encoded as 3, and so on.

  • 양수와 음수를 앞뒤로 "지그재그"하는 방식으로 이를 수행하므로 -1은 1로 인코딩되고, 1은 2로 인코딩되고, -2는 3으로 인코딩됩니다.

Non-Varint number type(wire type 1, 5, 2)

wire type 1 : 64bit double 등은 고정된 64bit 데이터를 지시.
wire type 5 : 32bit형 float 등은 고정된 32bit 데이터를 지시.

wire type 2

  • string 같이 wire type이 2인 경우, varints 형태에서 길이를 지시하는 byte가 추가.
    • key를 읽어서 wire type이 2라면 그 다음 바이트는 길이를 지정.
  • value는 UTF-8 인코딩 사용.

Ex) string query = 1, value에 "testing" 문자열 저장
field_number ==> 1
wire_type ==> 2
"testing" 문자열 길이 ==> 7

Encoding 결과 ==> '0A' '07' 74 65 73 74 69 6e 67

  • 0x0A(Key) : 0001_010 = field_num(1) & wire_type(2: string)
  • 0x07(value 길이) : 7 byte.
  • UTF-8 encoding "testing"
  • wire type 2 데이터의 길이가 0xFF = 255bytes 이상이라면 아래와 같이 2byte 이상으로도 표기.
    • default message limit = 4 1-24 1024 = 4Mbyte

Non-Varint number type(Embedded Message)

message 정의 시 field type을 다른 메시지로도 지정할 수 있다.(메시지간 포함 관계)

  • a 필드는08 96 01으로 encoding.
  • c 필드는 wire type = 2로 취급되고 길이 byte(03)가 추가되어 1a '03' 08 96 01 encoding.

int32 a = 150

08 96 01
field num = 1 & wire type = 0 (int32)
==> 00001000 ==> 0x08

150 : 1|0010110
==> 00000001 00010110
==> 00010110 00000001
==> '1'0010110 '0'0000001 ==> 96 01

Text1 c = 3

filed num = 3 & wire type = 2 (embedded Mesgage)
==> 00011_010

Sclar Type 별 주의사항.

Enumeration

첫번째 값을 default value로 사용한다.

  • 첫 번째 값이 default 값으로 지정되므로 첫 상수는 0으로 지정해줘야 한다

enum은 상수를 저장하는 열거형 데이터이며, encoding 하면 const 상수 형태로 저장되고, 각 name / value 별 map이 별도로 지정.

값들은 세미콜론(;)으로 구분. 구분하지 않았을때 에러 발생.

allow_alias 옵션 : 다른 name에 같은 값을 줄 수 있다. 향후 value를 참조할 때는 복수 개의 값이 있을 테니 oneof로 하나만 선정하여 사용.

enum 값을 삭제하거나 주석처리하여 update 시켰다면 다른 사용자가 이 값을 재 사용하게될 수도 있는데, 이는 proto file의 버전이 안맞을 경우 데이터 손상 등의 문제를 일으킬 수 있다.

  • 예전 버전에서 사라졌는데 최신 버전에서 값이 다시 부활하여 정상 동작 되는것 처럼 보이는 버그 발생

이를 방지하고 싶다면, value / name을 더이상 접근할 수 없게 reserve. 단, 아래 예제와 같이 reserved 만 선언한다면 error가 리턴되므로 default 값을 고려하여 enum 첫 줄엔 하나 이상의 상수를 선언 필요.

map

맵의 필드들은 repeated 될 수 없으며, 언어별로 구현 방식이 상이함.

인증

암호화 인증이 필요한 경우 두 가지 메커니즘을 지원

  • ssl/tls
  • token

ssl/tls

  • 대체적으로 사용. 인증 후 암호화 통신을 진행.
  • client의 경우 dial 옵션으로, Server에선 신규 grpc 서버 생성 시 적용.

token

  • gRPC로 Google API 접근 시 OAuth2 token과 같은 google access token이 지원.

트러블 슈팅

Server Down일때 Client가 요청.

  • Server가 준비되지 않은 상태에서 Client가 요청하는 경우, retansmission은 8번씩 새로운 session이 맺어질 때까지 무한정 시도

Server down 후 다시 up.

  • 일반적으로 Server가 down 된다면 Client는 timeout에 걸려 통신이 종료.
    • default timeout = 1s
  • 만약 timeout 되기전에 Sever Down 감지 후 recovery 되어 올라왔을 때 retransmission에 응답하여 새로운 세션으로 연결.

Client timeout이 충분히 커도, 8회 초과시 close.

  • timeout이 충분히 큰 경우라도 8회 Retransmission까지 응답이 없으면 해당 통신은 종료.

L4환경에서 failover.

  • VIP로 gRPC 요청할 때, session이 할당된 active server down 시엔 기존 session은 FIN 종료 후 즉시 새로운 session으로 연결.
    • 참고로 L4 구동 모드는 Proxy이며 Round Robin 알고리즘으로 LB 설정.
profile
mohadang

1개의 댓글

comment-user-thumbnail
2024년 4월 20일

잘 읽었습니다.

답글 달기