Accumulo 통합

Accumulo 통합 (Accumulo Integration)

Apache Accumulo는 Google BigTable 페이퍼를 기반으로 하는 정렬된 분산 키-값 스토어예요. Hive를 Accumulo의 SQL 인터페이스로 활용하면 Accumulo의 높은 처리량 배치 접근과 저지연 무작위 조회를 보완할 수 있어요. AccumuloStorageHandler로 테이블을 매핑하고, 컬럼 매핑·인덱싱·서버 측 필터 푸시다운까지 활용할 수 있답니다.

출처: 문서

본문

개요 (Overview)

Apache Accumulo는 Google BigTable 페이퍼를 기반으로 하는 정렬된 분산 키-값 스토어예요. Accumulo가 제공하는 API 메서드는 데이터 읽기/쓰기에 가장 높은 유연성을 제공하는 Keys와 Values의 관점에서 표현돼요. 하지만 더 높은 수준의 쿼리 추상화는 보통 사용자의 몫으로 남겨져요. Apache Hive를 Accumulo의 SQL 인터페이스로 활용하면 Accumulo의 기존 고처리량 배치 접근과 저지연 무작위 조회를 보완해요.

구현 (Implementation)

초기 구현은 HIVE-7068로 Hive 0.14에 추가됐고 Accumulo 1.6.x와 함께 동작하도록 설계됐어요. 구현을 구성하는 두 가지 주요 컴포넌트는 AccumuloStorageHandler와 AccumuloPredicateHandler예요. AccumuloStorageHandler는 StorageHandler 구현이에요. 이 클래스의 주요 역할은 Hive 테이블과 Accumulo 테이블의 매핑을 관리하고 Hive 쿼리를 구성하는 거예요. AccumuloPredicateHandler는 필터 연산을 Accumulo로 푸시다운해 데이터를 더 효율적으로 줄이는 데 사용돼요.

Accumulo 구성 (Accumulo Configuration)

필요한 유일한 추가 Accumulo 구성은 Hive 배포의 일부로 제공되는 hive-accumulo-handler.jar를 Accumulo 서버 클래스패스에 포함시키는 거예요. 이는 jar를 $ACCUMULO_HOME/lib 또는 $ACCUMULO_HOME/lib/ext에 복사/심볼릭 링크하거나 accumulo-site.xml의 general.classpaths에 jar 경로를 포함시켜 다양한 방법으로 할 수 있어요. jar가 비동적 방식($ACCUMULO_HOME/lib 또는 accumulo-site.xml의 general.classpaths 사용)으로 클래스패스에 추가되면 Accumulo tabletserver를 재시작해야 해요.

사용법 (Usage)

Hive를 사용해 Accumulo에 쿼리를 실행하려면 Hive 구성에서 네 가지 매개변수를 제공해야 해요:

커넥션 매개변수
accumulo.instance.name
accumulo.zookeepers
accumulo.user.name
accumulo.user.pass

Accumulo에 익숙한 사용자라면 이 네 가지 구성은 Accumulo에 연결하는 일반적인 구성 값이에요: Accumulo 인스턴스 이름, ZooKeeper quorum(콤마 구분 호스트 목록), Accumulo 사용자 이름과 비밀번호. 이 값을 제공하는 가장 쉬운 방법은 hive 명령의 -hiveconf 옵션을 사용하는 거예요. 제공된 Accumulo 사용자가 새 테이블을 만들 수 있거나, Hive 쿼리가 기존 Accumulo 테이블에만 접근할 것으로 기대돼요.

hive -hiveconf accumulo.instance.name=accumulo -hiveconf accumulo.zookeepers=localhost -hiveconf accumulo.user.name=hive -hiveconf accumulo.user.pass=hive

Accumulo 테이블에 접근하려면 STORED BY 절과 함께 CREATE 명령으로 Hive 테이블을 만들어야 해요. CREATE 호출에서 EXTERNAL 키워드를 생략하면 Accumulo 테이블의 수명 주기는 Hive 테이블의 수명에 묶여요: Hive 테이블이 삭제되면 Accumulo 테이블도 삭제돼요. 이것이 기본 사례예요. EXTERNAL 키워드를 제공하면 Accumulo 테이블을 참조하는 Hive 테이블을 만들지만, Hive 테이블이 삭제돼도 밑바탕 Accumulo 테이블은 제거하지 않아요.

