HBase에서의 Protobuf

HBase에서의 Protobuf

HBase는 메타데이터를 영속화하거나 객체를 네트워크로 주고받을 때 Google의 protobuf를 사용해요. 이 문서는 HBase가 protobuf를 어떻게 활용하는지, 그리고 hbase-2.0.0 이후 버전에서 shading이 도입된 배경과 사용법을 설명해요. 메타데이터 직렬화와 RPC 인터페이스를 다루는 개발자라면 꼭 알아야 할 내용이에요.

출처: 문서

본문

Protobuf 개요

HBase는 메타데이터를 영속화하는 곳마다 — hfile의 꼬리 부분이나 HBase가 시스템 hbase:meta 테이블에 쓰는 Cells, HBase가 zookeeper에 znodes를 쓸 때 등 — 그리고 RPC를 통해 객체를 네트워크로 주고받을 때 Google의 protobufs를 사용해요. HBase는 클라이언트에 노출하는 RPC 인터페이스(Service)를 기술할 때 protobuf를 사용하는데, 예를 들어 RegionServer가 처리하는 Admin과 Client 인터페이스, 또는 개발자가 Coprocessor Endpoint 메커니즘을 통해 추가하는 임의의 확장을 지정할 때가 그렇죠.

protobuf를 사용하면 .protos 파일에 직렬화와 서비스를 기술해요. 그다음 이 디스크립터를 protobuf 도구인 protoc 바이너리에 넣어, 기술된 직렬화를 marshall/unmarshall하고 지정된 Service를 처리하는 클래스를 생성해요.

모듈별로 클래스 생성을 실행하는 방법에 대한 자세한 내용은 HBase 하위 모듈의 README.txt를 참고하세요. 예를 들어 hbase-protocol/README.txt에서 hbase-protocol 모듈의 protobuf 클래스를 생성하는 방법을 볼 수 있어요.

HBase에서 .proto 파일은 hbase-protocol 모듈에 있어요. 이 모듈은 공통 proto 파일과 HBase가 메타데이터 직렬화에 내부적으로 사용하는 protoc 생성 클래스를 담당하는 전용 모듈이에요. REST나 Coprocessor Endpoint처럼 자체 디스크립터가 필요한 HBase 확장은 해당 기능의 호스팅 모듈 안에 protos가 위치해요. 예를 들어 hbase-rest는 REST proto 파일의 홈이고, hbase-rsgroup 테이블 그룹핑 Coprocessor Endpoint는 테이블 그룹핑과 관련된 모든 protos를 가지고 있어요.

Protos는 그것을 사용하는 모듈이 호스팅해요. 이렇게 하면 protobuf 클래스 생성이 모듈별로 분산되지만, 각 모듈이 자신이 HBase에 가져오는 기능과 관련된 모든 것을 캡슐화하도록 하기 위함이에요.

REST든 Coprocessor Endpoint든 확장은 hbase-protocol 모듈에 있는 핵심 HBase protos를 사용해요. CPEP Service를 제공하는 과정에서 Cell이나 Put을 직렬화하거나 ServerName으로 특정 노드를 참조할 때 이 핵심 protos를 사용하게 돼요. 앞으로 hbase-2.0.0 릴리스 이후에는 이 관행이 사라져야 해요. 그 이유는 아래 hbase-2.0.0 섹션에서 설명할게요.

hbase-2.0.0과 protobuf shading (HBASE-15638)

hbase-2.0.0부터는 protobuf 사용이 조금 더 복잡해져요. HBase 코어의 protobuf 참조는 사적인(private), 번들된 protobuf를 가리키도록 오프셋이 조정돼요. 코어는 com.google.protobuf._의 protobuf 클래스를 참조하는 대신 HBase 전용 오프셋인 org.apache.hadoop.hbase.shaded.com.google.protobuf._에서 protobuf를 참조해요. 이렇게 간접 참조(indirection)를 두는 이유는 HBase 코어가 의존성이 의존하는 protobuf 버전과 무관하게 자체 protobuf 버전을 발전시킬 수 있게 하기 위해서예요. 예를 들어 HDFS는 protobuf로 직렬화하는데, HDFS는 우리 CLASSPATH에 있어요. 위에서 설명한 간접 참조가 없다면 우리 protobuf 버전이 정렬되어야 해요. HBase는 HDFS가 업그레이드하기로 결정할 때까지 HDFS protobuf 버전에 묶이게 되고, HBase와 HDFS 버전이 결합되어 버려요.

우리는 protobuf-2.5.0에서 벗어나야 했는데, 그 이유는 protobuf-3.1.0에 추가된 기능이 필요했기 때문이에요. 특히 복사본을 줄이고 직렬화/역직렬화를 위해 protobuf를 힙(heap)에 올리지 않게 하려는 것이죠.

hbase-2.0.0에서 우리는 hbase-protocol-shaded라는 새 모듈을 도입했는데, 이 모듈에 protobuf와 그 후속 relocation/shading에 관한 모든 것을 담았어요. 이 모듈은 본질적으로 옛 hbase-protocol의 많은 부분을 복사한 것이지만 shading/relocation 단계가 하나 더 추가된 것이에요. 코어는 이 새 모듈에 의존하도록 변경됐어요.

