CAP Twelve Years Later: How the “Rules” Have Changed

지난 2012년 2월, IEEE Computer에 CAP Theorem에 대한 특집이 실렸습니다. 특집의 첫번째 아티클을 쓴 저자는 지난 2000년 PODC (Symposium on Principles of Distributed Computing)에서 CAP Theorem을 conjecture의 형태로 발표했던 바로 Eric Brewer입니다.

CAP Twelve Years Later: How the “Rules” Have Changed by Eric Brewer

1. CAP Theorem

소위 NoSQL 운동에 대해서 조금이나마 관심이 있었던 분들은 CAP Theorem에 대해 들은 바가 있을 것입니다.

CAP Theorem은 네트워크를 통해 데이터를 공유하는 시스템은 아래의 3가지 특성 중 2개만을 가질 수 있다고 얘기하고 있습니다.

  • Consistency (C)
  • High Availability (A)
  • Tolerance to Network Partitions (P)

네트워크로 연결된 2개의 노드로 구성된 시스템이 있다고 가정해봅시다. 그리고 네트워크 파티션에 의해 2개의 노드는 통신할 수 없는 상황이 벌어졌다고 생각해봅시다.

Availability를 위해 하나 이상의 노드가 상태를 업데이트할 수 있도록 한다면 2개의 노드가 가지고 있는 데이터는 inconsistent해지므로 Consistency를 포기하는 것이 됩니다. Consistency를 보존하려고 한다면, 2개의 노드 중 한 쪽은 unavailable한 것처럼 행동해야 하므로, Availability를 포기하는 것이 됩니다. 노드들이 서로 통신할 때만, Consistency와 Availability를 동시에 보존할 수 있으므로 이는 Partition Tolerance를 포기하는 것이 됩니다.

2. Why 2 of 3 is misleading

“Partition Tolerence를 포기할 수 없으므로, Consistency와 Availability 중 하나를 선택해야 하고, Availability를 희생할 수 없으므로, Consistency를 희생해야한다” 정도가 NoSQL 운동의 초기에 등장했던 AP 시스템들의 공통된 주장이었습니다.

그러나, Eric Brewer는 이런 식으로 3개의 특성 중 2개만을 선택해야 한다는 관점이 많은 오해를 불러왔다고 얘기하고 있습니다.

그 이유는 다음과 같이 설명하고 있습니다.

  • 파티션은 흔히 일어나지 않으므로, 파티션이 일어난 상황이 아닐 때에도 Consistency나 Availability를 포기할 타당성은 적다.
  • Consistency-Availability 사이의 선택은 하나의 시스템에서 단 한번 이루어지는 것이 아니라, 세부적인 단위로에서 여러번 일어날 수 있다.
  • 3개의 특성들은 연속적이다. Availability는 0% – 100%, 여러 레벨의 Consistency, Partition에 대한 시스템 내의 Disagreement 등이 이를 나타낸다.

3. Cap-Latency connection

또한 Eric Brewer는, 전통적인 관점에서 네트워크의 파티션은 커뮤니케이션의 단절을 의미하는 것이지만, 제한된 시간 내에 커뮤니케이션을 하지 못하는 것을 네트워크 파티션으로 보는 관점을 제시하고 있습니다. 즉, 어떤 오퍼레이션의 타임아웃이 발생한 시점에 시스템이 해당 오퍼레이션의 재시도를 시도한다면 Consistency를 선택하는 것이고, 그렇지 않는다면 Availability를 위해서 파티션을 허용하는 것입니다.

이러한 관점에서 시스템의 설계자가 목표로 하는 response time에 따라 타임아웃을 설정하면, 타임아웃이 발생하는 것을 파티션으로 감지하고, 파티션 모드로 진입할 수 있게 됩니다.

한편, 지연 (Latency)을 피하기 위해 강한 Consistency를 포기하는 시스템의 예로 Yahoo의 PNUTS를 들고 있고, 업데이트와 업데이트 이후 일정 시간동안의 읽기에 대해서는 지연을 허용하는 Facebook의 시스템을 그 반대의 사례로 들고 있습니다.

4. Managing partitions

Eric Brewer는 파티션을 다루는 전략을 다음과 같이 제안하고 있습니다.

