Big Data/HBase&Phoenix

Phoenix - Secondary Index

신씅 2016. 8. 12. 14:30
Phoenix의 document 를 기반으로 작성한 보조 인덱스에 대한 내용이다.
뒷 부분으로 갈 수록 이해하기 어려워 뒷부분은 거의 document 내용 그대로이다.

  • HBase 에서는 primary row key 에 의해 정렬된 단일 인덱스만 존재함
  • primary row key 이외의 방법으로 레코드에 접근하는건 잠재적으로 table 을 full scan 하는 위험이 있음
  • secondary indexing (인덱스로 형성한 column 이나 expression) 이 새로운 축을 따라 검색이나 range scan을 가능하게 함으로써 row key를 대체

covered indexes (index 페이지에서 row 식별자를 통해 index column 외의 데이터는 실제 데이터에 접근)

  • 일종의 보조 인덱스
  • index 에 등록되지 않은 컬럼을 정렬(cost 낭비 방지)없이 index 테이블에만 포함시키기 위한 방법
  • index 테이블 탐색 후, 다시 데이터 테이블(주 테이블) 에 가서 나머지 컬럼들의 데이터를 가져오는 오버헤드를 줄일 수 있음

CREATE INDEX my_index ON my_table (v1,v2) INCLUDE(v3)


functional indexes

  • 함수를 index로 지정

CREATE INDEX UPPER_NAME_IDX ON EMP (UPPER(FIRST_NAME||' '||LAST_NAME))


Global indexes

  • 읽기(read) 작업이 무거운 경우에 쓰임
  • 성능 패널티는 쓰기(write)에서 나타남
  • 쓰기 작업에서의 데이터 테이블의 업데이터를 가로채서 인덱스 업데이트를 작업하여 관련된 인덱스 테이블들을 업데이트 함
  • 읽기 작업 시, phoenix는 가장 빠른 쿼리 수행 시간을 산출하고 HBase table 처럼 직접 스캔하기 위해 위해 인덱스 테이블을 사용할지 말지 결정
  • 기본적으로 hint 가 없는한 indexing 되지 않는 칼럼을 참조하는 쿼리에 대해서는 index 가 사용되지 않음


Local indexes

  • 쓰기(write) 작업이 무거운 경우나 공간의 제약이 있는 곳에 쓰임
  • global index 처럼, 쿼리 시 local index를 사용할 것인지 말 것인지 자동으로 phoenix가 결정
  • local index 에서는 쓰기 작업 동안 네트워크 부하를 방지하기 위해 index 데이터와 table 데이터가 같은 서버에 존재함
  • Local index 는 쿼리가 fully covered(즉, 조회하는 컬럼이 모두 index 에 포함되는 경우) 가 아니더라도 사용될 수 있음
(phoenix 는 자동으로 index 가 아닌 컬럼을 탐색함)
  • global index 와는 달리, table 의 모든 local index 는 single, separate shared table 에 저장됨
  • 읽기 시(read), local index 가 사용될 때는, 데이터를 찾기 위해 모든 region 를 반드시 탐색해야함 (index data 의 정확한 region 위치가 미리 결정될 수 없기 때문에)


Index Population

기본적으로  index 가 생성될 때, “CREATE INDEX” 호출 동안 동기적으로 이주생성됨 (data table ➔ index table 이란 의미겠지?)
  • 이러한 방법은 data table 의 크기에 따라 실행 가능하지 못할 수도 있음
  • index 생성문에 ASYNC 키워드를 이용하여 index 의 초기 이주를생성을 비동기식으로 수행할 수도 있음

CREATE INDEX async_index ON my_schema.my_table (v) ASYNC

  • index table 을 이주생성시키는 map reduce 작업은 HBase 커맨드를 통해 반드시 분리되서 개시되어야 함