각 Hive 행은 같은 row ID를 가진 일련의 Accumulo 키에 매핑돼요. Hive 행의 한 컬럼은 Accumulo row ID로 사용되는 "특수" 컬럼으로 지정돼요. 행의 다른 모든 Hive 컬럼은 Hive 컬럼 값이 Accumulo 값에 배치되는 Accumulo 컬럼(컬럼 family와 qualifier)에 어떤 매핑이 있어요.

CREATE TABLE accumulo_table(rowid STRING, name STRING, age INT, weight DOUBLE, height INT)
STORED BY 'org.apache.hadoop.hive.accumulo.AccumuloStorageHandler'
WITH SERDEPROPERTIES('accumulo.columns.mapping' = ':rowid,person:name,person:age,person:weight,person:height');

위 문에서 일반 Hive 컬럼 이름·타입 쌍은 일반 create table 문과 같이 제공돼요. Accumulo가 이 Hive 테이블을 백엔드한다는 것을 Hive에 알리기 위해 전체 AccumuloStorageHandler 클래스 이름이 제공돼요. SERDEPROPERTIES 또는 TBLPROPERTIES로 AccumuloStorageHandler를 구성하기 위해 많은 속성을 제공할 수 있어요. 가장 중요한 속성은 "accumulo.columns.mapping"으로, Hive 컬럼이 Accumulo 컬럼에 매핑되는 방식을 제어해요. 이 경우 "row" Hive 컬럼은 Accumulo Key의 Accumulo row ID 컴포넌트를 채우는 데 사용되고, 다른 Hive 컬럼(name, age, weight, height)은 모두 Accumulo 행 내의 컬럼이에요.

"accumulo_table"의 위 스키마에 대해 테이블의 단일 행을 상상할 수 있어요:

hive> select * from accumulo_table;
row1	Steve	32	200	72

위 레코드는 선언된 accumulo.columns.mapping이 주어지면 다음과 같이 Accumulo Key-Value 쌍으로 직렬화돼요:

user@accumulo accumulo_table> scan
row1	person:age []	32
row1	person:height []	72
row1	person:name []	Steve
row1	person:weight []	200

컬럼 매핑의 강점은 서로 다른 컬럼 매핑을 가진 여러 Hive 테이블이 같은 Accumulo 테이블과 상호작용해 다른 결과를 낼 수 있다는 거예요. 컬럼이 제외되면 Hive 쿼리 성능은 서버 측에서 원하지 않는 데이터를 걸러내는 Accumulo 지역성 그룹(locality groups)을 통해 개선될 수 있어요.

컬럼 매핑 (Column Mapping)

컬럼 매핑 문자열은 오프셋이 테이블의 Hive 스키마에 대응하는 인코딩된 값의 콤마 구분 목록이에요. Hive 스키마의 컬럼 순서는 컬럼 매핑의 요소가 의도한 Hive 컬럼과 정렬되는 한 임의일 수 있어요. Accumulo에 익숙한 사용자에게 컬럼 매핑 문자열의 각 요소는 column_family:column_qualifier와 비슷해 보여요. 하지만 다양한 제어를 허용하는 몇 가지 다른 변형이 있어요.

  1. 단일 컬럼
    1. Hive 컬럼의 값을 주어진 column family와 column qualifier로 Accumulo 값에 배치해요.
  2. 컬럼 qualifier 맵
    1. column family가 제공되고 임의 길이의 column qualifier 접두사에 별표(*)가 이어질 수 있어요.
    2. Hive 컬럼 타입은 Map이어야 하며, Hive 맵의 키는 column qualifier 접두사에 추가돼요.
    3. Hive 맵의 값은 Accumulo 값에 배치돼요.
  3. rowid
    1. 어떤 Hive 컬럼을 Accumulo rowid로 사용할지 제어해요.
    2. 각 컬럼 매핑에는 정확히 하나의 ":rowid" 요소가 있어야 해요.
    3. ":rowid"는 대소문자를 구분하지 않아요 (:rowID는 :rowId와 동일).

추가로, 값이 어떻게 직렬화될지 제어하는 직렬화 옵션을 컬럼 매핑의 각 요소에 제공할 수 있어요. 현재 옵션은 다음과 같아요:

  • 'binary' 또는 'b'
  • 'string' 또는 's'

이들은 컬럼 매핑 요소 뒤에 파운드 부호('#')와 길거나 짧은 직렬화 값을 포함해 설정돼요. 기본 직렬화는 'string'이에요. 예를 들어 값 10의 경우 "person:age#s"는 "person:age"와 동의어이며 리터럴 문자열 "10"으로 직렬화해요. 대신 "person:age#b"를 사용하면 값이 4바이트(\x00\x00\x00\xA0)로 직렬화돼요.

