개발/데이터 엔지니어링

Apache Avro를 이용한 Kafka Message Schema 관리(1)

무심경 2022. 7. 10. 22:40

Apache Avro란?


Apache Avro™ is a data serialization system 
- Apache Avro™ 1.11.0 Documentation

Apache Avro는 데이터 직렬화에 관한 내용을 포괄하는 시스템이다.

 

다만 우리가 실제로 사용할 때는 이렇게 거창하게 생각할 필요는 없고 아래의 세 가지 기능을 제공하는 시스템이라고 생각하면 편하다. (실제 공식문서를 봐도 각 언어별로 사용하는 법 중심으로 간결하게 정리되어 있다)

1. 메시지 스키마를 제공해 준다

아래와 같이 Avro 스키마를 JSON으로 쉽게 정의할 수 있다. 파일의 확장자는 .avsc 로 저장하면 된다

{"namespace": "example.avro",
 "type": "record",
 "name": "User",
 "fields": [
     {"name": "name", "type": "string"},
     {"name": "favorite_number",  "type": ["int", "null"]},
     {"name": "favorite_color", "type": ["string", "null"]}
 ]
}

 

2. 해당 스키마에 맞게 메시지를 직렬화/역직렬화 해준다

위와 같이 정의된 스키마를 읽어와, 직렬화/역직렬화에 사용할 수 있다

// 1. Schema를 불러온다
Schema schema = new Schema.Parser().parse(new File("user.avsc"));

// 2. Schema에 맞게 User를 생성한다
GenericRecord user1 = new GenericData.Record(schema);
user1.put("name", "Alyssa");
user1.put("favorite_number", 256);
// Leave favorite color null

GenericRecord user2 = new GenericData.Record(schema);
user2.put("name", "Ben");
user2.put("favorite_number", 7);
user2.put("favorite_color", "red");

// 3. 직렬화/역직렬화에 사용한다
// Serialize user1 and user2 to disk
File file = new File("users.avro");
DatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(schema);
DataFileWriter<GenericRecord> dataFileWriter = new DataFileWriter<GenericRecord>(datumWriter);
dataFileWriter.create(schema, file);
dataFileWriter.append(user1);
dataFileWriter.append(user2);
dataFileWriter.close();

// Deserialize users from disk
DatumReader<GenericRecord> datumReader = new GenericDatumReader<GenericRecord>(schema);
DataFileReader<GenericRecord> dataFileReader = new DataFileReader<GenericRecord>(file, datumReader);
GenericRecord user = null;
while (dataFileReader.hasNext()) {
// Reuse user object by passing it to next(). This saves us from
// allocating and garbage collecting many objects for files with
// many items.
user = dataFileReader.next(user);
System.out.println(user);

 

3. (필요하다면) 스키마를 소스코드로 만들어 준다(Code Generation) 

avro-tools-1.11.0.jar 파일을 가지고 있다면, 아래와 같이 정의된 스키마를 Java 코드로 변환할 수 있다

java -jar /path/to/avro-tools-1.11.0.jar compile schema user.avsc .

생성된 User 클래스는 아래와 같다. 앞뒤로 수많은 Boilerplate Code를 생략하고 보면, 우리가 Schema 파일에 정의한 필드들을 가지고 있음을 확인할 수 있다.

/**
 * Autogenerated by Avro
 *
 * DO NOT EDIT DIRECTLY
 */
package example.avro;

import org.apache.avro.generic.GenericArray;
import org.apache.avro.specific.SpecificData;
import org.apache.avro.util.Utf8;
import org.apache.avro.message.BinaryMessageEncoder;
import org.apache.avro.message.BinaryMessageDecoder;
import org.apache.avro.message.SchemaStore;

@org.apache.avro.specific.AvroGenerated
public class User extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
  private static final long serialVersionUID = -3588479540582100558L;

  // ... 생략
  
  // 스키마에 정의된 필드들
  private java.lang.CharSequence name;
  private java.lang.Integer favorite_number;
  private java.lang.CharSequence favorite_color;

  // ... 생략
}

 

Avro를 통한 Kafka Message Schema 관리


이런 Avro의 대표적인 사용례는 바로 Producer와 Consumer 간의 Message Schema 관리이다. 그럴 수 밖에 없는 것이, Producer와 Consumer가 동일한 스키마 정보를 가지고 있어야 메시지를 주고받는데에 문제가 없을 것이고 Avro의 핵심이 바로 통일된 스키마를 제공해 주는 것이기 때문이다.

 

그렇다면, Producer와 Consumer간에 어떻게 동일한 스키마를 공유할 수 있을까? 다양한 방법이 있을 수 있겠지만, 조사해 본 결과 일반적으로 아래의 두 가지 방법이 사용되는 것 같다.

1. 별도의 Schema Registry Server에서 스키마를 제공 

 

오직 스키마만 관리하는 별도의  Schema Registry Server를 두고 Producer와 Consumer는 해당 서버로부터 스키마 정보를 받아 사용하는 방식이다.

 

장점 : 스키마 변경이 일어나도, 실시간으로 Registry에 반영되므로 문제가 없다

단점 : Schema Registry Server 구축 및 관리의 어려움. API 요청 증가로 인한 부하 발생

 

2. Git Submodule을 이용해 Schema Repository를 공유

 

Unreal Plugins as Git Submodules &ndash; Bionic Ape

스키마를 공통의 Repository에 저장해 두고, Producer와 Consumer가 공통으로 해당 Repository를 Submodule로 가지고 있는 방식이다. (위의 사진의 .py.avsc 로 바뀐다고 생각하면 된다)

 

장점 : Producer와 Consumer 입장에서는 개발이 매우 간단하다 (avsc파일을 그냥 가져다 쓰면 된다)

단점 : Git Submodule에 대한 이해와 관리가 필요. 실시간으로 일어나는 스키마 변경에는 대응하지 못한다 (Producer와 Consumer의 재빌드 및 재배포가 필요하다)

 

두번째 방법은 개발이 정말 쉬워진다는 장점이 있지만 스키마의 실시간 변경에 대응이 어렵다치명적인 문제점을 가지고 있어 범용적으로 사용되고 있지는 않은 것 같다. 다만, 스키마의 실시간 변경이 잦지 않고, 변경이 일어나더라도 관련 시스템의 재배포가 수월한 상황이라면 Git Submodule을 이용한 방법도 나쁘지 않은 것 같다.  

 

다음 글에서는, 두 번째 방법인 Schema Repository 공유를 통한 스키마 관리 방법을 실습해 보도록 하자!