이 글은 카프카를 창시한 사람이 작성한 아래 글을 요약한 글입니다.
The Log: What every software engineer should know about real-time data's unifying abstraction
I joined LinkedIn about six years ago at a particularly interesting time. We were just beginning to run up against the limits of our monolithic, centralized database and needed to start the transition to a portfolio of specialized distributed systems. This
engineering.linkedin.com
저는 6년전에 LinkedIn에 합류했고, 그동안 즐거운 시간을 보냈습니다. LinkedIn에 막 합류했을 때, 모놀리틱 데이터베이스 중심 시스템의 한계로부터 대응하기 위해서 분산 시스템으로의 전환을 시작하고 있었습니다. 이 과정에서 분산 그래프 데이터베이스, 검색 백엔드, 하둡, key-value 저장소를 설계하고 배포하는 과정은 정말 흥미로운 경험이었습니다.
이 과정에서 배운 가장 중요하고 간단한 핵심 개념은 로그 입니다. 누군가 write-ahead log나 commit log, transaction log라고도 부르는 로그는 대부분의 시스템이나 다양한 분산 시스템, 실시간 처리 아키텍처에서 핵심적인 역할을 하고 있습니다.
만약 로그에 대한 이해가 없다면 데이터베이스나, NoSQL 저장소, key-value 저장소, replication, paxos, hadoop, git과 같은 버전관리 시스템, 다른 어떤 소프트웨어 시스템에 대해서 완전하게 이해할 수 없을 것입니다. 하지만, 아직 대부분의 소프트웨어 개발자들은 이 로그 개념에 대해서 친숙하지 못한것 같습니다.
이 글에서는 로그가 뭐고, 어떻게 로그를 통해서 데이터 통합, 실시간 처리, 분산 시스템 구축을 할지 등등 우리가 로그에 대해 알아야할 모든것들에 대해 차근차근 알아볼 예정입니다.
Part One: 로그는 무엇인가?
(로그는 가장 간단한 저장소의 추상화로 볼 수 있습니다. 추가만 가능하고, 시간에 따라서 순서가 있는 레코드들의 연속된 집합으로 볼 수 있습니다.)

