
java.io.Serializable 같은 java 의 내장 직렬화는 성능이 좋지 않고 비대해지곤 한다. 이런 이유로, 언어에 내장된 부호화를 사용하는 것은 보통 좋지 않다.
그럼에도 JSON, XML, CSV 는 다양한 용도에서 사용하기 충분하고, 앞으로도 인기가 많을 것이다.
JSON, XML 은 이진 형식에 비해 더 많은 공간을 사용한다. 이에 따라 JSON, XML 용의 다양한 이진 부호화(binary encoding) 개발이 이루어졌다.
그러나 JSON 과 XML 의 텍스트 버전처럼 널리 채택되지 않았다. 이진 부호화가 가독성을 떨어뜨리는 것에 비해 텍스트 부호화에 비해 공간을 절약하는 정도가 그리 크지 않다는 의견이다.
Apache Thirft(by Facebook) 와 Protocol Buffers(protobuf, by Google) 는 같은 원리를 기반으로 한 이진 부호화 라이브러리이다.
스키마를 정의하면, 해당 스키마로 다양한 프로그래밍 언어로 스키마를 구현한 클래스를 생성한다. 이 클래스로 스키마의 레코드를 이진 부호화/복호화할 수 있다.
부호화된 데이터는 필드의 이름이 아닌 스키마에서 필드에 정의한 숫자인 필드 태그(field tag) 를 사용한다. 이를 통해 부호화된 데이터의 크기를 더욱 줄일 수 있다.
syntax = "proto3";
package demo;
message Simple {
string foo = 1; // ← 이 숫자 1이 "필드 태그(field number)"
}
Thrift 에는 BinaryProtocol과 CompactProtocol이 있다. CompactProtocol이 더 적은 용량을 사용한다.
Thrift의 BinaryProtocol
Thrift의 CompactProtocol
가변 길이 정수 메커니즘
7비트씩 자르고 최상위 비트엔 지속 비트(continuation bit) 를 사용하여 뒤에 바이트가 더 오는지 알려줌
- 각 바이트 형식: c | b7 b6 b5 b4 b3 b2 b1
- b1 ~ b7: 데이터 빝츠
- c: continuation bit. 1비트면 뒤에 바이트가 더 오고 0이면 마지막 바이트
- 예시
- 300 = 1 0010 1100(0b)
- 아래쪽부터 7비트씩: 0010 1100(0x2C), 나머지 0000 0010(0x02)
- 첫 바이트에 “더 있음” 표시로 MSB=1 → 0x2C | 0x80 = 0xAC
- 두 번째 바이트는 마지막이므로 MSB=0 그대로 0x02
- 전송 바이트: AC 02
Protobuf 는 Thrift 의 CompactProtocol와 매우 비슷하게 동작한다.
스키마 발전(schema evolution): 스키마가 시간이 지남에 따라 변하는 것
Thrift 와 Ptotobuf 에서 어떻게 상위호환성/하위호완성을 달성하면서 스키마 발전을 할 수 있을까?
필드명 수정
부호화된 데이터엔 필드명이 없고 필드 태그만 있기 때문에, 필드 태그는 수정하지 않되, 해당 태그의 필드명을 스키마에서 수정할 수 있다.
필드 추가
필드에 새로운 태그 번호를 부여하여 스키마에 새로운 필드를 추가할 수 있다.
상위 호환성 달성 필드 추가
새로 추가된 태그 번호는 이전 스키마를 가지고 있는 예전 코드에선 단순히 부호화에서 누락시키기 때문에, 상위호환성을 달성할 수 있다.
하위 호환성 달성 필드 추가
새로운 필드에 required 를 사용해선 안된다. required 를 사용하면, 새로운 스키마를 가지고 있는 새 코드가 옛 스키마의 데이터 복호화에 실패하고 만다. required 가 아닌 optional 이나 기본값으로 새로운 필드를 추가해야한다.
필드 삭제
필드 삭제는 필드를 추가할 때, 하위 호환성/상위 호환성 문제를 해결하는 방식과 반대로 하면 된다. 즉, optional 필드만 삭제할 수 있고 같은 태그 번호는 절대 다시 사용할 수 없다.
필드의 데이터 타입을 바꾸는 것은 위험하다.
32 비트 정수를 64 비트 정수로 바꾸는 것은 하위 호환성을 가능하나 상위 호환성은 깨지게 된다.
Protobuf 에서 optional 표시자를 repeated 로 변경하는 것은 상위 호환성과 불안전한 하위 호환성을 만족시킬 수 있다. repeated 는 같은 필드가 여러번 부호화 데이터에 나타날 수 있다는 것을 의미하며 프로그래밍 언어에서는 배열로 표시된다. optional 필드가 레코드에 존재하지 않으면 repeated 에서는 원소가 없는 배열로 읽게 되는 것이다. optional 을 사용한 예전 코드에서는 원소 중 마지막 원소만 보게 된다.
Thrift 와 Protocol Buffers와는 다른 또 하나의 binary encoding 형식
Avro history
Avro 스키마 작성 방법
읽기, 쓰기 스키마의 구분
사용자가 작성한 스키마는 읽기 스키마로 사용될 수도 있고, 쓰기 스키마로 사용될 수도 있음
encoding된 바이트배열
이런 avro 데이터를 decoding하려면
아브로의 핵심 아이디어
스키마 해석(resolution): 읽기, 쓰기 스키마 간의 차이를 해소하는 과정
Confluent Cloud doc - schema evolution
BACKWARD: 하위 호환성(V1 - read → V2)
FORWARD: 상위 호환성(V1 ← read - V2)null 타입을 사용하기 위해선 유니온 타입(union type) 을 사용해야 한다.
“type” [”null”, “string”]필드의 데이터 타입 변경이 가능하다. 호환성은 깨질 수 있다.
필드 이름 변경은 가능하지만, 상위 호환성은 깨진다.
필드 삭제는 각 호환성에 따라 조건부로 가능하다.
BACKWARD: 삭제 가능. 새 스키마에서 없는 필드는 무시하기 때문.FORWARD: default가 있는 경우만 가능. 새 스키마로 쓰여져서 해당 필드가 없으면 이전 스키마에선 default 값으로 채워넣음.thrift와 protocol buffer는 코드 생성에 의존한다. 즉, 스키마를 정의한 후 선택한 프로그래밍 언어로 스키마를 구현한 코드를 생성한다.
avro는 정적 타입 프로그래밍 언어를 위해 코드 생성을 선택적으로 제공한다. 하지만 코드 생성 없이도 사용할 수 있다. avro파일만 있으면 즉시 데이터 파일을 열어서 분석을 시작할 수 있다.