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

Building Highly Available Systems in Erlang by Joe Armstrong

QCon London 2012에서의 Joe Armstrong의 강연입니다.

Building Highly Available Systems in Erlang by Joe Armstrong

Joe Armstrong은 에릭슨에서 일하던 1986년에 Erlang의 초기 버전을 개발했으며, Erlang OTP 프로젝트의 Chief Architect였습니다. Erlang은 1998년 open source로 나오기 전에는 에릭슨의 proprietary 언어로 개발 되었기 때문에 Erlang의 창시자라고 불러도 될지는 조금 애매하기는 하지만, 소위 Programming XXX라는 책 – Programming Erlang (2007)의 저자로서 그렇게 불러도 큰 문제는 없지 않을까 싶습니다. 2003년 PhD thesis로 Making reliable distributed systems in the presence of software errors이라는 논문을 내놓았는데 이 역시 Erlang에 관한 내용이고, 현재도 Erlang에 관련한 활동을 활발히 하고 있는 것 같습니다. Erlang의 역사에 대해서 좀 더 알아보기는 해야겠지만, 이런 것들만 보아도 꽤나 재미있는 이력이라고 생각합니다.

6 Rules

먼저 Redundancy, Consistent Hashing과 Chord의 Quorum System에 대해 간략하게 설명하고, Reliable Data Storage의 문제는 알고리즘 차원에서는 이미 풀렸다라고 선언합니다. 100% 납득이 가는 것은 아니지만, 그렇다고 인정하고 넘어간다면 그 다음엔 이 개념들을 어떻게 구현 – 코딩할 수 있을 것인가에 대해서, 6가지의 룰을 설명합니다. 결국 이러한 6가지 룰을 – 프레임워크나 라이브러리가 아니라 – 프로그래밍 언어 차원에서 구현한 언어가 바로 Erlang이라고 얘기하는 흐름이라서 조금 힘이 빠지는 느낌이 드는 것은 사실입니다.

1. Isolation

모든 것이 격리되어있다면, 더 많은 컴퓨터가 투입될 수록 시스템의 실패 확률은 점점 낮아질 수 있다는 얘기입니다. 이를 통해 10 nines도 실현 가능하다고 얘기합니다. Joe Armstrong은 Erlang 프로세스들이 공유하고 있는 문맥이 없다는 점으로 설명합니다. Erlang 프로세스들은 VM위에서 동작하는 가벼운 프로세스 (light-weight process)들로 일반적인 프로세스들처럼 메모리를 공유하고 있지 않습니다.

2. Concurrency

세상의 대부분의 문제들은 생각보다 병렬적(parallel)이고, 2대 이상의 컴퓨터를 제공함으로써 병행성(concurrent)을 확보할 뿐만 아니라 분산(distributed)된다고 얘기합니다. Erlang의 프로세스 얘기로 다시 돌아가면, 이론적으로 모든 Erlang 프로세스들은 병렬적으로 동작하고, 여러 core로 퍼져서 실행된다고 합니다.

3. Fault Detection

실패의 탐지를 위해서는 3대의 컴퓨터가 필요하다고 얘기합니다. 별도의 설명이 없지만 leader election의 이야기겠죠? Erlang 프로세스들은 실패를 탐지할 수 있도록 되어있고, 원격의 프로세스들을 연결해서 이들의 실패를 탐지해서 대신 문제를 해결하는 것도 가능하다고 합니다.

4. Fault Identification

실패한 원인을 아는 것도 중요하다고 얘기합니다. 이를 위해서는 4대의 컴퓨터가 필요하다고 얘기하는데 역시 consensus의 이야기일까요? 슬라이드에서는 코드를 함께 보여주지만, Erlang에서 에러를 알려줄 때, 마치 Exception 객체를 catch할 때 cause가 들어있는 것처럼 에러의 원인에 해당하는 코드도 함께 알려줍니다.

5. Live Code Upgrade

Erlang에 대해 간략한 설명을 들을 때 나름 감명깊었던 기능인데, Erlang에서는 애플리케이션이 실행되는 도중에 코드를 수정 – 업그레이드할 수 있습니다.

6. Stable Storage

Erlang에는 기본으로 제공되는 mnesia라는 storage가 있는데, 데이터를 disk와 RAM 둘다에 저장할지, RAM에 저장하되 복제할지 등등을 모두 customize할 수 있다고 합니다. 그 외에도 riak, couchdb 등의 꽤 잘 알려진 NoSQL storage들이 erlang에 기반하고 있습니다.

Messaging

발표의 중간 정도에서 Joe Armstrong은 몇가지 인용구를 보여주는데 아래와 같습니다.

the process achives fault containment by sharing no state with other processes; its only contact with other processes is via messages carries by a kernel messge system
–Jim Gray, Why do computers stop and what can be done about it?, 1985

실패를 격리하기 위해서는 프로세스 사이에 상태를 공유하지 않고, 하부의 메시지 시스템에 의해서 제공되는 메시지를 통해서만 다른 프로세스와 접촉해야한다라는 이야기네요. 1985년의 글이므로 아마 이 글이 Erlang의 설계에 커다란 영향을 미쳤을지도 모르겠습니다. Shared nothing 아키텍쳐라는 단어는 아마 다들 익숙하시리라 생각합니다. 현대의 웹 서비스 아키텍쳐에서는 이미 널리 쓰이고 있죠. 공유하는 상태를 분리하는 것은 매우 중요합니다만, 오히려 공기처럼 우리 주위에 있기 때문에 그 중요성이 간과되고 있는 것이 아닐까 생각이 들 정도입니다.