레코드들은 로그의 끝에 추가되고, 이 레코드를 읽고 처리하는건 왼쪽에서 오른쪽으로 진행됩니다. 각 레코드는 유니크한 로그 숫자가 할당됩니다.
레코드들의 순서는 시간 개념이 포함되어 있고, 왼쪽이 오른쪽 레코드보다 더 오래된 레코드입니다.로그 숫자는 레코드의 타임스탬프로도 볼 수 있습니다. 이 순서를 시간으로 보는 개념이 처음에 어색할 수 있지만, 물리적 시간하고 다른 구별된 개념으로 아주 편리한 개념입니다. 이 개념은 나중에 분산 시스템에서 필수적인 요소로 활용하게 됩니다.
레코드의 내부 내용이나 포맷은 이 포스트에서 중요하지 않습니다. 그리고, 우리가 추가한 레코드를 계속해서 가지고 있는건 결국 공간이 부족해질것이기 때문에 불가능하지만, 이건 나중에 다시 설명하겠습니다.
그러므로, 로그는 파일이나 테이블과 완전 다른건 아닙니다. 파일은 바이트의 배열이고, 테이블은 레코드의 배열입니다. 그리고, log는 단순하게 레코드들이 시간순으로 정렬된 레코드의 파일이나 테이블 입니다.
이 시점에서, 이렇게 간단한 개념에 대해서 얘기할 가치가 있는지 의문을 가질 수 있습니다. 어떻게 추가만 가능한 레코드의 연속된 집합이 데이터 시스템과 연관이 있습니까? 라는 의문을 품을 수 있고
이에 대한 답변하면 로그의 레코드는 어떤일이 언제 일어났는지를 담고 있고, 분산 시스템에서는 이 개념이 매우 중요하기 때문입니다.
여기서, 우리가 좀 더 로그에 대해 알아보기 전에 몇가지 헷갈릴만한 개념을 확실하게 짚고 넘어갈 필요가 있습니다. 모든 프로그래머들은 우리가 여기서 얘기하는 로그와는 다른 개념의 로깅에 친숙합니다. 애플리케이션에서 log4j나 syslog로 로컬에서 출력되거나, 다른 곳에서 볼 수 있는 구조화되어 있지 않은 에러 메시지나, trace 같이 우리가 애플리케이션에서 보는걸 우리는 애플리케이션 로깅이라고 부르겠습니다. 애플리케이션 로깅은 여기서 얘기하는 개념과는 다릅니다. 가장 큰 차이점은 애플리케이션 로깅이 인간이 읽기 편하게 설계되어 있는 반면에, 우리가 얘기하는 로그는 기계가 읽도록 설계된 데이터 로그라는 점입니다.
데이터베이스에서의 로그
저는 로그 컨셉이 어디에서 시작되었는지는 모릅니다. 아마도 이진탐색과 같이 너무 간단해서 이걸 만들어낸 사람이 이게 혁신이라는 것도 몰랐을 것입니다. 데이터베이스에서의 로그는 시스템의 예기치 못한 장애로부터 다양한 데이터 구조와 인덱스의 동기화를 맞추기 위해서 사용되었습니다. 데이터구조와 인덱스를 원자성있고, 지속성있게 만들기 위해, 데이터베이스는 레코드를 변경하고 다양한 데이터 구조에서 유지보수하기 전에 로그에 먼저 쓰게 됩니다. 로그에는 레코드에 어떤 일이 발생했는지를 기록했고, 각 테이블과 인덱스는 이 모든 로그 기록을 적용한 결과입니다. 로그에는 즉시 값이 쓰여지기 때문에, 데이터베이스에 장애가 발생했을 때 저장된 구조들을 복구하는 경우 가장 믿을만한 데이터가 되었습니다.
시간이 지나면서 로그는 데이터베이스 ACID의 세부사항을 구현하는 것부터, 데이터베이스 간에 데이터를 복제하는 방식에서까지 사용되기 시작했습니다. 로그는 데이터베이스에 발생한 변경사항들이었고, 이는 복제 데이터베이스에서 원본 데이터베이스와 sync를 맞출 때 필요한 내용이었습니다. Oracle, MySQL과 PostgreSQL은 복제 데이터베이스에 이 로그를 전달하는 프로토콜을 포함하고 있습니다. Oracle은 XStreams나 GoldenGate를 통해서 oracle이 아닌 데이터 구독자들에게 로그 데이터를 전달하는걸 상품화 하였으며, MySQL이나 PostgreSQL에서도 이 개념은 많은 데이터 구조에서 중요한 컴포넌트가 되었습니다.
이러한 기원 덕분에, 컴퓨터가 읽을 수 있는 로그 개념은 데이터베이스 내부 구현에 제한되었습니다. 로그를 데이터 구독에 사용하는 게 된건 우연에 가까운 일이었던것 같지만, 이 개념은 메시징과 데이터 플로우, 실시간 데이터 처리에 아주 이상적이었습니다.
분산 시스템에서의 로그
로그가 해결한 두 가지 문제가 있습니다.
1) 변경사항을 순서대로 나열하는것
2) 데이터를 분산하는 것
이고 이는 데이터베이스에서 보다 분산 시스템에 더 중요한 개념입니다.
분산 시스템에서 로그 기반 접근방식은 상태 복제 머신 원칙 으로부터 시작되었습니다.
(만약 두가지가 동일하다면, 결정적 프로세스는 같은 상태이고, 같은 입력이 같은 순서로 들어온다면, 같은 결과를 가져올 것이고, 같은 상태일 것이다)
아직, 이 개념이 모호할테지만, 이게 어떤걸 의미하는지, 한번 살펴보겠습니다.
결정적 이라는 의미는 프로세스가 타이밍에 의존하지 않고, 이게 결과에 영향을 미치지 않는 것입니다. 예를 들어서 스레드의 실행순서에 영향을 받는 프로그램이나, 현재 시간을 돌려주는 메소드를 호출하는 것들은 결정적이 아닙니다.
프로세스의 상태는 데이터가 머신 메모리나 디스크에 처리가 끝났을 때 어떻게 남아있는지를 나타냅니다.
같은 입력에 같은 순서라는 것에서부터 로그 개념이 들어오게 됩니다. 직관적으로, 만약 두 개의 결정적 프로그램에 같은 로그 입력이 들어오게 된다면, 같은 결과를 출력하게 됩니다.
분산 컴퓨팅 애플리케이션에서 이건 꽤 분명합니다. 분산 프로세스에 동일한 로그를 입력으로 넣어주는 시스템을 개발하는 것으로, 동일한 내용을 처리하는 여러개의 머신에서 발생하는 문제를 줄일 수 있습니다. 여기서의 로그의 목적은 각 복제 프로세스가 동기화를 맞추는 것으로 비 결정적 요소들을 제거하는데 있습니다.
여기까지 내용을 이해했다면, 더이상 복잡한것도 깊게 들어갈 것도 없습니다. “결정적 프로그램은 결정적입니다” 외에 더 말할것도 없습니다. 제 생각에 이건 분산 시스템 디자인에서 가장 일반적인 방식입니다.
이 방식에서 가장 괜찮은 점은 로그의 인덱스가 타임스탬프가 되어서 복제 프로세스의 현재 상태를 알 수 있다는 점입니다. 복제 프로세스의 상태를 처리한 로그의 인덱스로 표현 할 수 있습니다.
로그에 어떤 내용을 집어넣는지에 따라서 이 원칙을 다양한 방법으로 적용할 수 있습니다. 예를들어, 서비스에 들어오는 요청을 로깅할 수 있고, 요청에 따라서 변경된 상태를 로깅할 수 있고, 실행해야 하는 상태 변경 명령어를 로깅할 수 있습니다. 이론적으로는, 복제 프로세스가 실행해야할 명령어나, 메소드 이름, 인자들을 로깅할 수도 있습니다. 두 개의 프로세스가 동일한 입력을 처리하는한, 프로세스들은 같은 상태로 남을 것입니다.
로그를 사용하는 사람마다 로그의 사용방식을 다르게 설명할 수 있습니다. 데이터베이스를 개발하는 사람들은 보통 물리적, 논리적 로깅을 구별합니다. 물리적 로깅은 각 로우가 변경해야 하는 내용을 로깅하는 것이고, 논리적 로깅은 로우에 대한 변경사항이 아니라, 로우에 적용해야할 SQL 명령어들을 로깅하는 것입니다. (insert, update, delete)
분산시스템에서는 처리와 복제에 대한 두 가지 접근방식으로 구별하고 있습니다. “state machine model”은 보통 active-active 모델로 들어오는 로그요청을 가지고 있고, 복제 프로세스에서 이 요청을 처리하는 방식입니다. 이와 약간 다르게, “primary-backup model”은 하나의 복제 프로세스를 리더로 선출하고, 리더가 들어오는 요청을 처리한 뒤에 변경사항을 로그로 적는 것입니다. 다른 복제 프로세스들은 리더가 만든 변경사항을 순서대로 적용해서 리더와 동기화를 맞추고, 리더가 죽으면 리더처럼 행동할 준비를 합니다.