우선 파티션은 흔하지 않기 때문에, CAP은 대부분의 시간 동안 완벽한 C와 A를 허용해야합니다. 하지만, 파티션이 발생했을 경우에는, 파티션을 매우 명시적으로 관리해야한다고 언급하고 있습니다. 이를 아래의 3단계와 그림으로 표현하고 있습니다.

  1. 파티션의 시작을 감지
  2. 특정 오퍼레이션을 제한할 수 있는 명시적인 파티션 모드에 진입
  3. 커뮤니케이션이 회복된 후, Consistency를 복구하고 파티션 모드 동안의 실수를 만회하기 위한 파티션 복구 프로세스를 시작

Managing Partition

4.1. Which operations should proceed?

기본적으로 어떤 오퍼레이션을 제한할지는 시스템이 유지해야하는 불변조건 (invariants)에 달려있습니다. 파티션 동안 유지되지 않아도 되고, 파티션 복구 시 쉽게 복구할 수 있는 경우 (예를 들어, 중복된 키들의 병합)에는 오퍼레이션을 허용할 수 있고, 파티션 동안 유지되어야만 하는 불변조건에 해당하는 경우에는 오퍼레이션을 금지하거나 지연하거나 수정해야 한다고 얘기하고 있습니다.

실제로 오퍼레이션에 대해 어떤 조치를 취할지는 시스템에 대한 모든 원자적인 (atomic) 오퍼레이션과 불변조건들의 테이블을 만들고 각각의 항목별로 오퍼레이션이 불변조건을 위반할 수 있는지 여부를 검토해야 합니다.

중요한 것은 실제로 시스템을 사용하는 사용자에게는 이러한 조치가 보이지 않는 것입니다. 신용카드 결제의 경우 통신이 불가능한 상황에서 의도를 기록한 후 나중에 실행하는 것이나, Bayou의 캘린더 애플리케이션에서 잠재적으로 inconsistent한 항목들을 다른 색상으로 표시하는 등 흔히 오프라인 모드라고 불리는 사용자 인터페이스에서의 처리를 언급하고 있습니다. 본질적으로 이러한 오프라인 모드는 장시간 동안의 파티션과 다름없다는 것입니다.

파티션이 끝난 후 복구를 위해 파티션의 양단에서 일어난 오퍼레이션의 이력을 추적하는 가장 좋은 방법으로 오퍼레이션들 사이의 인과적인 의존성을 보존하는 버전 벡터 (version vector)를 사용하는 것이라고 얘기하고 있고, 이러한 시스템의 좋은 예로 Dynamo를 들고 있습니다.

4.2. Partition recovery

파티션 복구 동안, 파티션 양단의 상태를 일관되게 (consistent) 만들고, 잘못된 응답이나 시스템 외부로의 영향 등 파티션 모드 동안에 이루어진 실수를 만회하는 두가지 문제를 해결해야 합니다.

우선 일관성의 문제는 파티션 상태로부터 시작해서 양단의 오퍼레이션들을 재실행 (roll-forward) 하면서 계속 일관된 상태를 유지하는 방식을 가장 쉬운 방식으로 언급하고 있습니다.

한편, 양단의 오퍼레이션을 병합할 때 발생하는 충돌에 대해서는, 일반적인 충돌 해결의 문제는 해결 불가능하지만, 현실적으로는 설계자는 파티션 동안의 오퍼레이션 제한을 통해 복구 동안의 자동 병합에서 문제가 생기지 않도록 합니다. (예를 들어, Google Docs는 텍스트의 추가, 삭제, 스타일의 적용으로 오퍼레이션을 제한)

상태의 자동적인 수렴을 위한 일반적인 프레임워크 중 하나는 상호적인 (commutative) 오퍼레이션입니다. 하지만, 실제로는 상호적인 오퍼레이션만 사용하는 것은 매우 어렵기 때문에, CRDT (Commutative Replicated Data Type)이란 개념을 소개하고 있습니다. CRDT는

  • 파티션 동안 모든 오퍼레이션들은 상호적임을 보장하거나,
  • 상태를 기록한 후, 파티션 동안에 일어나는 모든 오퍼레이션은 그 상태에 대해 단조적임을 보장합니다.