인덱싱 (Indexing)

HIVE-15795로 Hive 3.0.0부터 Accumulo 백엔드 Hive 테이블에 인덱싱 지원이 추가됐어요. 인덱싱은 데이터 테이블의 rowId에 대한 필드 값 매핑을 저장하기 위해 또 다른 Accumulo 테이블을 사용해 동작해요. 인덱스 테이블은 Hive를 통한 레코드 삽입 시 자동으로 채워져요.

인덱스 테이블을 사용하면 전체 테이블 스캔을 제거해 비-rowId 술어 쿼리의 성능을 크게 개선해요. 인덱싱은 Tez 또는 Map Reduce 쿼리 엔진을 사용하는 내부 및 외부 관리 테이블 모두에서 동작해요. 다음 옵션이 인덱싱 동작을 제어해요.

옵션 이름 설명
accumulo.indextable.name (필수) Accumulo의 인덱스 테이블 이름.
accumulo.indexed.columns (선택) 인덱싱할 hive 컬럼의 콤마 구분 목록, 또는 모든 컬럼을 인덱싱하는 * (기본: *)
accumulo.index.rows.max (선택) 각 검색 술어에 대해 인덱스에서 스캔할 최대 술어 값 수 (기본: 20000) 이 값에 대한 참고
accumulo.index.scanner (선택) 인덱스 스캐너 구현 (기본: org.apache.hadoop.hive.accumulo.AccumuloDefaultIndexScanner)

인덱스는 다음 형식으로 인덱스 테이블에 저장돼요:

rowId = [field value in data table]

column_family = [field column family in data table] + '_' + [field column quantifier in data table]

column_quantifier = [field rowId in data table]

visibility = [field visibility in data table]

value = [empty byte array]

문자열 인코딩 테이블을 사용할 때 인덱스된 필드 값은 숫자 타입에 대해 Accumulo Lexicoder 메서드를 사용해 인코딩돼요. 그렇지 않으면 값은 네이티브 이진 인코딩으로 인코딩돼요. 이 정보는 애플리케이션이 Hive 외부에서 데이터와 인덱스 값을 Accumulo에 삽입하되 Hive 내에서 고성능 쿼리를 여전히 요구할 수 있게 해줘요.

Hive 외부에서 데이터와 인덱스를 삽입할 때 두 테이블을 같은 작업 단위 내에서 갱신하는 것이 중요하다는 점에 유의해요. Hive 쿼리가 쿼리 술어 중 어떤 것에 대해 인덱스 일치를 찾지 못하면 쿼리는 데이터 테이블을 검색하지 않고 단락(short circuit)되어 빈 결과를 반환해요.

검색 술어가 옵션 accumulo.index.rows.max(기본 20000)로 정의된 것보다 더 많은 항목과 일치하면 인덱스 검색 결과는 폐기되고 쿼리는 술어 필터링으로 데이터 테이블의 전체 스캔으로 폴백해요. 이 옵션에 큰 값을 사용하거나 아주 큰 데이터 테이블 rowId 값을 가지면 메모리 오류를 방지하기 위해 hive 메모리를 늘려야 할 수 있음을 기억해요.

기타 옵션 (Other options)

다음 옵션도 AccumuloStorageHandler의 동작을 더 제어하기 위해 SERDEPROPERTIES 또는 TABLEPROPERTIES와 함께 유효해요:

옵션 이름 설명
accumulo.iterator.pushdown 필터 술어가 Accumulo 내에서 Iterators를 사용해 충족되어야 하는지 (기본: true)
accumulo.default.storage 값의 기본 저장 직렬화 메서드 (기본: string)
accumulo.visibility.label Accumulo에 어떤 레코드를 쓸 때 사용할 정적 ColumnVisibility 문자열 (기본: 빈 문자열)
accumulo.authorizations Accumulo를 스캔할 때 사용할 authorization의 콤마 구분 목록 (기본: 없음). Accumulo에 연결하기 위해 제공된 Accumulo 사용자는 모든 제공 authorization을 가져야 함에 유의
accumulo.composite.rowid.factory rowid 컬럼의 ObjectInspector를 변경하지 않고 rowid에서 LazyObject를 만들 때 사용자 정의 클래스를 제공할 수 있게 하는 확장 지점
accumulo.composite.rowid rowid 컬럼을 LazyObject로 사용자 정의 파싱할 수 있게 하는 확장 지점
accumulo.table.name 사용할 Accumulo 테이블 이름 제어 (기본: Hive 테이블 이름)
accumulo.mock.instance 실제 인스턴스에 연결하는 대신 MockAccumulo 인스턴스 사용 (기본: false). 테스트에 유용