이 두가지 접근 방식의 차이점을 이해하기 위해서, 간단한 문제를 생각해보자. 0으로 시작하여 덧셈과 곱셉을 분산 계산기를 생각해볼 수 있을것입니다. “active-active” 접근방식에서는 로그를 먼저 +1, *2 등등을 쓰게 되고, 각 복제 프로세스는 이러한 변경사항을 적용하여 동일한 값을 만들 수 있을 것입니다. “active-passive” 접근방식에서는 하나의 Master가 변경사항을 실행하고, 로그로 “1”, “3”, “6” 등등을 쓰게 될 것입니다. 이 예시는 왜 순서가 복제 프로세스들의 상태를 동기화할 수 있는 핵심 요소인지도 명확하게 나타냅니다. 덧셈과 곱셈의 순서를 재정렬할 경우 다른 결과가 나오게 됩니다.

분산 로그는 합의 문제의 데이터 구조로도 볼 수 있습니다. 로그는 결국 추가할 다음값에 대한 결정들을 나타냅니다.
Paxos 같은 계열의 알고리즘에서는 로그를 아주 주의깊게 보아야 합니다. Paxos에서는 로그를 일련의 합의 문제로 모델링 하는 “multi-paxos”라는 확장 프로토콜 사용합니다. 로그는 ZAB, RAFT나 Viewstamped 복제같이 분산환경에서 일관된 로그를 유지하는 문제를 모델링한 프로토콜에서 좀 더 두드러지게 사용됩니다.
저는 분산 시스템 이론이, 실제로 적용되는 것보다 좀 더 빨랐기 때문에 약간 편향되었다고 생각합니다. 사실 합의문제는 정말 간단합니다. 컴퓨터 시스템은 가끔 하나의 값을 결정할 필요가 있고, 대부분 들어오는 요청을 처리한다는 점입니다. 그래서, 로그는 간단한 하나의 값 저장소로 자연스러운 추상화라고 볼 수 있습니다.
더 중요한건, 알고리즘에 집중하는게 아래 설명할 로그 추상화 시스템을 모호하게 만들 수 있다는 점입니다. 나중에 가서 우리는 로그를 좀 더 상용화된 빌딩 블록으로 집중해서 보게 될것입니다. 로그는 상용화된 인터페이스가 될 것이고, 다른 많은 알고리즘과 구현체들이 최고의 성능을 제공하기 위해 경쟁할 것입니다.
Changelog 101: 테이블과 이벤트는 동일하다
다시 데이터베이스로 잠깐 돌아와봅시다. 변경사항에 대한 로그와 테이블간에 주목할만한 유사한 점이 있습니다. 로그는 지금까지 진행해온 현금, 부채, 은행 처리에 대한 모든 리스트와 유사하고, 테이블은 현재 계좌에 있는 잔고와 유사합니다. 만약 변경사항에 대한 로그를 가지고 있다면, 이 변경사항들을 모두 적용하여 현재 테이블이 가지고 있는 상태를 만들어낼 수 있습니다. 이 테이블은 가장 마지막 상태만을 가지게 됩니다. 여기서 로그가 직관적으로 좀 더 근본적인 데이터 구조라는 걸 알 수 있습니다. 로그를 이용하면 테이블을 만들어내는것 이외에도, 다른 파생된 종류의 테이블들도 만들어 낼 수 있습니다.

이러한 처리는 반대로 생각해봐도 동일합니다. 만약 테이블에 대한 업데이트 사항을 알고 있다면, 이러한 변경사항들을 저장하고, 테이블에 대한 상태 변경사항을 모두 changelog로 publish 할 수 있습니다. 이 changelog를 이용해 준 실시간으로 복제를 지원할 수 있습니다. 그러므로, 테이블과 이벤트들은 유사합니다. 테이블은 남은 데이터를 지원하고, 로그들은 변경사항을 캡처 합니다. 로그의 마술은 만약 변경사항들에 대한 로그가 전부 남아있다면, 최종 테이블 내용을 알 수 있을뿐만 아니라, 다른 버전의 테이블들도 모두 재생성할 수 있다는 점입니다. 이는 테이블 이전 상태에 대한 모든 백업을 정렬해놓은 효과와 동일합니다.
이 내용을 보면서, 버전 관리 시스템이 떠오를 수 있습니다. 소스 버전관리 시스템과 데이터베이스는 아주 밀접하게 연관되어 있습니다. 버전 관리는 분산 데이터 시스템이 해결한 것과 유사한, 분산 상태 변경을 관리하는 문제를 해결하였습니다. 버전 관리 시스템은 보통 로그와 유사한 효과를 내는 소스 변경사항들을 모델링 합니다. 버전 관리 시스템을 사용하면서 현재 코드의 스냅샷에 checkout하고, 다른 분산 상태 시스템과 같이 로그를 통해 어떤게 일어났는지를 복제하고, 만약 업데이트가 되면 현재 스냅샷에 변경사항들을 pull 받을 수 있습니다.
여기까지가 약간 이론적으로 보일 수 있는 내용이었습니다. 겁먹지 마세요 !! 이제 우리는 좀 더 실용적인 내용들을 빠르게 알아볼 예정입니다.
What’s next
이 아티클에서 나머지에서는 지금까지 알아본 로그 내용을 기반으로 분산 시스템과 이를 추상화 하는 모델링에 대한 내용을 알아볼 예정입니다.
아래 내용을 포함하고 있습니다.
- 데이터 통합 - 회사의 모든 데이터들을 모든 저장소와 컴퓨터에서 처리할 수 있도록 하는 것
- 실시간 데이터 처리 - 데이터 스트림으로부터 실시간 처리
- 분산 시스템 디자인 - 어떻게 로그를 기반으로 간단하게 실용적인 시스템을 디자인 할 수 있을지
각 케이스에서, 로그는 히스토리에 대한 내용을 저장하고, 다시 실행할 수 있는 것과 같은 아주 간단한 기능을 제공하기 때문에 유용합니다. 놀랍게도, 이 문제들의 핵심은 얼마나 많은 머신들이 히스토리를 결정적인 방식으로 재실행 할 수 있을지에 달려있습니다.
Part Two: Data Integration
이제 데이터 통합이 어떤거고, 왜 중요한지 다시한번 로그를 돌아가면서 생각해봅시다.
(데이터통합은 회사의 모든 데이터를 모든 서비스와 시스템에서 사용가능하도록 하는 것입니다.)
이 장의 내용이 “data integration”보다 더 많은 걸 서술하고 있지만, 이 단어보다 더 좋은걸 찾지 못했습니다. 보통 잘 알려지고 인기있는 ETL이라는 개념으로 data integration을 하고 있는것 같습니다. 하지만, 여기서 설명하는 내용들은 일반화된 실시간 처리흐름을 포함한 ETL로 생각할 수 있습니다.