예를 들어, 집합에 대한 추가와 삭제 오퍼레이션에 대한 CRDT 구현은, 추가된 항목들과 삭제된 항목들에 대한 집합을 각각 유지하는 것입니다. 파티션 복구 시점에서 시스템은 양단의 집합으로부터 정리 작업을 수행할 수 있습니다. 정리 작업은 파티션 동안에는 불가능하기 때문에 파티션 이후로 지연시켜야 하는 오퍼레이션이지만, 인지되는 가용성을 제약하지 않습니다. 따라서, CRDT를 통해 상태를 구현할 수 있다면, 설계자는 Availability를 선택하더라도 파티션 이후에 자동적으로 상태가 수렴될 수 있도록 보장할 수 있습니다.

4.3. Compensating for mistakes

시스템 외부에 가해진 실수를 복구하는 것은 시스템 외부로의 영향에 대한 이력을 필요로 합니다. 어떤 사람이 전날 밤 술에 취해 전화를 걸었다면, 그 전화들은 외부로의 영향에 해당하고, 그 사람이 다음 날 아침 정상 상태가 되었을 때, 잘못 건 전화들에 대해 만회를 하기 위해서는 어젯밤 걸었던 전화들에 대한 기록을 필요로 합니다.

다른 예로, 파티션 동안에 시스템이 동일한 주문을 두 번 수행했다고 가정해봅시다. 시스템이 주문의 이력으로부터 중복된 주문을 식별할 수 있다면, 중복된 주문 중 하나를 취소하고 고객에게 적절하게 사과하는 메일을 보낼 수 있지만, 그러한 이력이 없다면, 그러한 실수를 파악하는 것은 고객의 부담이 됩니다.

Brewer는 여기서 보상 트랜잭션 (Compensating Transaction)의 개념을 소개하고 있습니다. 예를 들어, 하나의 트랜잭션으로 모든 직원들의 레코드들을 업데이트 하려고 한다면 모든 레코드를 lock하게 됩니다. 보상 트랜잭션은 커다란 트랜잭션을 다수의 작은 트랜잭션으로 쪼개어 각각 따로 commit하는 방식을 취합니다. 따라서, 원래의 커다란 트랜잭션을 취소하려고 할 때 이미 commit된 작은 트랜잭션의 영향을 수정하는 새로운 트랜잭션 -보상 트랜잭션을 실시합니다. 보상 트랜잭션의 접근은 전통적인 데이터베이스의 접근과 같이 직렬성 (Serializability)이나 격리성 (Isolation)에 의존하기 보다는 트랜잭션의 전체적인 영향에 의존합니다. 그리고 외부에 미친 영향까지도 고려가 되어야 합니다. 예를 들어, 중복된 지불을 환불하는 것은 애초에 고객에게 청구하지 않는 것과 같다고는 할 수 없지만, 거의 동등하다는 것이고, 이러한 생각 – 실수를 인정하고 보상을 통해 동등한 결과를 얻는 방식이 파티션 복구에서도 성립한다고 얘기하고 있습니다.

5. Closing

Eric Brewer가 마지막에서 강조하고 있는 것은 시스템 설계자는 파티션이 존재할 때, 무턱대고 consistency나 availability를 희생하려고 해서는 안되고, 이 글에서 언급하고 있는 방식을 통해 양쪽 모두를 최적화하려고 해야한다는 것입니다. 이를 위한 버전 벡터 (version vector)나 CRDT 등의 기술들이 프레임워크화되고 좀 더 보편화될 것이라고 얘기하고 있습니다.

제가 이 글을 읽고 느낀 점들은 아래와 같습니다.

  • CAP Theorem에 대해서 이 글을 읽기 전까지만 해도 단순화된 관점 – 2 of 3을 가지고 있었으나, 이 글을 읽고나서 CAP Theorem에 대한 이해도가 좀 더 높아졌고, 단순한 AP 또는 CP 시스템 이외의 시스템 설계의 많은 가능성에 대해서 생각해보게 되었습니다.
  • 상태의 수렴을 위한 데이터와 오퍼레이션의 재설계가 필요하다는 것은 깨닫고 있었지만, CRDT와 같은 좋은 사고 도구가 될 수 있는 정식화 (formalization)가 있는지는 몰랐습니다.
  • 결국 현재로서는 분산 저장 시스템은 매우 복잡한 설계가 필요할 뿐만 아니라, 전통적인 DB와는 다르게 보편화된 프레임워크가 있기 보다는 개별 시스템 별로 따로 설계를 해야하는 수준이기 때문에, 아직 더 많은 연구와 개발이 필요한 분야인 반면, 그만큼 비용이 많이 들어가는 부문인 것 같습니다.
  • CAP Theorem 특집의 글들을 읽고 있습니다만, 이를 요약할 수 있을 정도로 완벽하게 이해하고 정리하는 것은 매우 시간이 걸리는 일이네요. 여력이 된다면 다섯개의 글 모두 정리해보고 싶습니다.

 

 

