View

Secondary Indexing

 Secondary index 는 기존에 HBase에서 설정된 primary row key가 아닌, 별도의 primary 접근 경로를 추가해 통해 데이터에 직접 접근하는 방식이다. (HBase에서는 primary row key를 사전적순서로 정렬된 하나의 Index만 가지고 있다.) 

 primary row가 아닌 다른 방식으로 데이터를 접근하기 위해서는 설정한 filter 값과 상관 없이 내부적으로 full-scan(테이블의 모든 rows에 접근)이 이루어져야 한다. 하지만 secondary index를 이용하면 미리 설정한 column 이나 expression 을 통해서 row key 에 접근할 수 있고 full-scan이 아닌 range-scan 이나 point-lookup이 가능해 진다. 



Indexing Type (Global Index / Local Index) 

 Phoenix 는 2 종류의 인덱싱 기술을 지원하는데, global indexing과 local indexing 이다. 각각은 다른 시나리오에 유용하고 각각의 장단점을 가지고 있다. 


Global Index 

  Primary data table을 기반으로 다른 정렬 순서를 가진 Index를 생성한다. Index 마다 서로 다른 HBase table을 별도로 생성하기 때문에, 내부적으로 phoenix가 어떤 index를 사용할 것인지 선택한 뒤에는 실제 primary table 처럼 동작한다. 


 

 데이터 중복은 통한 인덱싱이기 때문에  Global indexing은 read-heavy인 경우에 성능이 좋다. 읽기 요청이 발생되는 경우 phoenix는 가장 빠른 결과가 나올 것으로 예상되는 index table을 선택하여 HBase 테이블을 직접 읽는다. 기본적으로 hint 가 주어지는 것과 상관 없이 index는 자신의 부분에 포함되어 있지 않다면 해당 칼럼은 사용하지 않는다. 

 반대로 global index를 사용하면 write작업(DETELE, UPSERT VALUES, UPSERT SELECT)을 위한 오버헤드가 크다. global index는 write 와 관련된 데이터 테이블 업데이트를 가로체서 해당 데이터와 관련된 모든 index 테이블에 대해 업데이트를 수행하게 된다. 


 

 Local Indexes

 Local index는 인덱스 데이터와 테이블과 동일한 하나의 테이블로 저장되며, 각 데이터는 같은 hbase region server에 저장되어 write 작업 시 network 오버헤드를 줄여준다. local index는쿼리가 모두 만족covered 하지 않더라도 사용될 수 있다. 

 Phoenix 4.8.0 이전 버전에서는 테이블의 모든 local index가 각각의 샤드를 구성하는 분리된 table로 저장되었다. 4.8.0 버전 이후부터 하나의 테이블에 별도의 shadow column family(region)로 저장이 된다. 

 Local Indexing은 write-heavy하고 사용공간에 제약이 있는 경우에 사용된다. global index와 유사하게 쿼리를 수행하는 경우 phoenix가 자동으로  local index를 선택하여 쿼리를 수행하게 된다. phoenix가 자동으로 index에 포함되어 있지 않은 데이터를 data table로 부터 가져온다. 

 읽기 요청을 수행하는 경우 모든 region은 반드시 data 검사를 통해 index 데이터가 존재하는지 확인해야 한다. 그렇기 때문에 read-time에 오버헤드가 발생한다. 




Phoenix Indexing Feature 

Covered Indexes 

 Phoenix 는 covered index라는 강력한 기능을 가지고 있는데, 이것을 통해서 index의 값을 확인하기 위해 primary table에 접근하여 데이터를 읽어올 필요가 없다. 대신에 우리가 찾는 index rows에서 데이터를 바로 가져올 수 있어 읽기 오버헤드를 줄어준다. 

 예를들어 my_table의v1, v2에 대해서 index를 생성하고 v3 칼럼을 index에 포함하면 v3 칼럼에 대해서 data table로부터 데이터를 가져오지 않아도 된다. 

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


Functional Indexes 

 Functional index는 인덱스를 단순히 column 기반으로 생성하는 것이 아닌 임의의 표현식expression으로도 구성할 수 있게 해준다. 쿼리가 이 표현식을 사용하면 인덱스가 해당 결과를 이용하여 데이터 테이블 대신 사용한다. 

 예를들어 아래와 같이 인덱스를 생성하는 경우, 별도로 firstname과 lastname의 조합에 대한 인덱스를 신경쓸 필요가 없다. 

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

SELECT EMP_ID FROM EMP WHERE UPPER(FIRST_NAME||' '||LAST_NAME)='JOHN DOE'