요새 빅데이터에 대한 과대선전과 지나친 관심 속에서 데이터통합에 대해 많이 듣지 못했겠지만, 모든 시스템에서 회사의 모든 데이터를 사용할 수 있게 하는 문제가 조직이 집중해야 하는 가장 가치있는 문제라고 생각합니다.
데이터를 효과적으로 사용하기 위해서는 피라미드 계층 구조를 따라야 합니다. 피라미드의 제일 밑 계층에서는 관련 데이터를 캡처해서 모두 수용가능한 처리 환경으로 집어넣어야 합니다. 이 데이터는 읽고 처리하기 쉽도록 통합적인 방식으로 모델링 되어야 합니다. 데이터를 캡처해서 동일한 방식으로 통합하는 요구사항이 완성된 이후에 이 데이터들을 MapReduce, 실시간 쿼리 처리 등등을 수행하는 프로세스를 만드는 것이 합리적입니다.
만약 신뢰할 수 있고, 완성된 데이터플로우가 없다면, 하둡 클러스터는 매우 비싸고 조립하기 어려운 히터밖에 되지 못합니다. 데이터와 그에대한 처리가 가능해진다면, 이제 데이터 모델링이나, 이해가능한 일관성있는 문법같은 좀 더 정제된 문제로 초점을 옮길 수 있습니다. 최종적으로는 더 나은 시각화, 알고리즘 처리와 예측 등등과 같이 좀 더 복잡한 처리과정에 집중하게 됩니다.
내 경험상으로 대부분의 회사들은 이 피라미드의 기반에 거대한 구멍이 있었습니다. 그들은 신뢰할 수 있는 데이터 플로우가 부족했습니다. 하지만, 데이터 모델링 기술로 바로 관심을 옮겨갔고, 이 때문에 엄청난 결함이 생기게 되었습니다.
그렇다면, 어떻게 신뢰할 수 있는 데이터 플로우를 회사의 모든 데이터 시스템에 구축할 수 있을까요?
Data Integration: 두 가지 어려운 점
두 개의 트렌드가 데이터 통합을 더 어렵게 만듭니다.
이벤트 데이터
첫 번째 트렌드는 이벤트 데이터의 등장입니다. 이벤트 데이터 레코드는 어떤 일이 일어났는지에 대한 데이터 입니다. 웹 시스템에서는, 유저 행동 로깅이 될 수 있고, 머신 레벨 이벤트나 통계같이 안전하게 데이터 센터 머신들을 모니터하고 작동시키는데 필요한 것들이 될 수 있습니다. 사람들은 이러한 로그데이터들이 애플리케이션에서 로깅하기 때문에 그 기능에 대해서 헷갈리는 경향이 있는 것 같습니다. 이 데이터는 현대 웹에서 가장 중요한 부분입니다. google의 fortune에서는 클릭 같은것들이 이벤트로 파이프라인을 통해 생성됩니다.
이러한 형식의 이벤트 데이터들은 전통적인 데이터베이스에서 사용했던것보다 몇배 규모를 가지게 됩니다. 이 때문에 데이터 처리에서 엄청난 챌린지가 생기게 됩니다.
전문화된 데이터 시스템의 확산
두 번째 트렌드는 전문화된 데이터 시스템들이 지난 5년보다 더 인기있고 자주 사용된다는 점입니다. 전문화된 시스템들은 OLAP, 검색, 온라인 스토리지, 배치 처리, 그래프 분석 등등이 있습니다.
더 많은 데이터와 더 많은 데이터들을 전문화된 시스템에서 처리하려는 니즈가 데이터 통합에서 아주 큰 문제를 만들어내게 됩니다.
Log-structured data flow
로그는 자연스러운 데이터 구조로 시스템간의 데이터 플로우를 다루는데 사용됩니다. 방법은 아주 간단합니다.
-> 회사의 모든 데이터를 실시간 구독 처리를 위해 중앙 로깅 시스템에 넣으면 됩니다.
데이터를 집어넣는 곳은 클릭이나, 페이지뷰같은 이벤트를 로깅하는 애플리케이션이 될 수 있고, 변경사항을 받는 데이터베이스가 될 수 있습니다. 각각의 구독 시스템의 경우 이 로그를 가능한 빨리 읽어서 자신의 저장소에 새로운 레코드를 반영하고, 자신이 읽은 로그의 위치를 변경합니다. 구독 시스템은 캐시, 하둡, 다른 데이터베이스들이 될 수 있습니다.

