[발 번역] HBase Log Splitting

해당 블로그는 KT UCloud의 지원을 받고 있습니다.

해당 글은 http://www.cloudera.com/blog/2012/07/hbase-log-splitting/ 을 발번역한 글입니다. HBase 의 리플리케이션의 핵심이기도 한 실제 WAL 로그가 어떻게 동작하는지에 대한 글입니다.

HBase Log Splitting

HBase Write Path 라는 최근 블로그 글에서, HBase Region 서버 장애시 데이터가 유실되는 것을 방지하는 중요한 역할을 하는 Write-ahead-log(WAL) 에 대해서 얘기하였습니다. 해당 블로그 글에서는 Region 서버 장애 이후에 HBase가 어떻게 데이터 유실을 방지하는지, 특히, Log Splitting를 통한 유실한 업데이트를 어떻게 복구하는지에 대한 중요한 프로세스에 대해서 얘기하도록 하겠습니다.

Log splitting

이전 Write Path 글에서 언급했듯이, HBase 데이터 업데이트는 빠른 쓰기를 위해서 memstore 라고 불리는 메모리에 데이터를 저장해둡니다. Region 서버가 장애가 났을 때, memstore는 디스크에 쓰여지지 않았기 때문에, 모두 유실되게 됩니다. 이런 경우, 데이터 유실을 방지하기 위해서 memstore에 데이터를 저장하기 전에, WAL 파일에 변경 사항을 저장해둡니다. Region 서버가 장애가 나면, memstore의 유실된 데이터는 WAL 파일안의 변경사항(또는 edits 라고 불리는) 을 재현함으로써 재생성 할 수 있습니다.

하나의 Region 서버는 많은 Region들을 서비스 합니다. 하나의 Region 서버안의 모든 Region들은 하나의 동일한 WAL 파일을 공유하게 됩니다. WAL 파일안의 하나의 변경 사항은 각각 어떤 Region에 속하는지 정보를 가지고 있습니다. 그러므로 WAL file의 edit는 특정한 region의 데이터를 재현해서 재생성할 수 있도록 Region 별로 그룹화 될 수 있습니다. Region 별 WAL edit 그룹화는 “Log Splitting” 라고 불립니다. 이것은 Region 서버 장애시 데이터를 복구하는 중요한 프로세스입니다.

Log Splitting는 Region 서버가 셧다운되거나, HMaster가 클러스터를 시작할 때 수행됩니다. 일관성을 보장하기 위해서, 영향을 받는 Region들은 모두 데이터가 복구될 때 까지 모두 이용불가능하게 됩니다. 그러므로 해당 Region 들을 모두 이용가능하게 만들기 전에 모든 WAL edits를 재현해서 데이터를 복구할 필요가 있습니다. 그 결과, 모든 필요한 edit 들이 적용되고, 프로세스가 완료될 때 까지 Region들은 “Log Splitting”에 의해서 이용불가능한 상태가 됩니다.

Log Splitting가 시작되면, 로그 디렉토리 이름은 다음과 같이 변경됩니다.

/hbase/.logs/<host>,
<port>,<startcode>-splitting

예를 들면:

/hbase/.logs/srv.example.com,60020,1254173957298-splitting

폴더 명을 변경하는 것은 중요합니다. Region 서버는 여전히 동작중이지만, 마스터는 해당 Region 서버가 다운되었다고 생각합니다. Region 서버가 바로 응답하지 못하고, 그리고 Zookeeper의 heartbeat 세션을 유지하지 못합니다. HMaster는 이것을 Region 서버가 장애가 난 것으로 인식하기 시작합니다. 폴더 이름이 변경되면,  이미 존재하는 유요한 WAL 파일들이 Active 서버에 의해서 이용되어지지만, 바쁜 Region 서버때문에 실수로 내용이 기록되지는 않습니다.

각 로그 파일은 시간에 의해서 나뉘어집니다. Log Splitting는 한 번에 하나의 edit 를 로그 파일에서 읽고 변경되어야 할 region의 버퍼에 각 edit를 추가합니다. 동시에, 해당 Log Splitting는 여러 개의 쓰기 스레드를 실행시킵니다. 쓰기 스레드는 버퍼에서 edit를 가져와서, 버퍼 안의 edit를 임시로 복구된 edit 파일에 쓰게 됩니다.

해당 파일의 위치와 이름은 다음과 같습니다.


/hbase/
<pre><table_name>/<region_id>/recovered.edits/.temp

<sequenceid> 는 파일에 쓰여진 최초 로그의 첫번째 아이템의 sequence id 를 보여줍니다. 임시로 복구된 edit 파일은 해당 Region을 위한 WAL 파일안에 모든 변경 사항을 기록하고, “Log Splitting”이 완료되면 임시 파일의 이름은 다음과 같이 변경됩니다.