Data Infrastructure @ LinkedIn

QCon London 2012에서의 Siddharth Anand의 강연입니다.

Data Infrastructure @ LinkedIn by Siddharth Anand

Siddharth Anand는 작년 7월에 열린 QCon London 2011에서 NoSQL @ Netflix라는 제목의 강연을 한 적이 있었는데, 1년 새 Netflix로부터 LinkedIn으로 옮긴 모양이군요.

강연의 제목대로 이 강연은 LinkedIn의 데이터 기술에 대해서 다루고 있습니다만, 특히 데이터베이스와 데이터의 복제 기술에 대해서 다루고 있습니다.

Read Scalability & Write Scalability

LinkedIn은 현재 Oracle을 주 데이터베이스로 사용하고 있으며, 사용자가 LinkedIn에 제공한 데이터 – 사용자의 프로필, 관계들은 모두 이 곳에 저장된다고 합니다. 이 데이터를 가공하여 생성된 2차 데이터들은 목적에 따라 여러 종류의 스토리지를 활용하고 있는 것으로 보입니다.

서비스의 규모가 커지면 물론 Oracle 성능의 Scalability가 문제가 되는데, 이를 Read Scalability와 Write Scalability의 문제로 나누어서 설명하고 있습니다.

Read Scalability는 Oracle Slaves나 Memcached, Voldemort 등의 복제나 캐시 스토리지를 활용하고 있고, Write Scalability는 Oracle이 동작하는 하드웨어를 업그레이드하거나 다른 기술 (예를 들어, Cassandra)을 사용하는 방법 밖에는 없다고 얘기합니다.

Oracle Slaves

Oracle Slaves의 경우, 문제는 결국 Master에 쓰여진 데이터와의 consistency가 문제가 되는데, LinkedIn에서는 writer에게는 그 다음에 따르는 read를 보장하고, 다른 reader들에게는 eventual consistency를 허용하고 있습니다. 이를 위해서 사용하는 메커니즘은 다음과 같습니다.

데이터 도메인별로 데이터에는 변경시점을 나타내는 timestamp를 가지고 있는데, 클라이언트로부터의 write는 항상 master에 대해 발생하며, 이 때 timestamp를 변경하면서 이 timestamp를 자신의 context에 보유하고 있습니다. 클라이언트가 read를 할 때는 먼저 slave에 대해 read 액세스를 하되, 읽어 들인 timestamp와 자신이 보유하고 있는 timestamp를 비교하여, 만약 자신이 보유하고 있는 timestamp가 더 작다면 master의 데이터를 읽습니다.

이러한 메커니즘이 Oracle에서 직접 지원되는 기능인지 inhouse에서 만든 클라이언트 라이브러리와 별도의 필드를 이용해 구성한 메커니즘인지는 정확히 모르겠습니다만, 비교적 간단한 방식으로 replication 방식의 gap 문제를 극복하고 있군요. 하지만, consistency가 항상 중요한 데이터라면 이러한 방식은 사용할 수 없기 때문에, 결국 consistency 요구사항에 대한 정확한 파악이 필요하다고 할 수 있습니다.

Voldemort

Voldemort는 Dynamo paper에 기반해 만들어진 분산 스토리지로, NoSQL이라는 키워드의 역사로 따지자면 선조격에 해당한다고 볼 수도 있겠네요. 이 글에서는 Dynamo에 관한 얘기는 생략하도록 하겠습니다. 이 후에 만들어진 Dynamo 계열의 스토리지인 Riak, Cassandra 등을 선택하지 않은 이유는 단지 Riak이나 Cassandra는 스토리지 기술을 선택할 당시 (2008년)에 존재하지 않았다고 하는군요.