예를 들어, 로그 개념은 모든 구독 시스템이 변경사항을 어디까지 적용했는지에 대해 알려줍니다. 이러한 개념때문에 다른 구독 시스템들이 어디까지 로그를 읽었는지로 현재 상태를 추론해낼 수 있습니다.
이 개념을 확실하게 하기 위해서 데이터베이스와 캐시 서버들을 생각해볼 수 있습니다. 로그는 이 시스템들을 모두 동기화하는 방식을 제공하고, 각 시스템들이 어떤 상태인지를 추론할 수 있습니다. 우리가 이제 데이터베이스에 X라는 데이터를 썼다고 해봅시다. X는 캐시에서 이제 읽어야 하는 데이터입니다. 만약 최신 값을 못읽는 이슈를 해결하고 싶다면, X를 읽지 않은 캐시들을 접근하지 못하도록 해두면 됩니다.
또, 로그는 데이터 소비와 생산을 서로 비동기적으로 할 수 있는 버퍼 역할을 합니다. 이건 매우많은 이유들 때문에 중요한데, 특히 다른 구독 시스템들이 각각 다른 비율로 데이터를 소비할 수 있기 때문에 중요합니다. 만약 구독 시스템에 장애가 발생하거나, 예기치 못하게 종료되었을 경우 버퍼에 있는 내용을 통해서 다시 원 상태로 복구시킬 수 있습니다. 또, 구독 시스템은 자신의 페이스에 맞게 데이터 소비를 할 수 있습니다. 하둡같은 데이터 웨어하우스는 한시간이나 하루마다 데이터를 소비하고, 실시간 쿼리 시스템의 경우 실시간으로 데이터를 소비하면 됩니다. 데이터를 만들어내는쪽이나, 소비하는 쪽 모두 다른 시스템에 대해서 알지 못하기 때문에, 구독 시스템을 쉽게 추가하거나 제거할 수 있습니다.
특히 중요한점은, 데이터를 소비하는 시스템이 로그밖에 알지 못하고, 데이터를 만들어 내는쪽에 대해 전혀 알지 못한다는 점입니다. 소비 시스템은 데이터를 만들어내는 시스템이 RDBMS인지, key value 저장소인지, 실시간 쿼리 시스템인지 알 필요가 없습니다. 이 점이 사소해 보일 수 있지만, 정말 중요합니다.
저는 “messaging system”이나 “pub sub” 대신에 로그 라는 용어를 사용했는데, 이게 데이터 복제를 지원하기 위해서 필요한 실제적인 구현체에 더 밀접하고 구체적인 내용을 담고 있기 때문입니다. 저는 “publish subscribe”는 메시지를 설명하는데 있어서 많은걸 함축하지 못한다고 생각합니다. 만약 당신이 두개의 메시징시스템을 비교해본다면 매우 다른걸 보장해주고, 대부분의 것들은 도메인에 적합하지 않다는 걸 알게될 것입니다. 로그의 경우 메시징시스템에서 지속성과 강한 순서를 보장해주는걸로 생각할 수 있습니다.
로그는 여전히 단순한 인프라일 뿐입니다. 이게 데이터플로우에 대한 전부가 아닙니다. 나머지 얘기들은 메타데이터, 스키마, 호환성, 그리고 데이터 구조를 처리하기 위한 나머지 디테일들이 있습니다. 하지만, 신뢰가능하고, 일반적으로 데이터 플로우를 처리할 수 있을때까지, 디테일들은 부차적인 것으로 다룰것입니다.
LinkedIn 내부에서 아래와 같은 데이터 시스템을 처리하고 있습니다.
- 검색
- 소셜 그래프
- Voldemort (key value store)
- Espresso (document store)
- 추천 엔진
- OLAP 쿼리 엔진
- 하둡
각각 모두 전문화된 분산 시스템으로 각 영역에서 특화된 기능을 제공합니다.

로그를 데이터플로우에 사용하자는 아이디어는 제가 LinkedIn에 들어오기 전에도 떠돌고 있었습니다. 인프라에 가장 먼저 개발했던 건 데이터버스라고 불리는 서비스로 Oracle 테이블에서 로그 캡처를 해서 소셜 그래프나 검색 인덱스같은 다른 데이터베이스로 구독을 제공했습니다.
여기서 잠깐 맥락을 제공하겠습니다. 저는 2008년에 우리의 key-value 저장소를 출시하면서 작업을 시작했습니다. 다음 프로젝트는 하둡을 셋팅하는 일이었고, 우리의 추천 처리를 거기서 하였습니다. 우리는 이 분야에 대해 경험이 없었기 때문에 자연스럽게 몇주를 일정으로 잡고, 데이터를 넣고 빼는 작업을 하기로 했습니다. 그리고 나머지 일정에서는 아주 괜찮은 예측 알고리즘을 만들기로 하였습니다. 그렇게 긴 여정이 시작되었습니다.
우리는 기존에 있던 Oracle 데이터 웨어하우스에서 데이터를 스크랩핑할 계획을 세웠습니다. 우리가 Oracle에서 데이터를 빼내오면서 발견한 첫번째는 이게 일종의 흑마법과 유사하다는 점이었습니다. 더 안좋은 점은, 데이터 웨어하우스는 배치 처리에 적합하지 않다는 점이었습니다. 대부분의 작업은 롤백될 수 없었고, 이미 처리된 작업 보고에 특화되었습니다. 우리는 데이터 웨어하우스를 피하기로 결정했고, source 데이터베이스의 로그파일로 직접 접근하기로 결정했습니다. 마침내 우리는 데이터를 key-value 저장소로 서빙하는 파이프라인을 구현해냈습니다.
이런 평범한 데이터복사가 기존 개발에서 가장 중요한 과제가 되었습니다. 더 안좋은건 파이프라인에서 문제가 생길때마다 하둡은 쓸모없게 되었습니다. 아주 멋진 알고리즘에 안좋은 데이터를 넣어봤자 안좋은 결과만 나오게 되었습니다.
그리고 우리가 꽤나 일반적인 방식으로 파이프라인을 구현했지만, 새로운 source 가 필요할 때마다, 커스텀 셋팅이 또 필요하게 되었습니다. 당연히 그 과정에서 또 엄청난 에러와 실패가 새로 발생하게 되었습니다. 우리가 하둡으로 구현한 기능들은 인기있게 되었고, 여러 관계자들의 이를 통해 여러가지 다른 기능을 구현하고 싶어했습니다. 각 유저마다 새로운 데이터를 통합하려고 하는 니즈가 있었습니다.