그래도 Coprocessor Endpoint(CPEP) 주변에는 복잡한 문제가 생겨요. CPEP는 com.google.protobuf.*의 protobuf 클래스를 명시적으로 참조하는 공용 HBase API에 의존해요. 예를 들어 Table 인터페이스에는 CPEP Service를 얻어 호출을 실행하는 아래와 같은 방법이 있어요.

...
  <T extends com.google.protobuf.Service,R> Map<byte[],R> coprocessorService(
   Class<T> service, byte[] startKey, byte[] endKey,
     org.apache.hadoop.hbase.client.coprocessor.Batch.Call<T,R> callable)
  throws com.google.protobuf.ServiceException, Throwable

기존 CPEP는 ServerNames를 지정하거나 Mutations를 전달하면서 코어 HBase protobuf를 참조했을 거예요. hbase-2.0.0 이후 업그레이드에서도 CPEP와 그 com.google.protobuf.* 참조를 계속 서비스할 수 있도록, HBase는 com.google.protobuf.* 참조와 내부 오프셋 org.apache.hadoop.hbase.shaded.com.google.protobuf.* protobuf를 모두 처리할 수 있어야 해요.

hbase-protocol-shaded 모듈은 HBase 코어가 사용하는 모든 protobuf를 호스팅해요.

하지만 CPEP가 (shading되지 않은) hbase-protocol 콘텐츠에 남겨둔 잔존 참조를 위해, 앞으로도 이 모듈의 대부분을 CPEP가 사용할 수 있도록 유지해요. hbase-protocol의 대부분을 유지하면 겹치고 '중복된' proto 인스턴스가 생기는데, 어떤 것은 옛 모듈 위치에 non-shaded/non-relocated로 존재하고 또 어떤 것은 hbase-protocol-shaded 아래에 새 위치로 shaded되어 존재해요. 즉 생성된 protobuf 클래스 org.apache.hadoop.hbase.protobuf.generated.ServerName는 hbase-protocol에 있고, 또 다른 생성 인스턴스는 protobuf 참조가 내부 shaded 버전인 org.apache.hadoop.hbase.shaded.protobuf.generated.ServerName(패키지 이름 중간에 'shaded'가 추가된 것에 주목)을 가리킨다는 점만 빼고 모든 면에서 동일하게 존재해요.

내부 사용을 위해 hbase-protocol-shaded의 proto를 확장한다면 hbase-protocol에서도 확장(그리고 재생성)하는 것을 고려하세요.

앞으로 우리는 공용 API만큼 변경에 대한 보장이 있는 CPEP용 공통 타입의 새 모듈을 제공할 거예요. TODO.

hbase-3.0.0의 protobuf 변경 (HBASE-23797)

hadoop(3.3.x부터)도 protobuf를 shading하고 버전을 3.x로 올렸으므로, 우리가 protobuf 2.5.0에 남아 있을 이유가 없어졌어요.

HBase 3.0.0에서 hbase-protocol 모듈은 제거(purge)됐고, CPEP 구현은 hbase-protocol-shaded 모듈의 protos를 사용해야 하며, hbase-thirdparty의 shaded protobuf도 사용해야 해요. 일반적으로 protobuf 버전에 심각한 문제(예: protobuf의 심각한 CVE)가 없는 한 한 메이저 릴리스 전체에서 protobuf 버전 호환성을 유지할 거예요.

pom에 다음 의존성을 추가하세요:

<dependency>
  <groupId>org.apache.hbase.thirdparty</groupId>
  <artifactId>hbase-shaded-protobuf</artifactId>
  <!-- use the version that your target hbase cluster uses -->
  <version>${hbase-thirdparty.version}</version>
  <scope>provided</scope>
</dependency>

그리고 일반적으로 생성된 protobuf 코드가 hbase-thirdparty의 shaded/relocated protobuf 버전을 사용하도록 pom에 이 플러그인도 추가해야 해요.

<plugin>
  <groupId>com.google.code.maven-replacer-plugin</groupId>
  <artifactId>replacer</artifactId>
  <version>1.5.3</version>
  <executions>
    <execution>
      <phase>process-sources</phase>
      <goals>
        <goal>replace</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <basedir>${basedir}/target/generated-sources/</basedir>
      <includes>
        <include>**/*.java</include>
      </includes>
      <!-- Ignore errors when missing files, because it means this build
           was run with -Dprotoc.skip and there is no -Dreplacer.skip -->
      <ignoreErrors>true</ignoreErrors>
      <replacements>
        <replacement>
          <token>([^\.])com.google.protobuf</token>
          <value>$1org.apache.hbase.thirdparty.com.google.protobuf</value>
        </replacement>
        <replacement>
          <token>(public)(\W+static)?(\W+final)?(\W+class)</token>
          <value>@javax.annotation.Generated("proto") $1$2$3$4</value>
        </replacement>
        <!-- replacer doesn't support anchoring or negative lookbehind -->
        <replacement>
          <token>(@javax.annotation.Generated\("proto"\) ){2}</token>
          <value>$1</value>
        </replacement>
      </replacements>
  </configuration>
</plugin>

hbase-examples 모듈의 org.apache.hadoop.hbase.coprocessor.example 패키지 아래에 몇 가지 예시가 있어요. BulkDeleteEndpoint와 BulkDelete.proto에서 자세한 내용을 볼 수 있고, hbase-examples 모듈의 pom.xml에서 위 플러그인을 사용하는 방법을 확인할 수 있어요.

더 알아보기 (Learn more)