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 스토리지에 대한 언급이 나오는데, 어떠한 목적을 가지고 만드는 것인지 궁금하네요. 어서 볼 수 있었으면 합니다.