몇 가지가 조금씩 눈에 들어오기 시작했습니다.
첫 번째로, 우리가 구축한 파이프라인은, 엉망이긴 했지만 매우 가치있었습니다. 하둡에서 데이터 처리를 하게 한것만으로 여러 가능성이 생겼습니다. 이전에는 데이터로 하지 못했던 것들이 가능해졌습니다. 이전에 전문화된 데이터베이스에만 있던 데이터들을 한곳에 모아둔것 만으로 새로운 프로덕트나 분석이 나오기 시작했습니다.
두 번째로, 신뢰할 수 있는 데이터로드를 위해 데이터 파이프라인에 높은 수준의 지원이 필요하다는 걸 깨달았습니다. 우리가 필요한 구조를 캡처하게 되면, 우리는 하둡에 데이터를 집어넣는걸 자동적으로 만들 수 있고, 새로운 데이터 source나 스키마 변경에 대해서 수동적인 노력이 필요 없게 되었습니다.
세 번째로, 우리는 아직도 낮은 데이터 커버리지를 가지고 있었습니다. LinkedIn에 있는 데이터들을 전반적으로 보면, 하둡에서 처리할 수 있는 퍼센티지는 낮았습니다. 새로운 데이터 source를 운영하는데 필요한 노력을 생각해보면, 작업을 완료하는건 쉽지 않은 일이었습니다.
우리가 진행한 상황을 봤을 때, 각 데이터 source와 destination에 커스텀 데이터 파이프라인을 구축하는건 불가능 했습니다. 우리는 수십개의 데이터 시스템과 저장소가 있었습니다. 이 모든 시스템끼리 각각 연결하는 파이프라인을 만드는건 아래와 같을 것입니다.

데이터 플로우가 양방향으로 가는데 주목할 필요가 있습니다. 모든 데이터 시스템이 source가 될수 있고, destination이 될 수 있다. 이 의미는 시스템마다 두개의 파이프라인을 만들어야 한다는 소리입니다.
이걸 구축하고 운영하려면 군대와 같이 많은 사람들이 필요할 것입니다. 이러한 방식으로 완전한 연결을 하려면 O(N^2)만큼의 파이프라인이 필요합니다.
대신에 우리는 아래와 같은 일반적인게 필요했습니다.