예제 (Examples)

Accumulo 테이블 이름 재정의

사용자의 고유 키, 사용자 ID, 사용자 이름으로 구성된 사용자 테이블을 만들어요. Accumulo row ID는 Hive 컬럼에서, 사용자 ID 컬럼은 "f" 컬럼 family와 "userid" column qualifier로, 사용자 이름 컬럼은 "f" 컬럼 family와 "nickname" column qualifier로 쓰여요. "users" Accumulo 테이블 대신 TBLPROPERTIES에서 "hive_users" Accumulo 테이블을 사용하도록 재정의돼요.

CREATE TABLE users(key int, userid int, username string) 
STORED BY 'org.apache.hadoop.hive.accumulo.AccumuloStorageHandler'
WITH SERDEPROPERTIES ("accumulo.columns.mapping" = ":rowID,f:userid,f:nickname")
WITH TBLPROPERTIES ("accumulo.table.name" = "hive_users");

이진 직렬화로 Hive 맵 저장

컬럼 매핑 문자열에서 별표를 사용하면 Hive 맵이 단일 Accumulo Key-Value 쌍에서 여러 Key-Value 쌍으로 확장될 수 있어요. Hive Map은 매개변수화된 타입으로, 아래 경우 키는 문자열이고 값은 정수예요. 기본 직렬화가 'string'에서 'binary'로 재정의되는데, 이는 Hive 맵 값의 정수가 UTF-8 문자열 표현 대신 일련의 바이트로 저장된다는 뜻이에요.

CREATE TABLE hive_map(key int, value map<string,int>) 
STORED BY 'org.apache.hadoop.hive.accumulo.AccumuloStorageHandler'
WITH SERDEPROPERTIES (
  "accumulo.columns.mapping" = ":rowID,cf:*",
  "accumulo.default.storage" = "binary"
);

외부 테이블 등록

external 키워드로 Hive 테이블을 만들면 Accumulo 테이블의 수명 주기를 Hive 테이블에서 분리해요. 이 테이블을 만들 때 "countries" Accumulo 테이블이 이미 존재한다고 가정해요. 이는 Hive가 어떤 외부 도구(예: MapReduce 잡)가 만들고 채운 테이블을 관리하는 매우 유용한 방법이에요. countries Hive 테이블이 삭제돼도 Accumulo 테이블은 삭제되지 않아요. 또한 external 키워드는 같은 밑바탕 Accumulo 테이블에서 동작하는 다른 옵션으로 여러 Hive 테이블을 만들 때 유용할 수 있어요.

CREATE EXTERNAL TABLE countries(key string, name string, country string, country_id int)
STORED BY 'org.apache.hadoop.hive.accumulo.AccumuloStorageHandler'
WITH SERDEPROPERTIES ("accumulo.columns.mapping" = ":rowID,info:name,info:country,info:country_id");

인덱스 테이블 생성

인덱싱을 활용하려면 Hive가 각 필드에 대해 사전순(lexicographically) 정렬된 검색어 인덱스를 만들기 위해 다른 Accumulo 테이블을 사용해 매우 효율적인 정확 일치와 경계 범위 검색을 가능하게 해요.

CREATE TABLE company_stats (
   rowid string,
   active_entry boolean,
   num_offices tinyint,
   num_personel smallint,
   total_manhours int,
   num_shareholders bigint,
   eff_rating float,
   err_rating double,
   yearly_production decimal,
   start_date date,
   address varchar(100),
   phone char(13),
   last_update timestamp )
ROW FORMAT SERDE 'org.apache.hadoop.hive.accumulo.serde.AccumuloSerDe'
STORED BY 'org.apache.hadoop.hive.accumulo.AccumuloStorageHandler'
WITH SERDEPROPERTIES (
   "accumulo.columns.mapping" = ":rowID,a:act,a:off,a:per,a:mhs,a:shs,a:eff,a:err,a:yp,a:sd,a:addr,a:ph,a:lu",
   "accumulo.table.name"="company_stats",
   "accumulo.indextable.name"="company_stats_idx"
 );

감사의 말 (Acknowledgements)

이 스토리지 핸들러의 기반이 된 Brian Femiano의 노력을 언급하지 않을 수 없어요. 그의 Accumulo-Hive 통합 초기 프로토타입이 이 작업의 기반이 됐어요.

더 알아보기 (Learn more)