Index Population 

 기본적으로 index가 생성될 때, CREATE INDEX 명령을 수행하는 동안 동기적으로 위치를 정하게 된다. 이것은 data table의 현재크기를 고려하지 않고 이루어지는데 phoenix 4.5 버전부터 ASYNC 키워드가 주어졌을 때 비동기적으로 인덱스의 규모를 파악하게 된다. 

CREATE INDEX async_index ON my_schema.my_table (v) ASYNC

 인덱스 테이블의 크기를 확인 한 후 맵리듀스 작업이 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


 맵리듀스 작업이 끝난 후 인덱스가 활성화 되고 쿼리수행에 활용될 수 있다. 작업 종료 후에 클라이언트를 종료시킨다.  결과 경로 옵션은 HDFS 디렉토리를 명시하는 것으로 Hfile을 쓸 경로를 나타낸다. 



Index Usage 

 인덱스들은 자동으로 phoenix에 의해서 사용했을때 효과적이라고 판단되면 쿼리 수행에 사용된다. 그러나 앞서 말했듯 global index는 쿼리 안의 모든 column이 index에 포함이 되지 않았다면 global index를 사용되지 않는다. 

 예를 들어 다음 쿼리는 index를 사용하지 않는데 칼럼 v2는 인덱스에 포함되어 있지 않기 때문이다. 

SELECT v2 FROM my_table WHERE v1 = 'foo'

이 경우 인덱스를 사용하기 위한 3가지 방법이 존재한다. 


1) 새로운 global index 생성 

 v2를 포함하는 새로운 인덱스를 생성한다. 이렇게 만들어준다면 v2 칼럼 값이 인덱스로 복사되고 그 값의 변경에 따라 동기화가 이루어질 것이다. 이러한 작업은 index의 크기를 증가시키게 된다. 

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


2) Hint 사용 

 강제로 쿼리에 hint를 사용하여 특정 index를 타게한다. 이 경우 인덱스를 탐색하면서 v2값을 확인하기 위해 각 데이터 row값을 가져오게 된다. hint는 어떠한 index를 선택해야 하는지 확신이 있는 경우에만 사용해야 한다. 그렇지 않으면 성능상 full-scan과 별반 다를 것이 없게 된다. 

SELECT /*+ INDEX(my_table my_index) */ v2 FROM my_table WHERE v1 = 'foo'


3) local index 생성 

 Local index는 쿼리에서 사용하는 칼럼들이 모두 인덱스 안에 존재하지 않더라도 index를 사용할 수 있다. 이러한 작업은 local 인덱스에서 기본적으로 수행되는데 이는 테이블 데이터와 인덱스 데이터가 같은 region server에 있어서 local 레벨에서 탐색이 이루어지기 때문이다. 

CREATE LOCAL INDEX my_index ON my_table (v1)


Index Removal 

 인덱스를 삭제하기 위해서 다음 구문을 수행한다. 만약 index에서 사용하는 column이 data table에서 삭제되었으면 index는 자동으로 삭제된다. 추가적으로 만약 유관된 칼럼이 데이터 테이블에서 삭제되었다면, 그 인덱스 또한 자동으로 삭제된다. 

 DROP INDEX my_index ON my_table