다른 Dynamo 계열 스토리지에 대비해 Voldemort의 특징은 Layered Pluggable Architecture라고 하는데요. Conflict Resolution, Serialization, Repair Mechanism, Failure Detector, Routing 등의 Layer들을 필요에 따라 클라이언트 또는 서버 측으로 배치할 수 있다고 합니다. 현재 LinkedIn에서는 대부분의 기능을 클라이언트에서 수행하도록 구성 (fat client 방식)하고 있지만, 이들을 서버쪽으로 옮겨놓고자 (fat server 방식) 한다고 합니다.

Storage Engine도 필요에 따라 선택해서 사용할 수 있는데, Read-Write 저장소에는 BDB JE 스토리지 엔진을 사용하고 있고, Read-Only 저장소에는 커스터마이즈된 스토리지 엔진을 사용하는데, key는 원래의 key나 key의 MD5, value는 파일의 형태이되, 메모리 맵 인덱스 (offset의 index)를 통해서 필드를 액세스할 수 있도록 한다고 합니다.

LinkedIn에서는 Voldemort를 fault tolerant한 분산 memcached와 같이 생각한다고 합니다. Read-Only 저장소에 저장되는 데이터는 Hadoop을 통해서 생성된 데이터를 Voldemorts가 로드하는 배치를 통해 생성된다고 합니다.

DataBus: Timeline-Consistent Change Data Capture

DataBus는 LinkedIn에서 데이터를 복제하거나 2차 데이터를 생성하기 위한 주요 인프라라고 할 수 있습니다. Oracle master로의 write는 DataBus가 모두 복제하고 이를 검색 인덱스, 그래프 인덱스, replica, Standardization 서비스 등으로 보낸다고 합니다.

DataBus Architecture

DataBus는 Relay 서비스와 Bootstrap 서비스의 2가지로 이루어져 있는데, Relay는 Shard를 통해 분산되어 있고 Oracle로부터 받은 트랜잭션 – 트랜잭션을 통해 변경된 레코드 데이터 전체를 메모리 상에서 버퍼링하며, Avro로 인코딩해서 Bootstrap을 포함한  다른 스토리지로 전달하는 역할을 합니다. Bootstrap은 이러한 트랜잭션들을 저장해서 임의의 시점으로부터의 변경사항 (Consolidated Delta) 또는 특정 시점의 스냅샷 (Consistent Snapshot)을 제공할 수 있습니다. “arbitrary long lookback”이라고 부르는 기능인데요. DataBus의 Relay를 통해 트랜잭션을 수신하는 클라이언트가 재시작이나 실패 등으로 수신을 하지 못하는 상황이 벌어졌을 때, 새로운 클라이언트가 데이터를 얻어야 할 때 필요한 기능들이 아닐까 싶습니다. Bootstrap은 Log Storage와 Snapshot Storage로 구성되어 있고, Log Writer가 Log Storage에 write하고 Log Applier가 Snapshot Storage에 log를 적용하는 방식입니다.

SCN을 통해서 커밋 순서에 따른 전송 (in-commit-order delivery)을 보장한다거나 ‘arbitrary long lookback’과 같은 기능을 제공하는 것은 꽤 강력한 기능들인 것 같습니다. 반면에 Bootstrap의 데이터는 복제가 없기 때문에 데이터의 지속성 (durability)에 대해 취약한 면은 있는 것 같습니다.

Kafka: High-Volume Low-Latency Messaging System

Kafka는 기본적으로 일반적인 메시지큐라고 할 수 있는데, DataBus의 application-level 데이터 스트림, 사용자들의 행동 추적, 네트워크나 시스템의 메트릭 전송 등에 사용하고 있다고 합니다.

메시지큐의 topic (큐라고 생각하면 됩니다)은 sequential write로 쓰여지는 로그 방식의 파일이고, consumer는 이를 pulling 방식으로 가져갑니다. 그리고 topic들은 partition되어 있는데, 이들의 관리는 ZooKeeper가 담당합니다. sendfile을 이용한 zero copy와 같은 tuning들이 되어 있고, 자체적인 cache 없이 OS page cache에 의존한다고 합니다. 메시지의 전송 여부에 대한 기록은 없으며 단순히 일정 시간이 지나면 파기하는 방식이라고 합니다.

Secondary Index

