실시간 데이터 처리와 분산 시스템의 핵심인 '로그'에 대한 상세한 해설. 데이터베이스, 분산 시스템, 데이터 통합, 스트림 처리 등 다양한 활용 사례와 아키텍처 설계에 미치는 영향까지 총망라.
로그: 모든 소프트웨어 엔지니어가 실시간 데이터의 통합 추상화에 대해 알아야 할 것들
링크드인에 입사한 지 대략 6년 정도 됐을 무렵, 우리 회사는 단일 중앙 집중식 데이터베이스의 한계에 도달하기 시작했고, 특화된 분산 시스템 집합으로의 전환이 필요한 시기에 접어들고 있었습니다. 이후 우리는 분산 그래프 데이터베이스, 분산 검색 백엔드, 하둡(Hadoop) 설치, 1세대ㆍ2세대 키-값 스토어 등 다양한 시스템을 직접 구축·운영해왔습니다.
이 과정에서 제가 배운 가장 유용한 것 중 하나는 우리가 만든 많은 시스템 중심에는 "로그(log)"라는 대단히 단순한 개념이 자리잡고 있었다는 점입니다. 때로는 쓰기-선행 로그, 커밋 로그, 트랜잭션 로그 등으로 불리며, 로그는 컴퓨터만큼 오래된 개념이자 수많은 분산 데이터 시스템 및 실시간 애플리케이션 아키텍처의 핵심입니다.
데이터베이스, NoSQL 스토어, 키-값 스토어, 복제, 파락소스(Paxos), 하둡, 버전 관리, 그리고 거의 모든 소프트웨어 시스템을 이해하려면 로그를 이해해야 합니다. 하지만 대부분 소프트웨어 엔지니어들은 로그에 익숙하지 않습니다. 이 글을 통해 로그란 무엇이고, 로그를 데이터 통합, 실시간 처리, 시스템 구축에 어떻게 활용할 수 있는지 이해할 수 있도록 안내하겠습니다.
로그란 아마 가장 단순한 저장 추상화일 것입니다. 로그는 시간 순서로 정렬된 레코드의 추가-전용(append only) 완전히 순차적인 시퀀스입니다. 시각적으로 보면 다음과 같습니다.
레코드는 항상 로그의 끝에 추가되고, 읽기는 왼쪽에서 오른쪽 방향으로 진행합니다. 각 항목은 고유의 순차 로그 항목 번호를 가집니다.
이 순서는 시간 개념을 정의하는데, 왼쪽에 있는 항목이 오른쪽 항목보다 더 오래된 것으로 간주됩니다. 로그 항목 번호는 이 항목의 "타임스탬프"로 볼 수 있습니다. 시간이란 개념이 처음엔 다소 이상할 수 있지만, 이것이 실제 시계와 분리되어 있다는 점은 분산 시스템에서 아주 중요합니다.
레코드의 내용과 포맷은 이 글의 논의에서 중요하지 않습니다. 그리고 로그에 무한정 항목을 추가할 수도 없으니 언젠가는 공간이 부족합니다. 이 부분은 뒤에서 다루겠습니다.
즉, 로그는 파일이나 테이블과 별로 다르지 않습니다. 파일은 바이트의 배열, 테이블은 레코드의 배열이며, 로그는 레코드가 시간 순으로 정렬된 테이블이나 파일의 일종입니다.
그렇다면 이런 단순한 주제에 왜 주목해야 할까요? 데이터 시스템과 도대체 무슨 상관이 있을까요? 바로, 로그는 '무엇이 언제 일어났는지를 기록'하는 고유의 역할이 있기 때문입니다. 그리고 이는 분산 데이터 시스템에서 문제의 본질이 됩니다.
다만, 여기서 오해가 있을 수 있는데, 개발자라면 "로그"하면 흔히 애플리케이션에서 syslog나 log4j 등으로 남기는 비정형적인 에러 메시지·추적 정보 등을 떠올릴 겁니다. 본 글에서는 이를 "애플리케이션 로그"라고 부르겠습니다. 이와 달리, 제가 얘기하는 로그(저널 또는 데이터 로그)는 프로그램적으로 접근 가능한, 구조화된 로그입니다.
(사실 개별 서버 로그 파일을 사람이 직접 읽는 방식은 구시대적입니다. 수많은 서버 및 서비스가 얽힌 환경에서는 로그가 결국 질의·그래프의 입력인 셈이라 텍스트 파일보다 구조화된 로그가 훨씬 적합합니다.)
로그 개념의 기원은 분명하지 않지만, 아마도 너무 단순해서 발명이라고 인지되지 못했던 존재일 것입니다. 1970년대 IBM의 System R에도 나옵니다. 데이터베이스에서는 시스템 충돌 시 다양한 데이터 구조와 인덱스들을 동기화 상태로 유지하기 위해 로그를 사용합니다. 각종 구조에 변경을 반영하기 전에 로그에 변경사항을 먼저 기록하고, 이것이 원본 기록이 되어 시스템 복구 시 결정적 역할을 합니다.
시간이 흐르며, 로그는 ACID 구현의 디테일에서 데이터베이스 간 데이터 복제 방법으로 발전합니다. 데이터베이스에서 일어난 변경 내역의 시퀀스가 바로 원격 복제본과 동기화에 필요한 정보이기 때문입니다. 오라클, MySQL, PostgreSQL 등 대부분이 로그 배송 프로토콜을 갖추고 있습니다. 오라클의 XStreams, GoldenGate 등이 그러하며, MySQL, PostgreSQL 등도 유사한 기능이 핵심입니다.
이런 역사로 인해, "기계가 읽을 수 있는 로그" 개념은 대체로 데이터베이스 내부에 국한되었습니다. 하지만 이 추상화야말로 메시징, 데이터 플로우, 실시간 데이터 처리 등 다양한 형태를 지원하는데 가장 이상적입니다.
로그가 해결하는 두 가지 문제(변경의 순서 보장, 데이터 배포)는 분산 데이터 시스템에서 훨씬 더 중요합니다. 업데이트의 순서에 대한 합의(혹은 비합의의 처리)는 이런 시스템의 핵심 설계 문제입니다.
분산 시스템의 로그 중심 접근법은 "상태 복제(State Machine Replication) 원리"라는, 아주 단순한 통찰에서 비롯됩니다.
만약 두 개의 동일한 결정적 프로세스가 같은 상태에서 같은 입력을 같은 순서로 받게 되면, 동일한 출력과 최종 상태를 얻게 된다.
다소 추상적인데, 차근차근 설명해보겠습니다.
결정적(Deterministic)이라는 건, 처리 결과가 실행 시점이나 임의의 외부 입력(타이밍, 스레드 순서, gettimeofday
결과 등)에 영향을 받지 않는다는 뜻입니다.
프로세스의 _상태_란, 처리 완료 후 메모리/디스크의 모든 잔존 데이터를 뜻합니다.
중요한 것은 "같은 순서의 입력을 받는다"는 것으로, 바로 여기에 로그가 들어갑니다. 두 개의 결정적 코드에 동일한 입력 로그를 먹이면 결과는 항상 같습니다.
분산 컴퓨팅에 적용하면 간단합니다. 여러 기계가 똑같이 동작하도록 만드는 문제는 결국 이 기계들에게 동일한 일관된 분산 로그를 제공하는 문제로 귀결됩니다.
이 모델의 아름다운 점은, 로그의 타임스탬프가 곧 복제본의 논리적 "클럭"이 된다는 점입니다. 각 복제본은 "자신이 처리한 최대 로그 번호" 하나로 상태를 표현할 수 있게 됩니다. 로그와 이 번호가 결합되어 복제본의 전체 상태를 유일하게 포착합니다.
시스템에 따라 로그에 무엇을 기록하느냐에 따라 수많은 활용방식이 있습니다. 예컨대 서비스의 들어오는 요청을 로그하거나, 각 요청에 따른 상태 변화, 혹은 실행 명령을 로그할 수 있습니다. 극한으로는 머신 명령어, 메서드명/인자까지도 로그할 수 있겠죠.
분야에 따라 로그의 의미도 약간씩 달리합니다. 데이터베이스 분야에서는 물리적(physical) 로그(변경된 각 로우의 내용을 기록)와 논리적(logical) 로그(SQL 명령, 즉 insert/update/delete 등)로 나눕니다.
분산 시스템 분야에서는 "상태기계 모델(state machine model, active-active)"과 "primary-backup 모델"로 흔히 구분합니다. 전자는 들어오는 요청을 모든 복제본이 똑같이 처리(로그에는 트랜스폼 명령), 후자는 마스터가 요청을 처리하고 그 결과 변화만 로그합니다(로그에는 상태 변화만).
예를 들어 설명해보죠. 단일 숫자 상태값(초기 0)에 덧셈, 곱셈을 적용하는 아키텍처가 있다고 합시다. active-active 경우라면, 로그에는 +1, *2 등 트랜스폼 명령이 남고, 각 복제본이 이를 적용해 같은 값을 만듭니다. active-passive 모델이라면 하나의 마스터가 실제 변환 결과(1, 3, 6 등)만 로그에 남깁니다. 명령 순서가 다르면 결과가 달라짐을 알 수 있습니다.
분산 로그는 합의(consensus) 문제를 모델링합니다. 로그는 결국 "다음에 추가될 값"에 대한 일련의 합의 결정의 연속입니다. Paxos 류 알고리즘에서 로그 역할은 대부분 multi-paxos 형태로 구현되며, ZAB, RAFT, Viewstamped Replication 등은 아예 분산 로그 유지를 직접 모델링합니다.
실제로 컴퓨터 시스템은 단일 값의 합의보다는 요청 시퀀스의 처리가 주 목적이므로, 결국 로그 형태가 더 자연스러운 추상화입니다.
궁극적으로 로그는 commoditized(범용) 인터페이스가 되어 여러 구현(알고리즘)들이 최고의 성능과 보장을 경쟁하게 될 것입니다.
다시 데이터베이스로 돌아가 보죠. 변경의 로그와 테이블은 일종의 이중성(dual)이 있습니다. 로그는 은행 거래 내역, 테이블은 현재 계좌 잔고에 비유할 수 있습니다. 로그를 시간 순서대로 누적해서 테이블(최종 상태)을 만들 수 있습니다. 완전한 로그라면, 단순히 최종 테이블 뿐 아니라 그동안의 모든 버전을 재현할 수도 있습니다. 마치 모든 상태의 백업과 같습니다.
이는 소스코드 버전 관리를 떠올리게 합니다. 버전 관리 역시 분산 데이터 시스템과 유사한 문제(분산/동시 변경 관리)를 해결합니다. 버전 관리 시스템은 패치(로그) 시퀀스를 모델로, 체크아웃한 스냅샷(테이블)으로 작업합니다. 기록의 동기화 역시 로그를 통해 이루어집니다.
이런 로그 중심 데이터베이스의 대표 사례로 최근 Datomic이 떠오릅니다. 자세한 시스템 설계는 이 발표를 참고하세요.
다소 이론적으로 느껴질 수도 있지만, 실무의 영역으로 곧 들어갑니다.
남은 글에서는 로그가 분산 시스템이나 이론을 넘어서 실무적으로 어떤 역할을 하는지 살펴봅니다:
이 모든 활용점의 핵심에는 로그가 독립적 서비스로 존재한다는 개념이 있습니다.
각 경우마다 로그의 가장 중요한 기능은 영속적이고 재생(playback) 가능한 이력을 제공하는 것입니다. 놀랍게도, 이 문제의 본질은 "여러 머신이 자신의 속도에 따라 결정적으로 이력을 재생한다"는 것입니다.
먼저 "데이터 통합"이 무엇이고, 왜 중요한지 그리고 어떻게 로그와 연결되는지를 설명하겠습니다.
데이터 통합이란, 조직이 가진 모든 데이터를 모든 서비스와 시스템에서 사용할 수 있도록 하는 것입니다.
이렇듯 "데이터 통합"이란 용어는 흔하지 않지만, 이를 대체할 더 좋은 용어를 모르겠습니다. 흔히 ETL은 데이터 웨어하우스 채우기만을 의미하는데, 저는 여기서 말하는 데이터 통합이 모든 실시간 시스템과 데이터 처리 플로우를 포괄하는 것으로 확장된 ETL이라 보고 있습니다.
'빅데이터' 이야기만큼 화제가 되지는 않지만, 조직의 모든 데이터를 활용할 수 있게 만드는 이 따분한 문제야말로 오히려 더 큰 가치를 창출할 수 있다고 믿습니다.
효과적인 데이터 활용에는 일종의 마슬로우의 욕구 5단계 이론이 있습니다. 피라미드의 기초는 모든 관련 데이터를 수집하고 적절한 처리 환경(실시간 질의 시스템이든 단순한 텍스트 파일과 파이썬 스크립트든)에 엮는 것입니다. 이후 모델링, 인프라 구축, 처리 시스템의 개발이 논의될 수 있습니다.
완벽하고 신뢰할 수 있는 데이터 플로우 없이 하둡 클러스터는 값비싼 전기 히터일 뿐입니다. 데이터와 처리가 완비되어야 올바른 데이터 모델, 일관된 의미, 고급 시각화, 알고리즘 등이 가능합니다.
제 경험상, 많은 조직들이 데이터 플로우에 구멍이 숭숭 뚫려 있으면서도 고급 데이터 모델링부터 뛰어드려고 합니다. 이는 완전히 거꾸로 된 일입니다.
그렇다면 조직 전체 데이터 시스템에서 신뢰할 수 있는 데이터 플로우를 어떻게 구축해야 할까요?
첫 번째는 이벤트 데이터의 증가입니다. 웹 시스템에서는 사용자 활동 로그뿐만 아니라, 데이터센터 운용에 필요한 기계 레벨 이벤트/통계 데이터가 있습니다. 전통적으로 "로그 데이터"라고도 하나, 이는 형식(파일)에 불과합니다. 나아가서 사물인터넷, RFID, 금융 등 오프라인 영역에서도 이벤트 데이터 흐름이 늘고 있습니다.
이벤트 데이터는 "일어난 일"을 기록하므로 전통적 데이터베이스보다 훨씬 대용량입니다. 이는 처리 측면에서 큰 도전이 됩니다.
최근 5년 새 OLAP, 검색, 온라인 스토리지, 배치 처리, 그래프, 캐시, 실시간 처리 등과 같이 수십 종의 특화된, 오픈소스 데이터 시스템들이 등장했습니다.
데이터 양과 종류가 늘고, 다양한 시스템 간 데이터 이동 욕구가 커지면서 데이터 통합 문제의 규모도 커졌습니다.
로그는 시스템 간 데이터 플로우 처리에 가장 적합한 자료구조입니다. 그 방법은 아주 단순합니다:
조직 내 모든 데이터를 중앙 로그에 넣고, 이를 실시간 구독 방식으로 각 시스템에서 활용.
각 데이터 소스는 자체 로그로 모델링할 수 있습니다. 예를 들어, 클릭, 조회 이벤트를 남기는 애플리케이션, 혹은 수정이 발생하는 테이블 등이 하나의 로그가 될 수 있습니다. 구독 시스템은 로그를 소비하여 자체 저장소에 반영합니다. 구독자는 캐시, 하둡, 데이터베이스, 검색엔진 등 무엇이든 될 수 있습니다.
로그 덕분에 각 소비자(구독자)가 현재 어떤 타임스탬프까지 읽었는지 명확해져 상태를 쉽게 추론할 수 있습니다.
좀 더 구체적으로, 데이터베이스와 여러 캐시 서버가 있을 때, 로그는 이들 간 동기화에 핵심 역할을 합니다. 예를 들어, 로그의 X번 레코드를 쓴 후 캐시에서 읽는다면, 최소한 X까지 반영한 캐시에서만 읽으면 최신 데이터가 보장됩니다.
또 로그는 데이터 생산과 소비를 비동기로 만들어줍니다. 구독 시스템이 여러 개여도 각 시스템이 각자 속도로 읽으면 됩니다. 장애가 난 구독 시스템도 나중에 복구 가능합니다. 하둡 등 배치 시스템은 시간 단위로, 실시간 시스템은 실시간으로 소비하는 식이죠. 발신자나 로그는 소비처를 신경 쓸 필요가 없으므로 신규 소비처 추가가 용이합니다.
또한 소비자는 로그만 알면 되므로, 데이터가 어디서 왔는지(RDBMS, Key-Value DB, 기타 등등) 몰라도 됩니다. 이는 매우 중요합니다.
저는 "메시징 시스템"이나 "pub/sub"(퍼브섭) 대신 "로그"란 용어를 씁니다. 메시징 시스템마다 보장하는 바가 아주 제각각인 반면, 로그는 내구성과 순서 보장이 명확하기 때문입니다. 분산 시스템에서는 이를 atomic broadcast라고도 부릅니다.
로그는 인프라일 뿐, 진정한 데이터 플로우 관리에는 메타데이터, 스키마, 호환성 등 부가 요소가 더욱 중요합니다. 하지만 로그 없이는 그 어떤 의미론도 대단치 않습니다.
링크드인이 중앙 데이터베이스에서 다수의 분산 시스템으로 옮겨가면서 데이터 통합 문제가 빠르게 등장했습니다.
주요 데이터 시스템은 다음과 같습니다:
각 시스템은 각자 특화된 분산 시스템입니다.
데이터 흐름을 위한 로그 사용 아이디어는 아주 오래 전부터 있었습니다. 초기에 databus(데이터버스)란 서비스를 만들어 Oracle 테이블의 변경 구독 기능을 스케일하기 위한 로그 캐시 추상화를 제공했습니다.
저는 2008년경 링크드인에서 하둡 시스템 구축과 추천 알고리즘을 옮기는 과정을 담당하며 데이터 가져오기/내보내기에 수주만 투자하면 될 거라 생각했지만, 이게 얼마나 큰 난관인지 알게 되었습니다.
처음엔 Oracle 데이터 웨어하우스에서 데이터를 긁어오려 했는데, 빠르게 데이터를 꺼내는 건 거의 비법 영역이었고, 웨어하우스 가공 과정이 Hadoop용으로는 적절하지 않았습니다. 결과적으로 직접 소스 DB와 로그 파일을 썼고, Hadoop과 Key-Value 스토어에 각각 별도의 파이프라인을 만들어야 했습니다.
이 단순한 ETL 복사 과정이 차지하는 작업량이 거의 전체의 절반이었습니다. 파이프라인에 문제가 생기면 Hadoop 시스템은 무용지물이었습니다.
각 파이프라인은 상당히 제너릭하게 만들었지만, 새 데이터원을 추가하려면 매번 커스텀 설정과 처리가 필요했고, 오류 소지도 엄청났습니다. 기능이 인기를 끌면서 통합 요청과 신규 데이터 피드 요구가 이어졌습니다.
그리스 신화의 끊임없는 노동, ETL의 현주소
몇 가지를 깨달았습니다.
지금처럼 매 소스/목적지마다 맞춤 로드 파이프라인을 만든다면 관리 불가능한 수준에 도달합니다:
데이터 흐름 방향이 쌍방향인 경우가 많기 때문에 시스템마다 파이프라인이 2개씩 추가됩니다. 모든 시스템을 완전 연결하려면 O(N 제곱)개 만큼의 파이프라인이 필요합니다.
질서 있는 구조는 다음과 같습니다:
각 소비자는 단일(log) 저장소에만 연결해 모든 데이터에 접근할 수 있어야 합니다. 새로운 시스템 추가시, 파이프라인에만 한번 연결하면 되게 해야 합니다.
이런 고민은 Kafka 개발로 이어졌고, 메시징 시스템과 로그 구조를 결합하여 중앙 파이프라인(처음엔 액티비티, 점차 그 밖의 데이터들도 포함)으로 사용하게 되었습니다. 이에 자극 받아 아마존에서도 Kinesis라는 Kafka와 매우 유사한 서비스를 출시하며, 각종 시스템 연결에 로그/스트림 방식을 적용하기 시작했습니다.
데이터 웨어하우스는 클린하고 통합된 데이터를 담는 저장소로, 분석에 최적화된 구조입니다. 데이터 웨어하우스 구축은 소스 DB에서 데이터를 추출하고, 다듬어 Central Warehouse에 로드하는 과정입니다. 전통 DB, Hadoop 모두 이런 취지를 공유합니다.
그런데 현행 방식에는 한계가 있습니다.
문제는 이 클린 통합 데이터가 웨어하우스라는 배치 인프라에만 묶여 있다는 점입니다. 이 때문에 실시간 처리, 검색 인덱싱, 모니터링 등에서 활용이 제한됩니다.
ETL은 사실 크게 두 부분입니다. (1) 데이터 추출 및 정제(조직 내 여러 시스템별 특화 포맷 및 구조 깨기), (2) 데이터웨어하우스 대응 구조(관계형 DB 스키마, 스타/스노우플레이크 모델 등)로 변환입니다. 이 둘을 혼동하면 곤란합니다! 클린 통합 저장소가 실시간 처리에도 바로 활용될 수 있어야 하고, 웨어하우스식 집계 변환은 별도의 후처리에서 수행하는 것이 바람직합니다.
이렇게 하면 데이터웨어하우스 팀이 한 조직 내 모든 데이터 정제를 떠맡지 않아도 되고, 데이터 생산자가 자신이 만든 데이터를 중앙 저장소(log)에 넣을 책임만 지면 됩니다.
이런 구조는 데이터 커버리지, 신규 시스템 확장, 실시간 모니터링/검색 등으로 데이터플로우 유연성을 크게 높입니다.
데이터 구조 정제 기준은 다음과 같습니다:
즉, 정제는 최대한 앞에서, 목적지별 변환은 마지막에
이 아키텍처의 부가 효과 중 하나는 "이벤트 구동 시스템(event-driven architecture)"을 쉽게 만든다는 점입니다.
웹 업계에서 액티비티 데이터를 텍스트 파일로 남기고 Hadoop 등에 로드하는 방식이 여전히 널리 쓰이지만, 이는 언제나 배치 처리와 결합되어 데이터 플로우가 웨어하우스에 종속됩니다.
LinkedIn은 Kafka를 중앙 로그로 하여 수백종의 이벤트 타입을 정의, 단계별(뷰, 클릭, 광고조회, 서비스호출 등등)로 수집합니다.
이런 구조의 장점을 예로 들면, 취업공고페이지에 잡 포스팅 보여주기 로직이 진짜로 보여주는 역할만 담당하면 됩니다. 데이터 적재, 조회수 집계, 추천 시스템 연동, 모니터링 등 부수적 기능들은 각각 자체적으로 이벤트 피드를 구독(build)만 하면 되고, 잡 디스플레이 코드(프로듀서)는 추가적인 시스템들과 얽힐 필요가 전혀 없습니다.
프로듀서-구독자 분리는 새롭지 않지만, 대규모 소비자 접속, 실시간 이벤트 발행, 데이터 저널 등으로 로그가 유니버설 통합 수단이 되려면 확장성이 필수입니다. LinkedIn에서는 Kafka 기반으로 일일 600억 건 이상의 메시지(미러링 포함 수백억 규모)를 처리 중입니다.
Kafka 확장성의 핵심:
파티션은 독립적으로 완전히 순서가 보장되는 로그로, 각 메시지가 어느 파티션에 들어갈지는 클라이언트가 결정합니다(주로 키값으로 분할). 이러면 shards 간 조정 없이도 시스템 전체 처리량이 선형 확장 가능합니다.
각 파티션은 사용자 정의 레플리카 수 만큼 복제되어 고가용성을 보장합니다.
글로벌 순서가 없는 점은 한계처럼 보일 수 있지만, 실제 다양한 프로듀서가 동시에 동작하는 환경에서는 각 파티션 내에서만 순서가 보장되면 충분합니다. Kafka는 각 파티션에 대한 단일 프로듀서의 추가 시에는 순서를 반드시 보장합니다.
로그는 파일시스템처럼 연속 읽기/쓰기 패턴에 최적화가 쉽고, Kafka는 클라이언트~서버 전송, 디스크 기록, 클러스터간 복제, 소비자 전송 등 모든 경로에서 공격적으로 배치(batch) 방식(묶어서 전송)을 사용합니다.
Kafka는 메모리디스크네트워크 데이터 포맷이 모두 동일한 simple binary 형식이라, 제로-카피 전송 등 다양한 저수준 최적화가 가능합니다.
이 디테일 덕분에 메모리보다 훨씬 큰 대용량 데이터셋도 디스크/네트워크 한계까지 속도를 극대화할 수 있습니다.
Kafka의 상세 구조나 LinkedIn 아키텍처에 대해선 이 글 또는 Kafka 공식문서를 참고하세요.
여태까지는 사실상 "데이터를 여기서 저기로 복사"하는 멋진 방법을 설명했습니다. 하지만 로그란 사실상 스트림(stream)과 동일한 개념이며, 스트림 프로세싱의 핵심입니다.
그렇다면 스트림 프로세싱이란 무엇일까요?
DB 분야의 오로라, TelegraphCQ 등은 SQL기반 이벤트 처리라 보고, 오픈소스 생태계의 Storm, Akka, S4, Samza 등은 비동기 메시지 처리로 분류합니다. 하지만 본질은 SQL이나 언어와 상관없이, 시간 개념을 가진 데이터를 지속적으로 처리하는 인프라 전체를 뜻합니다.
데이터 수집이 배치라면 배치로, 지속적으로 수집한다면 계속적으로 처리할 수 있습니다. 미국 인구조사처럼 전수조사가 필요하던 시절에는 배치가 당연했고, 지금은 출생·사망 등 각종 실시간 데이터가 많으므로, 스트림 처리가 더 자연스럽습니다.
LinkedIn 역시 거의 모든 데이터가 실시간 생성(이벤트/DB 변경)이며, 일일 배치 작업도 사실 반복적으로 주기적 실행하는 스트림과 유사한 작업입니다. DataFu 같은 프레임워크도 이런 맥락에서 개발되었습니다.
즉, 스트림 프로세싱이란, 데이터에 시간성(time)을 가지며, 전체 데이터 스냅샷을 기다릴 필요 없이 사용자 제어 주기로 결과를 낼 수 있는 배치의 일반화라 할 수 있습니다.
과거 스트림 프로세싱이 틈새 분야였던 것은 실시간 데이터 수집의 부족 때문입니다. 기존 상용 스트림 처리 시스템들이 어려움을 겪었던 이유도 대다수가 파일 기반 일일 배치 등만 했기 때문입니다.
하지만 최근처럼 데이터 플로우가 실시간화되면 전체 인프라의 25%가 스트림 처리 대상이며, 로그 덕분에 가장 큰 난제인 멀티 구독 실시간 데이터 피드가 쉽게 해결됩니다. LinkedIn이 오픈한 Samza는 Kafka와 이런 아이디어 중심으로 만들어진 스트림 프로세싱 시스템입니다.
스트림 프로세싱의 핵심은 다양한 파생 피드를 1차 데이터처럼 피드(로그)로 생성하고, 이들을 자유롭게 그래프 형태로 연결할 수 있다는 점입니다. 실시간 조인, 집계, 변환 작업 등도 모두 파생 피드로 노출하면 됩니다.
스트림 프로세서는 로그를 읽고, 결과를 로그 혹은 외부 시스템에 쓰는 어떤 프로그램이든 될 수 있습니다. 이들 간 데이터 흐름 구조는 로그/피드를 중심으로 자연스럽게 DAG(Directed Acyclic Graph) 구조를 가집니다.
로그의 장점은 (1) 멀티 구독·순서 보장, (2) 대용량 버퍼 역할로서, 장애나 구독 시스템의 지연에도 전체 플로우엔 영향이 적습니다. 팀별 개발환경에서도 유용합니다.
Storm과 Samza)는 Kafka 등 로그를 토대로 동작합니다.
일부 실시간 처리(레코드 단위 변환)는 무상태(stateless)로 가능하지만, 실제로는 집계, 윈도우 합산, 조인 등 상태 관리가 필요합니다. 이를 위해 각 스트림 프로세서가 자체적으로 키-값 저장소(bdb, leveldb, lucene 등)를 두고, 입력 로그 피드에 따라 로컬 상태를 동기화하며 변동 내역(change log)을 기록할 수 있습니다. 장애시엔 로그로 상태를 복구할 수 있습니다.
이 덕분에 데이터베이스 로그와 동일한 원리로, 스트림 처리 애플리케이션에서도 상태 일관성과 복원이 확보됩니다. 자세한 내용은 Samza 문서를 참고하세요.
모든 상태 변화의 완전한 로그를 무한정 유지할 수는 없습니다. Kafka는 이벤트 데이터는 시간/용량 윈도우 기반, 키값 데이터는 최신 상태만 유지(=오래된 중복키 제거) 방식의 log compaction을 지원합니다. 이렇게 하면 로그에서 최신 상태만 유지하며 완전 복구가 가능합니다. log compaction 참조.
로그의 데이터플로우 역할은 분산 데이터베이스 내부와 조직 전체 데이터 통합 모두에 적용 가능합니다. 결국 기업 전체도 데이터베이스와 유사한 복잡한 분산 데이터 시스템입니다.
조직 전체 시스템·데이터 플로우를 하나의 분산 DB로 본다면, 각종 질의 시스템(레디스, 솔라, 하이브 등)은 각각 인덱스, 스트림처리(Samza, Storm 등)는 고도화된 트리거/뷰 물질화 시스템이라 할 수 있습니다. 전통 DB 커뮤니티가 왜 그토록 여러 다른 시스템을 쓰는가를 가장 쉽게 설명하는 뷰입니다.
각기 다른 시스템의 만연함은, 분산 데이터 시스템 구축의 난이도와도 같습니다. 각 시스템이 단일 요구만 다루면 개발이 쉬워지니까요. 하지만 다양한 시스템 운용은 곧 복잡성을 초래합니다.
미래 방향성은 대략 세 가지입니다:
이렇게 되면 각 분산 시스템 구현 속도가 획기적으로 단축되고, 단일 종합시스템으로의 융합 압력도 완화될 것입니다.
외부 로그 시스템을 전제로 하면, 각 시스템이 데이터 일관성, 복제, 커밋, 데이터 구독, 빠른 복구 등을 로그에 맡기고, 자체 구현 복잡성을 줄일 수 있습니다. 남는 건 클라이언트 API 및 인덱스 전략뿐이며, 이것이야말로 시스템 별로 차별화되는 부분이죠.
실제로 시스템은 로그와 서빙 레이어로 분리됩니다. 로그가 모든 상태 변화를 기록하고, 서빙 노드는 이를 인덱스에 반영합니다. 읽기 작업은 클라이언트가 로그 타임스탬프(최종 쓰기) 정보를 제공해 원하는 최신성 수준을 요청할 수도 있습니다.
마스터십도 필요에 따라 유무를 조정할 수 있습니다. 전체 복제, 분할 복구, 데이터 이동 등도 로그에서 일반 규칙으로 처리 가능합니다. 데이터베이스-ETL-검색 등 전방위 시스템이 동일한 로그를 기반으로 구동될 수 있습니다.
실제 LinkedIn의 실시간 질의 시스템(검색, 소셜 그래프, OLAP)들도 Kafka 등 로그 시스템을 중간에 두고, 각자 인덱싱/서빙만 신경 씁니다. 쓰기는 로그에만 하고, 각 노드가 분산 저장소의 피드를 자신의 저장소에 단순 반영하며, 장애시엔 로그 재생만으로 복구합니다.
이러한 로그 기반 구조는 데이터 파티션, 복구, 리밸런싱, 일관성 유지 등에서 모든 복잡성을 통합적으로 흡수합니다.
로그는 즉시 데이터플로우의 출발점이자 파이프라인의 핵심으로 수십종의 시스템에서 공유됩니다. 한편, 시스템별 인덱스 전략만 다르면, 쿼리 특성이 달라집니다.
Kafka, Bookeeper 등은 강한 일관성 로그입니다만, 반드시 그럴 필요는 없습니다. Dynamo류의 AP 일관성 DB도 마찬가지로 로그+서빙 계층 구조로 만들 수 있습니다.
로그가 데이터를 중복 보관하는 것이 낭비처럼 보일 수 있지만, 실제로 로그는 매우 효율적이고, 서빙 시스템은 쿼리 최적화/메모리/SSD 등 고가 장비를 쓰므로 아깝지 않습니다. 다양한 시스템에 동일 로그가 공급된다면 비용은 오히려 더 분산됩니다.
이패턴은 LinkedIn에서 검색, 소셜 그래프 등 수많은 실시간 시스템 구축에 활용된 대표적인 구조입니다. Kafka(데이터 로그)와 데이터베이스는 시스템 오브 레코드이며, 각각에 특화된 쿼리 시스템은 피드를 기준으로 동기화/복구만 신경 씁니다. 복제, 파티션, consistency, 데이터배포 등 모든 복잡성은 로그가 맡습니다.
여기까지 읽으셨다면 로그에 관해 제가 아는 대부분을 아시게 된 겁니다.
추가 참고 자료(학회 논문, 시스템, 블로그 등) 및 코드, 실무 적용 사례는 원문 마지막 참고문헌을 참고하시기 바랍니다.