The big idea is “messaging” — that is what the kernel of Smalltalk/
Squeak is all about (and it’s something that was never quite completed
in our Xerox PARC phase)….

–Alan Kay

한편, 메시징의 가장 큰 성공은 넓은 의미에서는 클라이언트-서버 아키텍쳐에 있다고 생각하지만, 좁은 의미에서는 우리에게 익숙한 것은 아니라고 생각합니다. 비동기적인 메시징과 메시지 디스패쳐 스타일의 애플리케이션은 아직은 자주 볼 수 있는 것은 아님에도 불구하고, 커다란 규모의 시스템에서는 오래전부터 요구되어 왔던 것이고, 인터넷 서비스의 규모가 점점 커지면서 점점 자주 보게되지 않을까 싶습니다.

Fail Fast and Early

Halt on failure: in the event of an error a processor should halt instead of performing a possibly erroneous operation.

Failure status property: when a processor fails, other processors in the system must be informed. The reason for failure must be communicated.

Stable Storage Property: The storage of a processor should be partitioned into stable storage (which survives a processor crash) and volatile storage which is lost if a processor crashes.

— Schneider, ACM Computing Surveys 22(4):229-319, 1990

6 Rules에서도 어느 정도 언급이 되어있지만 기본적으로는 failure를 detection할 수 있어야 하고 failure가 발생한 프로세스는 정지시키고, 안정적인 스토리지에 저장된 상태를 이용해 다른 프로세스가 failure를 복구하는 개념입니다. 높은 수준의 가용성을 요구하는 하드웨어에서 2개의 circuit을 준비하고 언제든지 하나의 circuit에서 failure가 발견되면 다른 circuit으로 대체하는 것과 같은 이야기 같습니다. 특히, 여러 인용을 사용하여 fail-fast와 fail-early를 강조하고 있는데, 이러한 scheme에서 fault 상태의 지속을 최소화하기 위해서는 당연한 이야기 같습니다.

Closing

이 발표를 들은 것은 애초에 Erlang 자체보다는 HA 시스템을 만들기 위한 방법에 대한 Erlang의 관점을 알고 싶었던 것이고, Shared nothing + Messaging 아키텍쳐,  fail-fast, fail-early 등의 개념들의 중요성을 다시금 깨닫는 계기가 되었습니다.

Erlang을 사용해보지도 않은 상태에서 섣부른 생각일 수도 있겠지만, 이 발표를 통해 느낀 Erlang의 가장 큰 장점은 Shared nothing + Messaging 아키텍쳐의 애플리케이션을 개발할 때 필요한 거의 모든 것들을 공짜 로 또는 바로 (off-the-shelf) 제공한다는 것 같습니다. 현대의 웹 서비스들은 비교적 failure에 안전한 언어와 플랫폼 (Java), 실패를 탐지하고 복구하기 위한 메커니즘 (L4), 메시징에 기반한 프로토콜과 그 구현 (HTTP와 이를 concurrent하게 처리할 수 있는 웹 서버 구현), 안정적인 스토리지 (DB)로 만들어져 있기 때문에 역시 이 비용을 크게 느끼지 않는 것 뿐이지, 이러한 것들에 의존하지 않는 새로운 애플리케이션을 만들어야 한다면, 무시할 수 없는 비용을 치뤄야만 합니다. 예를 들어, 언제든지 segmentation fault를 맞이하더라도 이상하지 않는 C로 애플리케이션을 개발해야하고, 실패 탐지와 복구를 Linux HA를 사용하되, 애플리케이션의 요구사항에 따라 실패 탐지의 방법과 복구 방법을 개발해야하며, 메시징을 위해 RPC 프레임워크나 ESB를 개발하는거나 적절한 대안을 탐색하는 것, 새로운 애플리케이션의 특성에 적절한 분산 스토리지를 개발하거나 탐색하는 것 모두가 비용에 해당하는 것입니다. Erlang이 이에 게재되는 모든 문제를 해결하는 것이라고 생각하지는 않지만, 일부는 해결해주고 있다고 생각합니다.

한편으로는, Shared-nothing 아키텍쳐의 애플리케이션, 즉 상태를 공유하지 않아도 되는 애플리케이션은 개발하기도 쉽고, Scalability도 쉽게 보장되기 때문에, 비용은 애플리케이션 환경이나 요구사항에 따라서 다르겠지만, 상대적으로 쉬운 문제입니다. 정말 어려운 문제는 상태 즉, 안정적인 스토리지에 있습니다. 물론, 안정적인 스토리지의 구현에도 Erlang은 위에서 언급한 면에 커다란 도움을 줄 수 있다고 생각합니다만,  단지 구현상의 문제라고 보기에는 현실과 거리가 있다고 생각합니다. 안정적인 스토리지의 문제 전체 영역은 Erlang이 해결하는 영역에 비해서 훨씬 크다고 생각합니다.