가능한한 우리는 데이터 소비자가 데이터 생산자와 분리되는게 필요했습니다. 이상적으로 하나의 통합된 데이터 저장소로 모두가 접근할 수 있는게 필요했습니다.
아이디어는 새로운 데이터 시스템을 추가할 때, 데이터 생산지건 목적지이건 모든 소비자와 연결하는 것 대신에 하나의 통합된 파이프라인에 연결하는 것입니다.
이 경험을 통해서 저는 우리가 메시징 시스템이나, 데이터베이스, 분산 시스템에서 봤던 로그 컨셉을 결합하여 Kafka를 만드는데 좀 더 집중하게 되었습니다. 우리는 모든 데이터들에 대해 중앙 파이프라인 처럼 동작하는 무언가를 원했고, 궁극적으로는 이를 통해서 데이터를 하둡이나 모니터링 등등 다양한 곳에서 이용하기를 원했습니다.
오랜 기간동안, Kafka는 독특한 인프라 시스템이 되었습니다. 데이터베이스도 아니고, 로그파일 수집 시스템도 아니고, 전통적인 메시징 시스템도 아니었습니다. 하지만, Amazon에서 Kafka와 비슷한 Kinesis라는 시스템을 제공하고 있습니다. 파이션을 다루는 방식, 데이터를 유지하는 방식, 소비자에게 고수준, 저수준 api를 나누는 방식 등등 많은 유사한 점이 있습니다. 나는 이에 대해 꽤 행복해하고 있습니다. 제가 만든 인프라 추상화가 AWS에서 서비스로 제공할 정도로 좋다는 것이니까요. 그들이 설명한 비전은 제가 설명했던 것들과 유사합니다. DynamoDB, RedShift, S3와 같은 다른 분산 시스템을 모두 연결하는 것입니다.
Relationship to ETL and the Data Warehouse
이제 데이터 웨어하우스에 대해서 조금만 얘기해봅시다. 데이터 웨어하우스는 분석 지원을 위한 통합 데이터 구조 저장소를 의미하며 매우 좋은 아이디어입니다. 잘 모르는 사람을 위해 좀 더 설명하면, 데이터웨어 하우스는 주기적으로 데이터베이스에서 데이터를 주기적으로 추출해서 이해할 수 있는 형태로 변경한 후, 중앙 데이터베이스 웨어 하우스로 데이터를 집어넣는 방법을 제공합니다. 모든 데이터를 복사해서 중앙 저장소에 가지는 것은 매우 분석과 처리에 있어서 매우 가치있는 자산입니다. 높은 레벨에서는, 이 방법은 Oracle을 쓰거나, Teradaya, Hadoop을 쓰던지간에 방식이 많이 변하지는 않습니다.
데이터웨어하우스는를 가지는건 놀랄만한 일이지만, 이를 구축하는건 약간 구식 방법입니다.
데이터 중심 회사의 핵심 문제는 통합 데이터가 데이터 웨어하우스에 강하게 연결되는 것입니다. 데이터웨어하우스는 배치 쿼리 인프라로 분석이나, 카운팅, 집계, 필터링에 매우 적합합니다. 하지만, 배치 시스템을 가지는건 완성된 데이터만을 가질 수 있으며, 실시간 데이터를 필요로 하는 시스템에서는 적합하지 않습니다. 검색 인덱싱이나, 모니터링 시스템 등이 될 수 있습니다.
내 관점에서는, ETL은 두가지로 볼 수 있습니다. 첫 번째로 다양한 시스템에 묶여있는 데이터를 자유롭게 해주는 데이터 추출 및 청소 과정으로, 특정 시스템에 맞지 않는 것들을 지우는 과정입니다. 두 번째로, 데이터를 데이터웨어하우스에 맞게 재구조화하는 과정입니다. (관계형 데이터베이스에 맞춰 만들어서, 고성능 컬럼 포맷으로 다시 재분해하는 과정입니다.) 이 두가지가 큰 문제입니다. 통합된 데이터는 실시간 시스템에 저지연 처리를 할 수 있어야 하고, 다른 실시간 저장 시스템에서 인덱싱 할 수 있어야 합니다.
데이터웨어하우스 팀의 고질적인 문제는 다른 팀이 만들어낸 데이터를 모으고 청소하는 책임이 있다는 점입니다. 데이터 생산자는 이 데이터가 데이터 웨어하우스에서 어떻게 쓰일지 알지 못하며, 데이터를 만들어내는 작업은 뽑아내기 매우 어렵고, 무거우며 사용가능한 형태로 변경하는 작업이 어려워지게 됩니다. 당연하게도, 중앙 팀은 다른 조직이 시스템을 확장해나가는 속도를 절대로 따라잡지 못합니다. 그 결과 데이터 커버리지는 빈틈이 많아지고, 데이터 흐름에서 에러가 쉽게 발생하며, 변경사항을 반영하는게 늦어지게 됩니다.
더 나은 접근방식은 잘 정의된 데이터 추가 API 통해 중앙에 로그를 이용한 중앙 파이프라인을 만드는 것입니다. 이 파이프라인에 통합하고, 깨끗하며 잘 정돈된 데이터를 연동하는건 데이터를 만들어내는 생산자가 책임지게 됩니다. 이 의미는 데이터를 만들어내는 쪽에서 파이프라인에 잘 정돈된 형태로 전달하거나 데이터를 받기 위해서 그들의 시스템 디자인이나 구현체를 신경써야 한다는 의미입니다. 이제 새로운 저장 시스템 추가가 데이터웨어 하우스 팀의 책임이 되지 않습니다. 데이터웨어하우스 팀은 좀 더 간단한 중앙 로깅 시스템에서 데이터를 받는 과정이나, 데이터를 변환하여 내보내는 것에만 집중할 수 있게 됩니다.

