[발 번역] Apache HBase Internals: Locking and Multiversion Concurrency Control

해당 글은 http://blog.cloudera.com/blog/2013/01/apache-hbase-internals-locking-and-multiversion-concurrency-control/ 라는 글을 발 번역한 것입니다. 오역에 주의하시길 바랍니다.

Apache HBase Internals: Locking and Multiversion Concurrency Control

[노트] 해당 글은 Apache HBase 가 어떻게 concurrency control 을 하는지 설명하고 있습니다. 이 글은 HBase Write Path에 대해서 알고 있다고 가정하고, 추가적인 부분은 다음 글을 읽기를 바랍니다.

Introduction

Apache HBase 는 Consistent 하고 이해할 수 있는 데이터 모델을 사용자에게 제공하면서도 높은 성능을 냅니다. 이 글에서, HBase 데이터 모델의 보장성에 대해서 얘기하고, 전통적인 데이터베이스와 어떻게 다른지 설명합니다. 그리고, concurrent 한 쓰기에 대해서 공부함으로 써, concurrency control 의 필요에 대해서 이야기 하고, 간단한 concurrency control 방법을 소개합니다. 마지막으로,  읽기/쓰기 concurrency control에 대해서 공부하고 MVCC라고 불리는 효과적인 방법에 대해서 이야기할 것입니다.

Why Concurrency Control?

HBase의 concurrency control을 이해하기 위해서, 먼저 HBase에서 왜 concurrency control 이 필요한지 이해해야 합니다. 어떤 속성들로인해 concurrency control 이 필요한 데이터에 대해서 HBase에서 보장해 주는지에 대해서 살펴보겠습니다.

이에 대한 대답은 HBase는 Row 단위의 ACID semantics 를 보장합니다. ACID는 다음 특성들의 첫글자를 딴 것입니다.

  • Atomicity: 모든 트랜잭션은 완료되거나 실패되어야 한다.
  • Consistency: 올바른 데이터만 저장되어야 한다.
  • Isolation: 동시에 발생하는 트랜잭션이 서로에게 영향이 없어야 한다.
  • Durability: 트랜잭션이 커밋되면, 해당 결과가 유지되어야 한다.

전통적인 관계형 데이터베이스에 경험이 있다면, 이 단어들이 친숙할 것입니다. 전통적인 관계형 데이터베이스는 일반적으로 데이터베이스의 모든 데이터에 ACID semantics를 제공합니다. 성능상의 이유로, HBase는 오직 ACID semantics를 Row단위로만 제공합니다. 이 단어들이 익숙하지 않더라도, 걱정하지 마십시오. 정확한 정의에 대해서 살펴보는 대신에, 간단한 예들을 보도록 하겠습니다.

Writes and Write-Write Synchronization

HBase 의 {company, role} 에 동시에 두개의 쓰기가 발생하다고 가정합니다.



Image 1.  Two writes to the same row

이전에 올렸던 HBase Write Path 에서, 각 쓰기에 대해서 다음과 같은 작업을 수행한다는 것을 알고 있습니다.

(1) Write-Ahead-Log (WAL) 쓰기

(2) MemStore 업데이트: (row, column) 쌍의 각 data cell 을 memstore 에 쓴다.

List 1. Simple list of write steps

재해 복구의 목적으로 WAL을 작성하고, 데이터를 메모리에(MemStore)에 저장합니다.

이제, 다음과 같은 순서로 이벤트가 발생한고, concurrency control 이 없다고 가정하자.


Image 2.  One possible order of events for two writes

결과적으로 다음과 같은 결과를 얻게 될 것이다.

Image 3.  Inconsistent result in absence of write-write synchronization

결코 용납할 수 없는 일입니다. ACID 에서 말하는, 쓰기에 대한 Isolation을 제공하지 않았기 때문에, 두 개의 쓰기가 섞여 버렸습니다.

이래서 확실히 concurrency control 이 필요합니다. 간단한 방법은 배타 락을 Row마다 제공해서 같은 Row를 업데이트 해야할 경우, 쓰기가 독립적으로 일어나도록 합니다.(파란색 안에 새로운 단계가 있습니다.)

(0) Row Lock 획득
(1) Write-Ahead-Log (WAL) 작성
(2) MemStore 업데이트: memstore 에 각 셀의 내용을 저장
(3) Row Lock 반환

List 2: List of write-steps with write-write synchronization

Read-Write Synchronization