<pre>/hbase/
<table_name>/<region_id>/recovered.edits/<sequenceid>

해당 예제에서 <sequenceid> 는 recoverd edits 파일의 edit 중에서 가장 높은(가장 최신의)  sequence id 값입니다. 그 결과, recoverd edits 파일을 재현할 때, 모든 edit 가 쓰여져 있는지 판별이 가능합니다. HFile 에 쓰여진 마지막 edit 가 파일 이름에 포함된 sequence id 보다 크거나 같으면, 모든 쓰기가 edit 파일에 제대로 쓰여졌다고 확신할 수 있습니다.

log splitting 이 완료되면, 각 Region 서버로 각각의 영향 받은 region 들이 할당되기 시작합니다. region 이 오픈될때 recover.edits 폴더는 recovered.edits 파일들을 체크합니다. 어떤 recovered.edits 파일이라도 존재하면, 해당 파일에서 edit 들을 읽고, mestore에 저장되어 집니다. 결국, 모든 edit 파일들이 재현되면, memstore의 내용들은 디스크(HFile) 로 저장되고, edit 파일들은 삭제됩니다.

싱글 스레드에서 log splitting이 완료되는 시간은 다 다르지만, 여러개의 Region 서버에서 장애가 발생하면 많은 시간이 소모될 수 있습니다. 분산 log splitting은 HBase 0.92(HBASE-1364) 에서 페이스북의    Prakash Khemani 에 의해서 추가되었습니다. 해당 패치는 해당 작업이 완료되는 시간은 극적으로 줄여주고 그로 인해, region들과 table의 가용성을 증가시켜줍니다. 예를 들어, 경험했던 것 중에 하나는, 하나의 클러스터가 장애가 나면, 싱글 스레드 log splitting 에서는 9 시간이 걸리던 작업이 분산 log splitting 에서는 6분 정도 밖에 걸리지 않았습니다.( 역자 주: 음, 장비 수가 많아야 될것 같다라는 생각이 듭니다. 쿨럭…)

Distributed log splitting

HBase 0.90 의 log splitting은 HMaster에 의해서 모든 것이 수행됩니다. 하나의 log splitting이 실행되면, 모든 로그 파일이 순서대로 처리됩니다. 장해 복구 후에 클러스터가 재시작되면, 불행히도, 모든 region 서버는 idle 상태가 되고, 마스터가 log splitting 작업을 끝내기를 기다리게 됩니다. 모든 region 서버가 idle 상태로 남아있는 대신에, log splitting 프로세스 중에 유용하고 도움이 되는 것들을 만들면 안되나? 라는 생각이 분산 log splitting의 시초입니다.

분산 log splitting 에서도, 마스터가 모든 것을 관리합니다. 모든 스캔되고 분할될 log 파일들을 관리하기 위한 split log 매니저를 가지고 있습니다. Split log 매니저는 모든 로그 파일들을 Splitlog zookeeper node(/hbase/splitlog) 에 태스크로 추가합니다. 예를 들어서, zkcli 에서 “ls /hbase/splitlog” 를 수행하면 다음과 같은 결과를 얻을 수 있습니다.

</pre>
[hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2F.logs%2Fhost8.sample.com%2C57020%2C1340474893275-splitting%2Fhost8.sample.com%253A57020.1340474893900, hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2F.logs%2Fhost3.sample.com%2C57020%2C1340474893299-splitting%2Fhost3.sample.com%253A57020.1340474893931, hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2F.logs%2Fhost4.sample.com%2C57020%2C1340474893287-splitting%2Fhost4.sample.com%253A57020.1340474893946]

몇몇 글자가 ASCII 형태로 변경되면 다음과 같습니다.