Index Properties 

  CREATE TABLE 구문과 같이 CREATE INDEX 구문도 HBase 테이블의 기초가되는 property 들을 전달할 수 있다. 만약 primary 테이블이 salted 상태라면, global index와 같은 방식으로 자동으로 salted 인덱스가 생성된다. 

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

 추가적으로 MAX_FILE 을 인덱스에 적용한다면,  index 테이블에 대한 상대적인 primary 크기가 설정될 것이다. local index의 경우 SALT_BUCKETS 설정이 허용되지 않는다. ( salting에 대한 글은 추후 추가할 예정이며, 그 전까지는 apache 문서를 확인 하세요! https://phoenix.apache.org/salted.html )



Consistency Guarantees 

  커밋 후에 성공적인 결과가 client에게 리턴되었을때, 모든 데이터는 primary table과 유관 index에 모두 쓰여졌다고 보장할 수 있다.  즉, 인덱스 업데이트는 동기적으로 이루어지며 HBase로 부터 강력한 일관성을 보장받는 것이다. 하지만 인덱스가 데이터 테이블과 다른 테이블에 저장되기 때문에, 테이블의 속성과 인덱스 타입에 따라서, 

 테이블과 인덱스 사이의 일관성은 서버쪽 충돌로 인해 다양한 커밋 실패를 야기할 수 있다. 이것은 유스케이스와 사용자 요구사항에 따른 매우 중요한 디자인 고려사항이다. 

 아래에 일관성 보장 레벨을 위한 다양한 옵션들이 주어져있다. 

 

Transactional Tables 

   Transactional 테이블을 선언함으로써, 데이터 테이블과 인덱스 테이블 사이에서 높은 레벨의 일관consistency을 보장받을 수 있다. 이 경우 테이블에 대한 테이블 변경으로 인한 커밋작업과 인덱스와 관련된 update 작업은 원자적atomic으로 이루어 지며 ACID를 강하게 보장한다. 

 만약 커밋작업이 실패했다면 인덱스 테이블과 데이터 테이블의 어떠한 데이터도 변경되지 않을 것이고 데이터 테이블과 인덱스 테이블은 항상 동기화된 상태로 유지될 것이다. 

  그렇다면 왜 항상 테이블을 transactional 로 선언하지 않는 것일까? 그렇게 하는것이 좋을 수도 있다. 특히 테이블의 선언이 변경불가능immutable 하다면 transactional 오버헤드가 매우 작기 때문에 이러한 경우는 좋다. 

 하지만 데이터가 변경가능mutable 하다면 transaction 테이블에서 transaction manager 수행으로 인한 운영operation 오버헤드와 conflict detection과 관계된 오버헤드가 발생할 것이다. 

 추가적으로 transactional 테이블 중 세컨더리 인덱스를 가지고 있는 것은 write 작업에 대해서 낮은 성능을 가지고 있기 때문에, 데이터 테이블과 세컨더리 테이블은 반드시 이용가능해야 하며, 그렇지 않는 경우 write 작업은 실패할 것이다. 

 

Immutable Tables 

 오직 한번만 쓰여지고 절대 update가 발생하지 않는 테이블을 위한 최적화 방법을 적용할 수 있는데,  이는 점진적으로 관리하며 write-overhead를 줄이기 위한 것이다.

 이것을 일반적으로  time-series(시계열) 데이터에 일반적인데 log/event 데이터와 같이 row가 한번 쓰여지고 절대 변경되지 않을때 사용된다. 이 최적화 방식의 장점을 활용하기 위해, 테이블 선언 시 DDL 구문에 IMMUTABLE_ROWS=true 옵션을 추가해야 한다. (default : mutable) 

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

 Global immutable index의 경우 인덱스는 전체적으로 index 테이블이 데이터 테이블 변화 발생과 같은 관리가 전부 clinet-side에서 이루어 진다. 반면에 local immutable index의 경우 server-side에서 관리가 된다. 

 Immutable로 선언된 테이블이 실제로 데이터를 변경하지 못하게 하는 안전장치가 있는 것은 아니다. 만약 데이터가 수정된다고 한다면 테이블과 인덱스가 더이상 동기화가 맞지 않게 될 것이다. 

 만약 이미 존재하는 테이블에 대해서 immutable 인덱싱에서 mutable 인덱싱으로 변경하고 싶으면 아래와 같이 ALTER TABLE명령어를 사용하면 된다. 

ALTER TABLE my_table SET IMMUTABLE_ROWS=false


 Non-transactional immutable 테이블에 대한 인덱스는 자동으로 commit 실패에 대한 처리를 하지 않는다. 테이블과 인덱스의 일관성을 유지하기 위해서는 client가 처리하는것으로 남겨두어야 한다. update로 인해 데이터가 변경되지 않기 때문에, 가장 단순한 해결방법은 성공할 때 까지 계속해서 mutation-batch를 반복하는 것이다. 



Mutable Tables 

 Non-tranactional mutable 테이블에서 인덱스 업데이트 지속성durability을 위해, WAL(Write-Ahead-Log) entry에 primary 테이블 row를 추가한다. WAL entry가 디스크와 성공적으로 동기화 된 뒤에 인덱스와/primary 테이블을 업데이트 수행하게 된다. 

 기본적으로 index update를 병렬적으로 수행하여 높은 처리량throughput 으로 수행된다. 만약 서버에서 인덱스 업데이트를 쓰는 도중에 충돌이 발생한 경우, WAL recovery 프로세스를 이용하여 인덱스 테이블을 다시 update 할 수 있다. 결과적으로 non-transactional mutable 테이블은 primary 테이블 뒤에서 오직 한번의 배치 처리만 수행한다

 Mutable table과 관련된 몇가지 중요한 사항이 있다. 

- Non transactional table에서 primary table과 동기화가 맞지 않는 인덱스 테이블을 볼 수 있다. 
- 위에서 언급했다시피, 짧은 기간동안 동기화가 맞지 않는 것은 큰 문제가 되지 않는다.
- 각 데이터 항목과 그것의 인덱스 항목은 쓰여지도록 보장되거나 손실되도록 보장되어, 부분적으로 업데이트 되지 않는다. (consistency)
- WAL 로그가 disabled 되어 있지 않다면 index 테이블 이후에 data 가 테이블에 쓰여진다. 


 Mutable Table Singular Write path 

 Mutable Table에는 하나의 쓰기작업을 위한 경로가 존재하는데 이것은 실패 properties를 보장한다. 모든 쓰기작업에 HBase 이루어지는 것들은 phoenix coporcessor를 통해 중간에 인터셉트 된다. 

 그리고 업데이트가 지연되어(async) 인덱스를 구성한다. 이러한 업데이트들은 WAL로그 항목으로 쓰여진다. 만약 이러한 작업에 실패가 발생하면 client에게 실패결과를 리턴하고 client에서 변경된 데이터를 볼 수 없게 된다. 일단 WAL entry가 쓰여지고  실패가 발생했다면,  인덱스와 primary 테이블이 update가 수행되고 비동기적으로 데이터가 보여질 것이다. 

 만약 서버에서 에러가 발생했다고 한다면, 우리는 WAL 재수행 메커니즘을 통해서 업데이트를 다시 수행할 것이다.  만약 서버 에러가 아니라면, 각각의 테이블에 인덱스 업데이트를 수행할 것이다. 만약 이 인덱스 업데이트가 실패한다면 다양한 방법으로 아래 일관성을 보장할 것이다. 

 만약 phoenix 시스템 카탈로그 테이블이 오류에 의해 접근할 수 없다면 poenix는 강제로 서버를 긴급하게 중시하고 JVM 을 빠져나오도록 하여 server가 죽도록 할 것이다. server를 죽임으로써 WAL이 다시 수행되어 복구recovery될 것이고 적절한 테이블에 인덱스를 업데이트 할 것이다. 이것은 세컨더리 인덱스가 보여지지만 유효하지 않은 상태일때 계속진행하지 않는 것을 보장한다. 


Disallow table writes until mutable index is consistent

 Non transactional 테이블과 인덱스의 가장 높은 레벨의 일관성 유지 방법으로 데이터 테이블에 대한 쓰기작업이 인덱스 업데이트 작업 실패 시 허용되지 않는다. 이 일관성 모드에서는 테이블과 인덱스가 실패가 발생하기 전의 타임스템프를 가지고 있는데, 인덱스가 다시 온라인 상태가되어 데이터 테이블과 싱크가 맞을 때 까지 데이터 테이블의 쓰기작업은  허용되지 않는다. 해당 인덱스는 액티브 상태로 될 것이고 평소와 같이 쿼리에 사용될 것이다. 

다음의 server-side 설정들이 이 동작을 제어하게 된다. 

phoenix.index.failure.block.write

- 반드시 true로 설정되어 있어야 커밋작업이 실패했을대 데이터 테이블에 쓰기작업을 하지 않는다.

 phoenix.index.failure.handling.rebuild

- 커밋이 실패했을 때 백그라운드에서 인덱스를 리빌드 하기 위해서 반드시 true로 설정해야 한다.(default) 


Disable mutable indexes on write failure until consistency restored

 Mutable 인덱스의 기본동작으로 커밋 수행시 쓰기작업에 실패하는 경우 인덱스가 사용불가하도록 체크한다. 부분적으로 백그라운드에서 리빌드 작업이 이루어지며, 일단 일관성consistency가 복구되면 다시 활성화 된다. 

 이러한 consistency mode에서 데이터 테이블에 대한 쓰기작업은 세컨더리 인덱스가 다시 빌드되는 동안 중지되지 않는다. 하지만 세컨더리 인덱스가 리빌드 되는 동안은 쿼리에 사용되지 않는다. 

다음 server-side 설정들이 이 동작들을 제어한다. 

 phoenix.index.failure.handling.rebuild

-반드시 true로 설정하여 커밋 실패 발생 시 인덱스가 리빌드 되도록 해야 한다. 

 phoenix.index.failure.handling.rebuild.interval

 mutable 인덱스가 부분적으로 리빌드되어야 하는지 점검하는 주기를 밀리세컨드로 정의한다. 기본 값은 10000 milisecond (10s) 이다. 

 phoenix.index.failure.handling.rebuild.overlap.time 실패가 발생되었을때 저장된 타임스탬프 중 얼마나 뒤로 돌아갈 것인지 설정한다. 기본 값은 1 이다. 



Disable mutable index on write failure with manual rebuild required

이 방법은 가장 낮은 레벨의 mutable 인덱스를 위한 일관성consistency 이다. 이 경우 세컨더리 인덱스가 쓰기작업을 실패하는 경우 인덱스는 disabled  상태가 되고 수동으로 인덱스를 리빌드 해주어야 한다. 

다음 server-side 설정이 동작을 제어한다. 

phoenix.index.failure.handling.rebuild

 반드시 false로 설정되어, mutable 인덱스가 백드라운드에서 리빌드 되는 것을 막는다. 




Share Link
reply
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31