[Avro] Data Encoding과 Avro Format

Jaemun Jung
9 min readJan 25, 2021

--

Data Encoding Types

프로그램 내에서 활용되는 데이터는 object, struct, list, array, hash table, tree 등의 data sturcture 형태로 사용되고, 이러한 데이터 구조는 CPU에서 조작할 수 있도록 보통 포인터를 이용해 최적화된다.
데이터를 파일에 쓰거나 네트워크를 통해 전송하려면 바이트열(JSON과 같은)의 형태의 인코딩(encoding) 작업이 필요하다. 포인터는 다른 프로그램이나 프로세스가 이해할 수 없으므로 이 바이트열은 메모리에서 사용하는 데이터 구조와는 다르다.
따라서 두가지 표현 사이에 전환이 필요하다.
인메모리 표현에서 바이트열로의 전환을 인코딩(encoding, 부호화, serializing, 직렬화)이라고 하며, 그 반대를 디코딩(decoding, 복호화, deserializing, 역직렬화)라고 한다.

encode, decode, serialize, deserialize.. (https://symfony-docs-zh-cn.readthedocs.io/components/serializer.html)

언어별 Encoding Formats

대부분의 프로그래밍 언어는 인메모리 객체를 바이트열로 인코딩하는 기능을 내장한다. Java의 java.io.Serializable, 루비의 Marshal, 파이썬의 pickle, 또는 자바 전용인 Kryo와 같은 라이브러리도 있다. 이러한 내장 인코딩 라이브러리는 최소한의 코드로 인코딩 할 수 있으므로 매우 편리하지만 문제점 또한 많다:

  • 보통 특정 프로그래밍 언어와 묶여 있어 다른 언어에서 읽기가 매우 어렵다.
    다른 언어를 사용하는 시스템과 통합하는데 방해가 될 수 있다. MSA 형태의 Polyglot System에서 활용하기 힘들다.
  • 데이터 버전 관리가 힘들다. 상위, 하위 호환성 문제가 있을 수 있다.
  • 효율성이 떨어진다. 예를 들어 자바의 내장 직렬화는 성능이 좋지 않고 비대해지는 직렬화로 알려져 있다.

위와 같은 이유로 언어 내장 인코더를 사용하여 외부 시스템과 데이터를 교환하는 방식은 일반적으로 좋지 않다.

Text Encoding

표준화된 텍스트 인코딩 형식으로써 JSON과 XML, 또는 CSV가 있다. 텍스트 형식이라 사람이 읽기에 유리하다.
그러나 이들도 조금 부족한 점들이 있는데:

  • number 형식의 인코딩이 어렵다. XML, CSV는 number type과 문자열에 숫자가 들어간 형식을 구분할 수 없다. JSON은 문자열과 수를 구분하지만 정수와 부동소수점 수를 구별하지 않는다.
  • JSON과 XML은 유니코드 문자열(사람이 읽을 수 있는 텍스트)를 잘 지원하지만 이진 문자열(문자 인코딩이 없는 바이트열)을 지원하지 않는다. Base64를 사용해 이진 데이터를 텍스트로 부호화해 사용할 수 있기는 하다. 그러나 데이터 크기가 33% 증가한다.
  • 바이너리 타입에 비해 훨씬 큰 공간을 차지한다.

Binary Encoding

작은 데이터셋의 경우에는 인코딩 타입에 따른 영향이 거의 없지만 테라바이트 단위가 되면 데이터 타입의 선택이 큰 영향을 미친다.

JSON이나 XML은 바이너리 타입과 비교하면 훨씬 많은 공간을 사용한다. 이런 이유로 BSON, BJSON, WBXML 등 JSON과 XML을 발전 시킨 바이너리 인코딩 방식이 개발되기는 했으나, 음… 사용 빈도가 보편적이지는 않다.

Thrift and Protocol Buffers(protobuf)

프로토콜 버퍼는 구글에서 개발했고 스리프트는 페이스북에서 개발했다. 같은 원리를 기반으로 한 바이너리 인코딩 라이브러리다.
모두 부호화할 데이터를 위한 스키마가 필요하다. 스리프트를 예시로 들면 다음과 같이 스리프트 인터페이스 정의 언어(interface definition language, IDL)로 스키마를 기술해야 한다.

struct Person {
1: required string userName,
2: optional i64 favoriteNumber,
3: optional list<string> interests

Schema Evolution
스키마는 필연적으로 시간이 지남에 따라 변하기 마련이다. 이를 Schema Evolution이라고 한다. 프로토콜 버퍼와 스리프트는 상위 호환성과 하위 호환성을 유지하면서 스키마를 변경할 수 있다.

  • 상위 호환성은 예전 코드가 새로운 코드로 기록된 레코드를 읽을 수 있는 것
  • 하위 호환성은 새로운 코드가 예전 데이터를 읽을 수 있는 것

Avro

이제 이 글의 주인공인 Avro(보통 아브로라고 읽으면 된다)에 대해서 알아보자. 아브로는 하둡의 하위 프로젝트로 시작 했다. 이것도 또 더그 커팅 형님이 창시했다.

유난히 멋있는 AVRO 로고. 어디서 왔나 했더니 지금은 없어진 영국의 aircraft manufacturer(A.V. Roe and Company)의 로고였다고 한다.

Avro도 인코딩할 데이터를 위해 스키마를 사용한다.
두 개의 스키마 언어가 있는데, 하나는 사람이 편집할 수 있는 Avro IDL이고 하나는 기계가 더 쉽게 읽을 수 있는 JSON 기반 언어다.

  • Avro IDL 예시:
"record Person {
string userName;
union { null, long } favoriteNumber = null;
array<string> interests;
}
  • JSON 기반:
{
"type": "record",
"name": "Person",
"fields": [
{"name": "userName", "type": "string"},
{"name": "favoriteNumber", "type": ["null", "long"], "default": null},
{"name": "interests", "type": {"type": "array", "items": "string"}}
]
}

Avro는 어떻게 Schema Evolution을 지원할까?

애플리케이션이 Avro로 인코딩을 원한다면 알고 있는 스키마 버전을 사용해 데이터를 인코딩한다. 해당 스키마를 애플리케이션에 포함할 수 있고, 이를 쓰기 스키마(writer’s schema)라고 한다.

애플리케이션이 네트워크로부터 수신한 데이터를 복호화하길 원한다면 복호화를 위한 스키마가 필요하다. 이 스키마를 읽기 스키마(reader’s schema)라 한다.

Avro의 핵심 아이디어는 쓰기 스키마와 읽기 스키마가 동일하지 않아도 되며, 단지 호환 가능하면 된다는 점이다. 데이터를 읽을 때(디코딩) Avro 라이브러리는 쓰기 스키마와 읽기 스키마를 모두 살펴보고 쓰기 스키마로부터 읽기 스키마로 데이터를 변환해 그 차이를 해소한다.

  • 쓰기 스키마와 읽기 스키마 간 필드 순서가 달라도 관계 없다. Schema Resolution(스키마 해석) 시 이름으로 필드를 일치시키므로.
  • 데이터를 읽는 코드가 읽기 스키마에는 없고 쓰기 스키마에만 있는 필드를 만나면 이 필드는 무시 한다.
  • 데이터를 읽는 코드가 기대하는 어떤 필드가 쓰기 스키마에는 포함되어 있지 않은 경우에는 읽기 스키마에 선언된 default value로 채운다.
Avro reader가 쓰기 스키마와 읽기 스키마의 차이를 해소한다. (데이터중심 어플리케이션 설계. 그림 4.6)

Writer’s schema는 어떻게 전달할까?

Avro 데이터를 읽는 코드가 특정 데이터를 인코딩한 쓰기 스키마를 어떻게 알 수 있을까? 모든 레코드마다 전체 스키마를 포함시킨다면 데이터보다도 더 커질 수 있다. 이러면 이진 부호화로 절약한 공간이 소용 없어진다.
아래와 같은 전달 방법들이 있다.

  • 대용량 파일
    Avro의 일반적인 용도(하둡과 함께 활용하는)는 수백, 수천만 레코드를 포함한 큰 파일을 저장하는 용도이다. 이 경우 파일의 시작 부분에 쓰기 스키마를 포함시킨다.
    object container file이라는 파일 형식을 명시한다.
  • 데이터베이스
    데이터베이스의 다양한 레코드들은 다양한 쓰기 스키마를 사용해 서로 다른 시점에 쓰여졌을 수 있다. 이 경우 레코드의 시작 부분에 버전 번호를 포함하고 데이터베이스에는 스키마의 버전별 히스토리를 보관한다. 읽기 코드는 레코드에서 버전 번호를 추출한 다음 해당 버전에 해당하는 쓰기 스키마를 가져올 수 있다
  • 네트워크 연결
    네트워크를 통해 통신할 때는 연결 설정에서 스키마 버전 합의를 할 수 있다. 연결을 유지하는 동안 합의된 스키마를 사용한다. Avro RPC protocol이 이렇게 동작한다.

스키마의 장점

위와 같이 프로토콜 버퍼, 스리프트, 아브로는 스키마를 사용해 이진 부호화 형식을 기술한다. 이 스키마 언어는 XML이나 JSON보다 훨씬 더 간단하며 더 자세한 유효성 검사 규칙을 지원한다.
관계형 데이터베이스에도 이러한 데이터베이스 네트워크 프로토콜로부터 응답을 인메모리 데이터 구조로 복호화하는 드라이버(ODBC나 JDBC API)를 제공한다.

JSON, XML, CSV와 같은 텍스트 데이터 타입보다 스키마를 기반으로 한 바이너리 인코딩 타입이 갖는 장점들이 있다.

  • 인코딩된 데이터에서 필드 이름을 생략하므로 다양한 “이진 JSON”보다도 크기가 더 작다.
  • 디코딩을 위해 스키마가 필요하므로 스키마가 최신 상태인지 확신할 수 있다.(문서로 수동으로 관리되는 스키마는 버전과 동떨어지기 쉽다)
  • 스키마의 상위 호환성과 하위 호환성을 확인할 수 있다.
  • Static 타입 프로그래밍 언어(Java, C++)의 경우 컴파일 시점에 타입 체크를 할 수 있다.

Reference

Martin Kleppmann, Designing Data-Intensive Applications(데이터 중심 애플리케이션 설계), 위키북스, pp.113~131, 2018

https://avro.apache.org/docs/current/

--

--

No responses yet