[hdfs://host2.sample.com:56020/hbase/.logs/host8.sample.com,57020,1340474893275-splitting/host8.sample.com%3A57020.1340474893900, hdfs://host2.sample.com:56020/hbase/.logs/host3.sample.com,57020,1340474893299-splitting/host3.sample.com%3A57020.1340474893931, hdfs://host2.sample.com:56020/hbase/.logs/host4.sample.com,57020,1340474893287-splitting/host4.sample.com%3A57020.1340474893946]

로그 분할 작업의 목록이고, 스캔되고 분할된 WAL 파일명 목록입니다.

Split log 매니저는 모든 태스크를 splitlog znode에 저장합니다. 매니저는 해당 작업들을 모니터링 하고 작업이 모든 작업이 처리되기를 대기합니다.

Split Log Manager

각 Region 서버에서는, Split log worker 라고 불리는 데몬 스레드가 존재합니다. Split log worker는 log를 실제로 분할하는 작업을 합니다. worker는 항상 splitlog zonde를 관찰하고 있다가, 새로운 태스크가 들어오면, Split log worker 는 해당 태스크 경로를 탐색해서, 다른 worker가 아직 처리하지 않은 태스크을 루프를 돌면서 수집합니다. 하나를 수집한 후에, 해당 태스크에 대한 소유권을 요청하고, 성공적으로 소유권을 얻게 되면, 해당 태스크를 처리하게 됩니다. 그리고 분할된 결과에 따라서 해당 태스크 상태 정보를 업데이트 합니다. split worker 가 현재 태스크를 완료하면, 남아있는 것들 중에서 다른 것을 처리하기 위해서 다른 태스크를 수집하려고 동작합니다.

해당 기능은 설정의 hbase.master.distributed.log.splitting 에 의해서 제어됩니다. 기본적으로, 사용함으로 설정되어 있고( CDH3u3는 0.90을 기반으로 패치되었습니다만, 해당 기능은 CDH3u3에서 기본적으로 사용안함으로 되어 있습니다. 사용하길 원한다면, hbase.master.distributed.log.splitting  설정을 true로 바꾸길 바랍니다.) HMaster가 시작될 때, Split log 매니저 인스턴스는 해당 파라매터가 명시적으로 false 가 아니면 생성됩니다. Split log 매니저는 모니터링 스레드를 생성하고, 모니터링 스레드는 정기적으로 다음과 같은 작업을 수행합니다.

  1.  큐에 이미 종료된 split log worker가 있는지 체크하고, 만약 존재하면, 이미 종료된 split log worker 에게 소유된 태스크를 다시 추가합니다. 만약 zookeeper 예외로 인해서 추가되지 않으면, 재시도를 위해서 다시 종료된 worker를 큐에 추가합니다.
  2.  아직 할당되지 않은 태스크가 있는지 체크하고, 만약 존재하면, 임시적인 nodeChildrenChanged  Zookeeper 이벤트를 통해서 다른 split log worker 가 미할당된 태스크를 재탐색 할 수 있도록, 임시 재스캔 노드를 생성합니다.
  3. 할당된 태스크들이 작업시간을 초과했는지 확입합니다. 작업시간을 초과한 태스크가 있으면, 해당 태스크가 재시도 될 수 있도록 TASK_UNASSIGNED 로 변경합니다. 해당 태스크들은 느린 worker에게 할당되어 있고 log splitting 작업의 멱등성(역자 주: 여러 번 반복되더라도 값이 동일한 연산) 덕분에 재시도 되더라도 괜찮습니다. 이것이 여러개의 log splitting 작업이 동시에 실행되어도 어떤 문제도 없는 이유입니다.

Split log 매니저는 HBase split log znode를 항상 모니터링 하고 있고, 어떤 split log 태스크 znode의 데이터가 변경되면, 해당 노드의 데이터를 검색합니다. 해당 노드의 데이터는 해당 태스크의 현재 상태 정보를 가지고 있습니다. 예를 들어, zkcli 에서 다음과 같은 명령을 실행시키면


get /hbase/splitlog/hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2F.logs%2Fhost6.sample.com%2C57020%2C1340474893287-splitting%2Fhost6.sample.com%253A57020.1340474893945

다음과 같은 결과를 얻게 됩니다.

</pre>
<em><strong>unassigned</strong> host2.sample.com:57000</em>
<pre> <em>cZxid = 0×7115</em>
 <em>ctime = Sat Jun 23 11:13:40 PDT 2012</em>
 <em>mZxid = 0×7115</em>
 <em>mtime = Sat Jun 23 11:13:40 PDT 2012</em>
 <em>pZxid = 0×7115</em>
 <em>cversion = 0</em>
 <em>dataVersion = 0</em>
 <em>aclVersion = 0</em>
 <em>ephemeralOwner = 0×0</em>
 <em>dataLength = 33</em>
 <em>numChildren = 0</em>

해당 태스크가 아직 미할당 되었다는 것을 보여주고 있습니다.
데이터가 변경된 태스크의 상태에 따라서, Split Log 매니저는 다음 중에 하나의 동작을 하게 됩니다.

  1. 미할당된 태스크를 다시 제출합니다.
  2. 할당된 태스크가 정상적으로 동작중인지 체크합니다.
  3. 중간에 취소된 태스크를 다시 제출하거나 실패로 처리합니다.
  4. 에러가 발생했지만 완료된 태스크를 다시 제출하거나 실패로 처리합니다.
  5. 에러로 인해서 실패한 태스크를 다시 제출하거나 실패로 처리합니다.
  6. 성공적으로 완료되었거나 실패한 태스크를을 삭제합니다.

다음과 같은 경우에 태스크는 실패하게 됩니다.

  1. 태스크가 삭제 된 경우
  2. 해당 노드가 더 이상 존재하지 않는 경우
  3. 해당 태스크의 상태를 TASK_UNASSIGNED 로 바꾸는 데 실패한 경우
  4. 다시 제출되는 수가 임계치보다 큰 경우

Split log worker는 region 서버에 의해서 만들어지고 시작되어집니다. 그래서 각 Region 서버마다 Split log worker가 존재합니다. Split log worker 가 시작되었을 때, 스스로 HBase znode에 자기 자신을 등록하게 됩니다.

어떤 splitlog znode 의 자식노드들이 변경되고, worker thread가 잠들어 있다면, 다른 태스크들을 찾기 위해서  worker 스레드를 깨우기 위해, worker thread로 해당 변경에 대해 통지합니다. 현재 태스크의 노드 데이터가 변경되면, 해당 태스크가 다른 worker에 의해서 소유되었는지 체크합니다. 만약 그렇다면, 해당 worker 스레드는 중단되고 현재 태스크는 중단되어집니다.

If any splitlog znode children change, it notifies the worker thread to wake up to grab more tasks if it is sleeping. If current task’s node data is changed, it checks if the task is taken by another worker. If so, interrupt the worker thread and stop the current task.

Split log worker 스레드는 splitlog znode의 어떤 자식 노드라도 변경되면 해당 노드에 대해서 체크하게 됩니다.

각 태스크를 위해서 다음과 같은 일 들을 수행 하게 됩니다.

  1. 태스크 상태를 읽고, TASK_UNASSIGNED 상태가 아니라면 아무런 작업도 하지 않습니다.
  2. 해당 태스크가 TASK_UNASSIGNED  상태라면, woker에 의해서 TASK_OWNED로 변경을 시도합니다. 해당 작업이 실패하며, 다른 worker가 다시 해당 태스크를 처리할려고 시도할 것입니다. Split log 매니저는 해당 태스크가 미할당으로 남아있으면, 추후에 모든 worker에게 재스캔하라고 요청할 것입니다.
  3.  worker 가 해당 태스크를 소유하게 되면, 정말로 그것을 소유했는지 확인하기 위해서 태스크의 상태를 다시 한번 비동기적으로 확인하게 됩니다. 그 동안에, 실제 작업을 처리하기 위해서 Split task executor를 실행시킵니다.
  4. HBase root 폴더를 찾아서, 루트 밑에 임시 폴더를 생성합니다. 그리고 분할된 로그 파일을 해당 임시 폴더로 옮깁니다.
  5. 모든 것이 정상적으로 완료되면, task executor는 해당 태스크의 상태를 TASK_DONE으로 변경합니다.
  6. 예상치 못한 IOException 이 발생하면, 해당 태스크의 상태는 TASK_ERR로 셋팅됩니다.
  7. 해당 작업이 종료되어 버리면, 해당 태스크의 상태는 TASK_RESIGNED로 설정됩니다.
  8. 태스크가 다른 worker에 의해서 소유되어 있다면  단지 해당 정보를 로깅해둡니다.

Split Log Worker

Split log 매니저는 모든 태스크가 성공적으로 완료 되었을 때, 리턴합니다. 만약 모든 태스크가 완료될 때 몇몇 에러가 있다면, log splitting 작업을 재시도 하기 위해서 예외를 발생시킵니다. 비동기적인 구현 때문에, 매우 흔치 않은 경우에, Split log 매니저는 몇몇 완료된 태스크를 잃어버릴 수 있습니다. 그래서 정기적으로 아직 완료되지 않은 태스크가 taskmap 이나 zookeeper 에 남아있는지 체크합니다. 만약 아무것도 없다면, 무슨 일이 발생하기를 계속 기다리며 대기하기보다는 log splitter 가 바로바로 재시작 할 수 있도록 예외를 발생시킵니다.

Conclusion

해당 글에서, 장애 복구시 잃어버린 데이터 복구를 위한 Log Splitting라는 중요한 프로세스에 대해서 이야기 하고 있습니다. Log Splitting는 HMaster에 의해서 연속적으로 수행되고, 0.92 에서 분산 Log Splitting의 발전에 대해서 소개되었스니다. 실제 작업은 Region 서버에서병렬적으로 수행되고, 클러스터에 매우 많은 리전 서버가 존재하므로, 클러스터에 있는 리전 서버들의 분산 Log Splitting는 로그를 나누는 시간 줄이기, Region가용성 향상등의 장점이 잇습니다.