NoSQL은 간단한 동작 방식 (semantic)을 통해 기존의 관계형 데이터베이스들에 비해서 가용성이나 성능을 높이는 것에 초점을 맞추고 있는데, Siddharth Anand는 secondary index를 NoSQL에 접목시키는 순간 복잡한 시스템이 되어버리고 애초의 장점을 잃는다고 얘기합니다. 따라서, DataBus와 같은 것을 이용해 비동기적으로 secondary index를 유지하는 방식을 선호한다고 얘기합니다. 클라이언트가 2군데 이상의 스토리지에 write를 하는 동기적인 방식에 대해서도 비판적으로 얘기합니다. 데이터의 불일치 문제 등에 대해서는 Netflix에서도 이를 해결하기 위한 reconcilation job이 존재했었고, 지인에게 듣기로는 Google에서도 비슷한 작업이 많이 있다고 합니다.

Closing

이 강연에서 가장 인상적인 것은 DataBus 였습니다.

  • 변경 사항을 실시간으로 제공하면서 변경 사항의 이력과 스냅샷을 제공하는 시스템
  • 어떤 이벤트에 대해서 수행해야할 여러가지 작업을 효과적으로 분산하는 메시지 전송 시스템

Oracle의 트랜잭션을 복제하는 것은 아주 오래전부터 캐싱을 위해서 활용하는 방법이기 때문에 새롭다고 하기는 힘들지만, 이력과 스냅샷을 제공하는 시스템으로 만든 것 하나만으로 유용성이 굉장히 높아지는 것 같습니다. 그리고, 이러한 방식 자체는 스토리지의 복제 뿐만 아니라 변화의 추적과 동시에 전체 데이터의 동기화를 필요로 하는 여러가지 장소에 활용될 수 있는 것 같습니다. 예를 들어,

  • 스마트 클라이언트의 동기화를 위한 서버 시스템: 스마트폰과 같이 로컬 스토리지를 가지고 있는 디바이스는 서버의 변경사항을 지속적으로 수신하면서도 서버의 데이터 전체를 받아야하거나 서버와의 동기화를 필요로하는 시점도 필요합니다. 이 때 서버에서는 이에 적합한 저장 방식을 보유하고 있어야 합니다.
  • 이종 스토리지 사이의 복제: Redis의 복제는 Redis의 특성상 lookback이 존재하지 않습니다. 즉, Redis slave로부터 master로의 접속이 한순간이라도 끊기면 모든 데이터를 다시 받아야 합니다. Redis-Redis 사이에서는 크게 문제가 안될지 모르지만, Redis-MySQL이나 Redis-HBase가 필요하다면 단순히 replication 프로토콜을 구현하는 것만으로는 해결되지 않습니다. 이런 경우에 유용하게 사용할 수 있습니다. MySQL을 예를 들면, 위의 Redis와 같은 문제를 해결하기 위해 binlog를 master쪽의 디스크에 저장하고 있습니다만, 당연히 master의 트랜잭션에 방해가 되거나 디스크의 용량을 크게 차지하기 때문에 이를 외부로 돌릴 수 있다면 메모리 상의 트랜잭션 로그나, 최소한의 binlog를 유지할 수 있게 되어 유용할지도 모르겠습니다.

반복되지만 스토리지 아키텍쳐의 구성에 대해서도 역시 계속 곱씹을 여지를 주는 것 같습니다.

  • 서비스에서 가장 핵심이 되는 데이터는 그 자체로는 Scalability에 한계가 있더라도 Oracle과 같은 신뢰성이 높은 스토리지를 이용한다.
  • 신뢰성이 높은 스토리지가 존재하고 이로부터의 트랜잭션을 순서대로 제공할 수 있는 기능만 있다면 비교적 단순한 구조 하에서 Eventual Consistency를 성취하는 것은 그리 어렵지 않을지도 모른다.
  • 사용자 입장에서 Loose한 Consistency를 제공하더라도 문제가 없는 데이터를 식별한다.
  • 데이터의 불일치에 대해서 수정하는 작업이 필요하다.

데이터 복제의 문제만 하더라도 일반적인 노드 사이의 복제, 클라이언트로부터의 quorum write, Proxy 등 여러가지 방법을 고민하고 있는데, 정말 쉬운 문제는 아니로군요.

한편, 강연에서 현재 LinkedIn에서 개발하고 있고 몇개월 내에 완성될 예정인 Espresso라는 key-value 스토리지에 대한 언급이 나오는데, 어떠한 목적을 가지고 만드는 것인지 궁금하네요. 어서 볼 수 있었으면 합니다.