조직의 확장성에 대한 이 관점은 전통적인 데이터 웨어하우스를 넘어선 시스템을 적용할 때 특히 중요해집니다. 예를 들어, 한 조직이 완성된 데이터 셋을 넘어선 검색 기능을 제공하고 싶어할 수 있습니다. 아니면, 실시간 데이터를 기반으로 모니터링 과 알람 시스템을 구축하고 싶을 수 있습니다. 이 케이스 둘 다, 전통적인 데이터 웨어 하우스나, 하둡 클러스의 경우 부적절하게 됩니다. 더 안좋은것은, 데이터베이스 로드를 지원하기 위한 ETL 처리 파이프라인이 이러한 시스템에서는 쓸모가 없을 수 있고, 이 점이 데이터 웨어하우스를 채택하는 것 만큼이나 인프라에 큰작업에 될 수 있습니다. 이런 것들은 실현 불가능하고, 왜 대부분의 회사들이 이러한 기능을 가지고 있지 않은지 알 수 있습니다. 반대로, 회사가 통합된 피드와 잘 구조화된 데이터, 새로운 시스템이 완전히 데이터에 접근할 수 있도록 하면, 하나의 통합된 파이프라인만 있으면 됩니다.
이 아키텍처는 데이터를 청소하거나, 변환할 수 있도록 다양한 옵션을 제공합니다.
- 데이터 제공자가 회사의 로그 저장소에 데이터를 추가하기 전에 먼저 처리할 수 있습니다.
- 로그에서 실시간 변환 처리가 가능해집니다.
Log Files and Events
이제 이 아키텍처의 사이드 혜택에 대해서 얘기해봅시다. 시스템끼리 약하게 결합하도록 해주고, 이벤트 기반 시스템으로 동작할 수 있게 합니다.
웹 분야에서 활성 데이터에 대한 전형적인 접근방식은 텍스트 파일로 로깅하고, 이를 데이터웨어하우스나 하둡에서 스크래핑하여 집계하고, 쿼리하는 것입니다. 이 방식의 문제는 배치 ETL과 동일합니다. 시스템과 데이터웨어하우스간의 데이터플로우가 강하게 결합되어 있습니다.
LinkedIn에서는, 로그 중심의 방식으로 우리만의 이벤트 데이터를 핸들링하기로 결정했습니다. Kafka를 중앙에 두고, 이벤트 로그에 대한 여러 구독자를 두었습니다. 우리는 수백개의 이벤트 타입을 정의했습니다. 이 방식은 페이지 뷰, 광고, 검색, 서비스 호출 및 애플리케이션 예외들을 전부 커버하였습니다.
이 방식의 이점을 이해하기 위해서, 간단한 이벤트를 상상해봅시다. 채용공고 페이지에 공고를 올리는 것입니다. 공고 페이지는 공고 페이지는 공고를 보여주는 로직만 포함해야 합니다. 하지만, 꽤 동적인 사이트에서는, 공고를 보여주는것 이외에 다른 추가적인 로직이 쉽게 추가될 수 있습니다. 예를 들어서 아래와 같은 요구사항들이 생길 수 있습니다.
- 페이지 뷰 데이터를 오프라인 처리 목적으로 하둡에 보내야 합니다
- 페이지 뷰를 확실히 카운팅하여 공고를 보는 사람이 공고를 스크랩핑하는 것을 막아야 합니다.
- 공고 페이지들을 분석하기 위해서 뷰를 집계해야 합니다.
- 유저에게 적절한 공고를 추천하기 위해서 뷰를 기록해야 합니다.
- 공고의 인기를 추적하기 위해서 뷰를 기록해야 합니다.
간단하게 공고를 보여주는 작업이 매우 복잡해졌습니다. 그리고 모바일 어플리케이션에서 공고를 보여주는 것도 추가해야 합니다. 로직은 점점더 복잡하게 될 수 밖에 없습니다. 더 안좋은건, 시스템의 인터페이스가 매우 얽혀있게 됩니다. 공고를 보여주는 쪽에서 일하는 사람은 다른 시스템에 대해서 알아야할 필요가 생기게 됩니다. 이건 토이 수준의 문제고, 실제 애플리케이션은 더하면 더했지, 덜하지는 않습니다.
“event driven” 스타일은 이 접근법을 간단하게 해줍니다. 공고를 보여주는 페이지는 이제 공고를 단순하게 보여주고, 이 공고가 보여졌다는 팩트만 기록하게 됩니다. 이 사실에 흥미를 가지는 다른 시스템들은 단순하게 이 이벤트를 구독하여 처리하게 됩니다. 공고를 보여주는 코드는 이제 다른 시스템에 대하여 알 필요가 없고, 새로운 구독자가 나타나도 추가적인 변경사항은 필요없습니다.
Building a Scalable Log
당연하지만, 데이터 생산자들과 데이터 구독자를 분리하는건 새로울게 없습니다. 하지만, 실시간으로 웹 사이트에서 발생하는 모든 이벤트를 커밋로그를 보관하고 이를 여러명의 구독자들한테 실시간으로 전달하는건, 정말 큰 과제입니다. 우리가 중앙 로깅 시스템을 빠르고, 값싸고, 확장 가능하게 만들지 못하면 이 매커니즘은 그저 우아한 판타지밖에 되지 못합니다.
사람들은 분산 로그가 느리고, 무거운 추상화일것이라고 생각합니다. 하지만, 대용량 데이터 스트림을 디테일하게 구현하게 되면 이건 사실이 아닙니다. LinkedIn에서는 현재 하루에 Kafka로 600억건의 메시지를 처리하고 있습니다.
우리는 Kafka가 이 규모을 지원하기 위해 몇가지 방식을 썼습니다.
- 로그의 파티셔닝
- 배치를 통한 읽기와 쓰기
- 필요없는 데이터 복사 피하기
수평적인 확장을 통해서 우리는 로그를 파티션으로 나누었습니다.

각 카티션은 순서대로 로그를 정렬한 상태이지만, 글로벌한 순서는 보장되지 않습니다. 특정 파티션에 메시지를 할당하는건 로그를 쓰는 시스템에서 결정하고, 대부분의 경우 키를 이용하여 파티션을 고릅니다. 파티셔닝은 로그를 샤드간 조정 없이 추가할 수 있으며, 카프카 클러스터 사이즈와 함께 확장할 수 있습니다.
각 파티션은 설정한 복제숫자에 따라서 복제됩니다. 복제된 것들중 하나는 리더로 설정되고, 만약 리더가 중료되면, 다른 복제가 리더가 됩니다.
글로벌 파티션 순서는 한계가 있지만, 우리는 이걸 중요하지 않다고 생각했습니다. 사실 전형적으로 로그에 수백, 수천개의 구별된 프로세스가 접근하기 때문에 전체 순서에 대해서 논의하는건 의미가 없다고 생각했습니다. 대신에, 카프카는 특정 파티션에서 보낸 순서대로 받을 수 있는 건 보장하고 있습니다.
로그는 파일시스템과 같이, 순서대로 읽고, 쓰는걸 최적화 할 수 있습니다. 로그는 읽기, 쓰기 요청들을 한꺼번에 그룹으로 묶어서 높은 성능을 내도록 합니다. 카프카는 이 최적화를 공격적으로 추구하고 있습니다. 클라이언트에서 서버로 보낼때 배치로 보내게 되고, 디스크에 쓸 때 배치로 쓰게 되고, 복제도 배치로 동작합니다. 데이터 커밋, 소비자로 보낼 때도 마찬가지 입니다.
마지막으로, 카프카는 간단한 바이너리 포맷을 사용하여 인메모리와 디스크 로그에 유지하며, 네트워크 데이터 전송을 합니다. 이를 통해 제로카피 데이터 전송을 포함한 수많은 최적화를 할 수 있습니다.
이러한 최적화를 통해, 메모리를 많이 초과하는 요청에서도 디스크나 네트워크에서 지원하는 속도로 데이터를 쓰거나 읽을 수 있습니다.
출처
The Log: What every software engineer should know about real-time data's unifying abstraction
I joined LinkedIn about six years ago at a particularly interesting time. We were just beginning to run up against the limits of our monolithic, centralized database and needed to start the transition to a portfolio of specialized distributed systems. This
engineering.linkedin.com