${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool
  --schema MY_SCHEMA --data-table MY_TABLE --index-table ASYNC_IDX
  --output-path ASYNC_IDX_HFILES

  • map reduce 작업이 완료되었을 때 index 는 활성화 되고 쿼리에서 사용될 수 있음
  • map reduce 작업은 client 의 종료에 따라 유동적임
  • output-path 옵션은 HFile 이 쓰여질 HDFS 디렉토리를 지정하는데 쓰임


Index Usage

피닉스는 쿼리를 서비스 하는데 인덱스를 사용하는 것이 좀 더 효율적인 방법으로 결정되면 index 를 자동으로 사용함
global index 는 쿼리에서 참조하는 모든 컬럼들이 index 에 포함되어 있지 않으면 index 가 사용되지 않음
아래 예는 쿼리가 index 를 사용하지 않음. v2 칼럼이 index 에 포함되어 있지 않음

SELECT v2 FROM my_table WHERE v1 = 'foo'

위 경우에 3 가지 index 가 사용되게 하기 위한 방법 3가지가 있음

1. covered index 생성
  • v2 컬럼의 data 가 index table 로 copy 됨
  • index 의 크키가 커짐
CREATE INDEX my_index ON my_table (v1) INCLUDE (v2)

2. index 가 사용되도록 hint 를 사용하여 강제
  • index table 에 존재하지 않는 v2  컬럼의 값을 찾기 위해 data row 의 탐색 cost 발생 
  • hint 는 index 가 좋은 선택인지 알고 있는 경우에 사용하여만 함 (이 예제에서 ‘foo’의 데이터는 적음)
  • 기본적으로 full scan 보다 성능이 나음
SELECT /*+ INDEX(my_table my_index) */ v2 FROM my_table WHERE v1 = 'foo'

3. local index 생성
  • global index 와 달리, local index 는 쿼리에소 참조하는 컬럼들이 모두 index 에 포함되지 않더라도 index 가 사용됨
  • data table 과 index table 이 같은 region 에 있기 때문에 local 탐색을 보장하므로 가능함
CREATE LOCAL INDEX my_index ON my_table (v1)


Index Removal

DROP INDEX my_index ON my_table

  • Data table 에서 index 컬럼이 삭제되면, index 는 자동으로 삭제됨
  • Data table 에서 covered 컬럼이 삭제되면, index 에서 자동으로 삭제됨


Index Properties

CREATE TABLE 처럼, CREATE INDEX 명령문도 HBase 테이블의 속성들을 적용할 수 있음(ex: salt)

CREATE INDEX my_index ON my_table (v2 DESC, v1) INCLUDE (v3)
    SALT_BUCKETS=10, DATA_BLOCK_ENCODING='NONE'

  • data table 이 salted 이면, index 는 자동적으로 같은 방법으로 salted 임 (global index의 경우)
  • index 에 대한 MAX_FILESIZE 는 data, index tables 의 크기에 비례하여 아래로 조정된다.
  • 반면 Local index 에는 salting 은 적용시킬 수 없음


Consistency Guarantees (일관성 보장)

  • commit 후 클라이언트에게 return 이 성공이면, 모든 데이터는 관련된 index/data table에 쓰여지는 것이 보장됨
  • 다시 말하면 HBase 에 의해 제공되는 강력한 일관선 보장(consistency guarantee)에 의해 index 갱신은 동기적임
  • 그러나 index 는 data table 보다 분리되어 테이블에 저장되기 때문에, table 의 속성과 index 의 타입에 따라, 서버쪽의 충돌로 인한 commit 이 실패한 경우 table과 index 사이의 일관성은 서 다르다.(???)
  • 설계 고려 시 매우 중요함

Transactional Tables
  • table 을 transactional 로 선언함으로써, table 과 index 간, 최고 수준의 일관성을 얻을 수 있음
  • 이 경우 table 변화에 대한 commit 과 관련된 index의 갱신은 강력한 ACID 보장과 함께 원자성(atomic)을 제공
  • commit 실패 한다면, 아무 데이터도 갱신 되지 않으며, 이렇게 table 과 index 는 항상 동기화가 되어있음
  • 그런데 왜 항상 테이블을 transactional 로 선언하면 안되는 것일까?
    • table 이 immutable 로 선언되어 있다면, 이 경우 transactional 오버헤드는 매우 작음
    • data 가 mutable 이면, transactional table 에서 발생하는 충돌 감지(conflict detection) 와 관련된 오버헤드, 그리고 transaction manager가 실행하면서 생기는 오버헤드를 수용가능 한지 확인해야함
  • Secondary index 가 사용된 transactional table 들은 잠재적으로 data table에 쓰기 작업을 하는 가능성을 낮춤
    • data table 과 secondary index 는 둘다 사용가능한 상태여야 함, 그렇지 않으면 쓰기 작업은 실패함

Immutable Tables
  • table 의 데이터가 오직 한번만 쓰여지고 절대 업데이트가 되지 않는 다면, 증가하는 maintenance 에 대한 쓰기 오버헤드를 줄이기 위해 특정 최적화(optimization) 이루어질 수도 있음
  • 로그나 이벤트 데이터 같은 time-series 데이터 들이 이런 경우
  • IMMUTABLE_ROWS=true 속성을 사용함으로써 이런 최적화 장점을 얻을 수 있음

CREATE TABLE my_table (k VARCHAR PRIMARY KEY, v VARCHAR) IMMUTABLE_ROWS=true

  • IMMUTABLE_ROWS=true 속성으로 선언된 모든 index 와 table 은 immutable 로 간주됨(dafult는 mutable)
  • Global immutable index 에서는, data 변경으로 인해 발생하는 index table 의 index 들은 전적으로 client-side 에 유지됨 (???)
  • Lobal immutable index 에서는, server-side 에 유지됨
  • immutable table 로 선언된 테이블의 데이터가 실제로 갱신되지 않도록 하는 안전장치는 마련되어 있지 않음 (성능상의 이점이 무효화 됨)
    • 이렇게 되면 더 이상 index 와 테이블은 동기화 되지 않음
  • 기존의 immutable table 을 mutable 로 변경하고 싶다면 ALTER TABLE 을 사용

ALTER TABLE my_table SET IMMUTABLE_ROWS=false

  • non-transactional 상의 index 에서 immutable table 은 commit 실패 시 자동적으로 처리할 수 있는 메커니즘이 없음
  • table 과 index 간 일관성을 유지 하는 것은 핸들하기 위한 clietn 에 남겨짐(?????)
    • 갱신 작업은 멱등이기 때문에
    • 가장 간단한 해결책은 성공할 때까지 데이터 조작을 계속 재시도 하는 것

Mutable Tables (좀 이해가 잘 안되는 부분)
  • non-transactional mutable table 에서, data table row의 WAL entry 에 index update 를 추가함으로써 index update 의 내구성을 유지
  • WAL entry 가 디스크에 성공적으로 동기된 후에만, index/pirmary(data) table update를 시도
  • index update 는 높은 처리율을 위해 기본적으로 parallel 하게 진행
  • index update 를 하는 도중 server 가 충돌나면, WAL 복구 process 내의 index table에 모든 index update 를 재실해하고 정확성을 보장하기 위해 update의 멱등성에 의존함(?)
  • non-transactional mutable table 의 index 는 오직 primary(data) table 뒤에서 항상 오직 single-batch
  • 몇 가지 중요 포인트
    • non-transactional table에서 data table하고 index 테이블하고 싱크가 안맞는 경우가 있음
    • 매우 잠깐의 차이이고 매우 짧은 기간동안만 싱크가 안맞는 것이므로 괜찮음
    • 각각의 데이터 row 와 index row들은 쓰여지거나 실패하거나가 보장됨 - hbase의 원자성 보장의 일부로 일부만 갱신된 적이 없음
    • 데이터는 테이블에 먼저 쓰여지고 뒤이어 index 테이블에 쓰여짐 (WAL이 disabled 되어 있으면 반대)

1. Singular Write Path
  • 실패 속성들을 보장하는 단일 쓰기 경로(singular write path) 라는 것이 존재
  • HRegion 으로의 모든 쓰기는 phoenix 의 coprocessor 가 가로채고 나서 index update 를 구축
    • 그리고 나서 index update는 본연의 update 를 위해 WAL entry 에 추가됨
  • 만약 이 지점에서 어떤 실패라도 하면 클라이언트에 실패를 반환하고 데이터는 유지하지 않거나, 클라이언트에 표시함
  • WAL 에 쓰여지고 나면, phoenix 는 index 와 data table 이 표시가능하는 것을 보장함 (실패한 경우라도)
    • 만약 서버 장애가 발생하면, WAL 재시도 메커니즘으로 index update 를 재시도
    • 만약 서버 장애가 아니면, 관련된 table에 index update 를 insert 함
      • index update 가 실패하면, consistency 를 유지하는 다양한 방법들이 있음
      • 오류가 나타난 경우 phoenix 의 system catalog table 에 접근할 수 없으면, phoenix 는 서버에 즉시 취소되고 실패되도록 강제하고 JMV의 System.exit 를 호출하여서버를 죽임
      • 서버를 죽임으로써 WAL 이 복구를 재개하고, 적절한 table에 index update 를 재개하도록 보장함
      • 이것은 secondary index의 사용을 지속할 수 없는 것을 보장함(유효하지 않은 상태)

2. Disallow table writes until mutable index is consistent (mutable index 가 일관될 때까지 table write 막기)
  • non transactional table 과 index 간의 consistency 를 유지하는 최고 수준은 index를 update 하는 것이 실패한 경우에는 data table 에 write 하는 것을 임시적으로 막는 것임
  • consistency mode 에서는 table과 index 는 오류가 나타나기 전의 timestamp를 가지고 있을 것이고, data table 에 데이터를 쓰는것은 index 가 online으로 돌아오고 data table 가 동기화가 될 때까지 막힐 것임
  • index 는 active 로 유지되고 평상시처럼 쿼리에 의해 계속 사용될 것임
  • Server-side 설정
    • phoenix.index.failure.block.write
      • commit 오류가 발생한 경우 index 가 data table 을 따라 잡을 때까지 data table 에 write 하는 것이 실패하도록 하기 위해서는 반드시 true 이어야 함
    • phoenix.index.failure.handling.rebuild
      • commit 오류가 발생한 경우 mutable index 가 백그라운드에서 rebuilding 이 가능하도록 하기 위해서는 반드시 true

3. Disable mutable indexes on write failure until consistency restored (consistency 가 복구 될때까지 쓰기 실패시 mutable index를 비활성화)
  • mutable index 들은 기본적으로 commit 시 쓰기가 실패하면 index 를 비활성화 하기 위해 index 에 마킹하고, 백그라운드에서 부분적으로 rebuild 하고 난 후, consistency 가 복구되고 나면 다시 active 상태로 index 를 마킹함
  • consistency 모드에서, data table 에 write 하는 것은 secondary index 가 rebuild 되어 지는 동안에는 차단 되지 않음
  • 그러나 secondary index 는 rebuild가 발생하는 동안 query 에 사용되지 않을 것임
  • Server-side 설정
    • phoenix.index.failure.handling.rebuild
      • commit 에 실패한 경우 mutable index 가 백그라운드에서 rebuild 가능하게 하기 위해서는 반드시 true (default)
    • phoenix.index.failure.handling.rebuild.interval
      • mutable index 가 data table update 를 따라잡기 위해 부분적인 rebuild 가 필요한지 아닌지 확인하기 위해 서버 체크를 millisecond 주기로 제어
      • 기본은 10000ms(10s)
    • phoenix.index.failure.handling.rebuild.overlap.time
      • 부분적인 rebuild 가 수행될 때, timestamp 에서 실패가 나타난 곳으로 얼마만큼 돌아가야 하는지 제어
      • 기본은 1ms
4. Disable mutable index on write failure with manual rebuild required
  • mutable secondary index 에서 가장 낮은 수준의 consistency
  • 이 경우, secondary index 쓰기가 실패했을 때, index 는 query 에 의해 다시 한번 사용되도록 요구되는 index 의 수동 rebuild 와 함께 비활성화 되도록 마킹됨
  • Server-side 설정
    • phoenix.index.failure.handling.rebuild
      • commit 실패한 경우 mutable index 가 백그라운드에서 rebuild 되는 것으로부터 비활성화 하기 위해 false 로 설정해야 함


Setup
  • non-transactional 에서 mutable indexing 은 region server 와 master 에서 특별한 설정을 요구한다.
    • Phoenix 는 table 에 mutable indexing 을 가능하기 위해 제대로 설정되도록 보장한다.
    • 만약 정확하게 설정되지 않으면, secondary indexing 을 사용할 수 없음
    • 모든 region server 의 hbase-site.xml 에 아래 설정을 추가한 후, 클러스터를 재시작

<property>
  <name>hbase.regionserver.wal.codec</name>
  <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>

  • 위 속성은 커스텀 WAL 가 가능하도록 하고 index update 의 적절한 writing/replay 를 보장함
  • 이 코덱은 WALEdit 압축으로 알려진 WALEdit 옵션의 일반적인 host 를 지원함

<property>
  <name>hbase.region.server.rpc.scheduler.factory.class</name>
  <value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value>
  <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
<property>
  <name>hbase.rpc.controllerfactory.class</name>
  <value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value>
  <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>

  • 위 속성은 data update 보다 index update 에 더 높은 우선순위를 제공함으로 써, global index 의 index 유지보수 동안의 데드락을 방지함
  • 마찬가지로 data roc call 보다 metadata roc call 에 더 높은 우선순위를 제공함으로써 데드락을 방지

  • Local indexing 은 data table 과 index 가 같은 region 에 있도록 master 에서 특별한 설정이 필요하다.(아래 설정을 master의 hbase-site.xml 에 추가)

<property>
  <name>hbase.master.loadbalancer.class</name>
  <value>org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer</value>
</property>
<property>
  <name>hbase.coprocessor.master.classes</name>
  <value>org.apache.phoenix.hbase.index.master.IndexMasterObserver</value>
</property>

  • data region 병합에서 local index 병합을 지원하기 위해 모든 region server 의 hbase-site.xml 파일에 아래 내용 추가후 재시작

<property>
  <name>hbase.coprocessor.regionserver.classes</name>
  <value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value>
</property>


Tuning
  • indexing 은 매우 빠르지만, 최적화 하기 위해 튜닝할 수 있는 속성들이 있다.
  • 이 속성들은 hbase-site.xml 에 설정해야만 함

1. index.builder.threads.max
  • data table update 시, index update 를 위해 사용되는 스레드 갯수
  • 값을 증가시킬 수록 HRegion 의 row 상태를 읽는 bottleneck을 극복할 수 있음
  • 이 값을 너무 높이면 일반적인 스레드-스와핑 문제 뿐만 아니라 너무 많은 동시 scan 요청을 제어할 수 없기 때문에 HRegion 에서 bottleneck 발생 할 수 있음
  • Default: 10

2. index.builder.threads.keepalivetime
  • 빌더 스레드 풀에서 스레드를 만료시키는 시간
  • 사용되지 않는 스레드는 이 시간이 지난 후 바로 릴리즈 되고, core 스레드는 유지되지 않음 (매우 작은 우려이긴 하지만, table은 공평하게 변함없는 쓰기 부하가 유지되길 기대함)
  • 그러나 동시에 예상되는 부하를 알지 못하면 우리에게 스레드 드랍을 허락함
  • Default: 60

3. index.writer.threads.max
  • index table 에 쓸 때 사용할 스레드 수
  • 테이블 기준 당 병렬화 수준 - index table 의 수와 거의 일치함
  • Default: 10

4. index.writer.threads.keepalivetime
  • 쓰기 스레드 풀에서 만료되는 시간(초)
  • 사용되지 않는 스레드는 이 시간이 지난 후 바로 릴리즈 되고, core 스레드는 유지되지 않음 (매우 작은 우려이긴 하지만, table은 공평하게 변함없는 쓰기 부하가 유지되길 기대함)
  • 그러나 동시에 예상되는 부하를 알지 못하면 우리에게 스레드 드랍을 허락함
  • Default: 60

5. hbase.htable.threads.max
  • 쑤기를 위해 사용할 수 있는 index HTable 의 스레드 수
  • 동시 index update 를 증가 시킴 (높은 처리량을 위해)
  • Default: 2,147,483,647

6. hbase.htable.threads.keepalivetime
  • HTbale 의 스레드 풀에서 만료되는 시간
  • “direct handoff’ 방식을 사용하여, 필요하게 되면 새로운 스레드가 생성되고, 무한히 늘어남
  • 이러한 접근법은 나쁠 수 있지만 HTable은 region server 수 만큼의 Runnable 을 생성함
  • 그러므로 새로운 region 서바가 추가될 때 확장됨
  • Default: 60

7. index.tablefactory.cache.size
  • 캐시에 보관하는 index HTable 의 수
  • 이 수를 늘리는 것은 index table 에 쓰기를 시도하는 각각의 시도에 HTable 재생성할 필요가 없게 되는것을 보장함
  • 반대로, 너무 많이 높이면 메모리 압박이 있음
  • Default: 10

8. org.apache.phoenix.regionserver.index.priority.min
  • index 우선 순위 범위의 최소값을 지정
  • Default: 1000

9. org.apache.phoenix.regionserver.index.priority.max
  • index 우선 순위 범위의 최대값을 지정
  • index min/max 내에서의 높은 우선수위는 update 를 먼저 처리하는 것을 의미하지는 않는다
  • Default: 1050

10. org.apache.phoenix.regionserver.index.handler.count

  • Global index 유지하기 위한 index 쓰기 요청을 처리할 때 사용하는 스레드 수
  • 실제 스레드 수는 Max(call 큐의 수, handler count) 의 의해 좌우되더라도, call queue 의 수는 standard HBase 설정에 의해 결정됨c
  • 더 나아가 큐를 튠하면, standard rpc queue 길이 파라미터를 조정할 수 있음 (현재 index queue 를 위한 특별한 방법은 없음)
    • ipc.server.max.callqueue.length
    • ipc.server.callqueue.handler.factor
  • Default: 30


참조 : https://phoenix.apache.org/secondary_indexing.html