이제, ACID semantics를 보장하기 위해서 쓰기에 row lock을 추가합니다. 읽기를 위해서도 concurrency control이 필요할까요? 예를 들어, 다음과 같은 순서로 이벤트가 일어난다고 가정합니다.( List 2의 규칙을 따릅니다.)


Image 4.  One possible order of operations for two writes and a read

읽기에 대해서는  Concurrency control 이 없고, 두 개의 쓰기가 발생하는 중에 read를 동시에 요청했다고 하고, 그리고 read 가 “Waitor”가 MemStore에 저장되기 직전에 바로 실행되었다고 가정합니다. 이 읽기는 그림에서 붉은 줄로 표시되어 있습니다. 이 경우에, read에는 row 데이터의 불일치가 발생하게 됩니다.



Image 5.  Inconsistent result in absence of read-write synchronization

그러므로 읽기-쓰기 동기화를 위해서도 concurrency control이 필요합니다. 간단한 방법은 쓰기와 같은 방법으로 읽기 시에도 락을 획득하는 방법입니다. 이 방법은 ACID 위반은 해결할 수 있지만, row lock으로 인해서 읽기와 쓰기에 성능저하가 일어나게 됩니다.

대신, HBase 에서는 row lock을 읽기를 위해서 획득하는 대신에 Multiversion Concurrency Control (MVCC) 라는 방법을 이용합니다. HBase 에서 MVCC는 다음과 같이 동작합니다.

쓰기:

(w1) RowLock 획득 뒤에, 각 쓰기 연사은 즉시 쓰기 번호를 할당 받는다.
(w2) 각 데이터 셀은 쓰기 번호를 저장한다.
(w3) 각 쓰기 연산은 쓰기 번호를 저장하면서 완료한다.

읽기:

(r1) 각 읽기 연산은 읽기 시간을 할당 받고, 이를 read point라고 한다.
(r2) 각 read point는  쓰기 번호 중에 가장 높은 값(완료된 값 중에서) 을 할당 받는다.
(r3)각 읽기 r은 r 의 read point 와 같거나 작은 것 중에서 쓰기 번호가 가장 높은 것을 가지는 (row, column)과 일치하는 데이터 셀에서 (row, column) 의 조합을 가져온다.


List 3. Multiversion Concurrency Control steps

그림 4의 동작을 다시 살펴보자, 여기서 MVCC 를 사용하고 있다.


Image 6.  Write steps with Multiversion Concurrency Control

MVCC 때문에 새로운 단계가 추가되었다. 각 쓰기는 쓰기 번호를 할당받고(w1), 각 데이터 셀은 memstore에 자신의 쓰기 번호와 함께 저장한다.(w2, 예를 들어 “Cloudera [wn=1]”) , 그리고 쓰기 번호를 저장하면서 각각의 쓰기는 완료된다.(w3)

이제 그림 4에서의 읽기에 대해서 생각해 보자. 예를 들어 “Restarant[wn=2]” 이 후, “Waiter[wn=2]” 단계 이전에 읽기가 발생한다면, r1 과 r2의 규칙에 따라서, read point는 1이 할당된다. r3로 인해서 읽기에서는 쓰기 번호가 1인 것의 값을 읽게 되고, 다음과 같은 결과를 얻게 된다.

Image 7.  Consistent answer with Multiversion Concurrency Control

읽기에 대한 락 없이 필요없이 일관성있는 응답을 얻게되었다.

MVCC의 쓰기 과정을 정리하면(붉은색 안의 읽기-쓰기 동기화를 획득하는 새로운 단계를 포함해서)

(0)  Row Lock 획득
(0a) 쓰기 번호 획득
(1) Write-Ahead-Log (WAL) 쓰기
(2) MemStore 업데이트 : memstore 에 각 셀의 데이터 저장
(2a) Write Number 저장
(3) Row Lock 반환

Conclusion

이 글에서는 처음에는 HBase의 row-level ACID의 보장성에 대해서 얘기했고, 동시 쓰기에 대해서 배우므로써 concurrency control의 필요성에 대해서 이야기한 다음, row-level  락 솔루션을 소개했다. 마지막으로 읽기-쓰기 concurrency control 에 대해서 살펴보고 MVCC라고 불리는 효과적인 방법을 알아보았다.

이 블로그는 HBase 0.92에 맞춰져 있고 HBase 0.94에서는 좀 더 다양한 최적화가 이루어졌다. HBASE-5541 는 차후에 다시 다루도록 하겠다.

Gregory Chanan은 클라우데라의 소프트웨어 엔지니어이고 HBase committer이다.