책너두 (데이터 중심 애플리케이션 설계) 39일차 (~453p)

요약

  • 이벤트 스트림 전송 중, 파티셔닝된 로그에 대해 이해함.
    • 로그를 사용한 메시지 저장소
    • 로그 방식과 전통적인 메시징 방식의 비교
    • 소비자 오프셋
    • 디스크 공간 사용
    • 소비자가 생산자를 따라갈 수 없을 때
    • 오래된 메시지 재생
  • 데이터베이스와 스트림에 대해 이해함.
    • 시스템 동기화 유지하기
    • 변경 데이터 캡처
      • 변경 데이터 캡처의 구현
      • 초기 스냅숏
      • 로그 컴팩션
      • 변경 스트림용 API 지원

메모

파티셔닝된 로그

  • 파티셔닝된 로그는 네트워크 상에서의 일시적 연산과 데이터베이스의 영구적 저장 방식의 차이를 해결하기 위한 방법임.
    • 일반적인 메시징 시스템은 메시지를 소비자에게 전달한 후 즉시 삭제하지만, 데이터베이스와 파일 시스템은 명시적으로 삭제할 때까지 영구적으로 보관됨.
  • 이러한 차이 때문에 일괄 처리와 메시징 처리의 파생 데이터 생성 방식이 크게 다름.
    • 이를 해결하기 위해 로그 기반 메시지 브로커(log-based message broker)의 아이디어가 등장했음.
    • 이 아이디어는 데이터베이스의 지속성 있는 저장 방법과 메시징 시스템의 지연 시간이 짧은 알림 기능을 조합하려는 것임.
    • 이렇게 함으로써, 메시징 시스템에 새로운 소비자를 추가하더라도 과거에 기록했던 데이터를 읽을 수 있게 되어, 일괄 처리와 메시징 처리간의 차이를 해결할 수 있음.

로그를 사용한 메시지 저장소

  • 로그를 사용한 메시지 저장소는 메시지 브로커의 구현에 사용되는 구조로, 추가 전용 레코드의 연속을 디스크에 저장함.
    • 생산자는 메시지를 로그 끝에 추가하고, 소비자는 로그를 순차적으로 읽어 메시지를 받음.
    • 로그는 처리량을 높이기 위해 파티셔닝되며, 다른 파티션은 다른 장비에서 서비스할 수 있음.
    • 이 구조는 아파치 카프카(Apache Kafka), 아마존 키네시스 스트림(Amazon Kinesis Stream), 트위터의 분산 로그(DistributedLog)와 같은 로그 기반 메시지 브로커에서 사용됨.
  • 로그 기반 메시지 브로커는 모든 메시지를 디스크에 저장하되, 여러 장비에 메시지를 파티셔닝해 초당 수백만 개의 메시지를 처리할 수 있으며, 메시지를 복제해 장애에 대비할 수 있음.
    • 이런 방식의 브로커는 데이터베이스의 지속성 있는 저장 방법과 메시징 시스템의 지연 시간이 짧은 알림 기능을 결합할 수 있음.

로그 방식과 전통적인 메시징 방식의 비교

  • 로그 방식과 전통적인 메시징 방식의 비교에서 로그 기반 접근법은 팬 아웃 메시징 방식을 제공하며, 소비자들이 독립적으로 로그를 읽을 수 있음.
    • 이와 반대로, JMS/AMQP 방식은 메시지를 처리하는 비용이 높고 메시지 단위로 병렬화 처리할 수 있음.
  • 로그 기반 방식은 처리량이 많고 메시지를 처리하는 속도가 빠르며 메시지 순서가 중요한 경우 효과적임.
    • 그러나 토픽 하나를 소비하는 노드 수는 로그 파티션 수로 제한되며, 특정 메시지 처리가 느리면 후속 메시지 처리가 지연될 수 있음.
    • 이 경우, JMS/AMQP 방식의 메시지 브로커가 적합함.

소비자 오프셋

  • 소비자 오프셋을 사용하면 메시지 처리 위치를 쉽게 파악할 수 있음.
    • 브로커는 개별 메시지마다 확인 응답을 추적할 필요가 없고, 소비자 오프셋을 주기적으로 기록하기만 하면 됨.
    • 이 방법은 처리량을 늘리는 데 도움이 된다.
  • 메시지 오프셋은 로그 순차 번호와 유사하며, 데이터베이스 복제에서 사용된다. 소비자 노드에 장애가 발생하면, 다른 노드가 마지막 기록된 오프셋부터 메시지 처리를 시작함.
    • 장애가 발생한 소비자가 처리했지만 아직 오프셋을 기록하지 못한 메시지가 있다면, 이 메시지는 재시작 시 두 번 처리될 수 있음.

디스크 공간 사용

  • 로그를 추가하면 디스크 공간을 모두 사용할 수 있으므로, 로그를 여러 조각으로 나누고 오래된 조각을 삭제하거나 보관 저장소로 이동함.
    • 소비자 처리 속도가 느려 메시지가 생산되는 속도를 따라잡지 못하면 메시지 일부를 잃어버릴 수 있음.
    • 결과적으로 로그는 원형 버퍼(circular buffer) 또는 링 버퍼(ring buffer)로 구현된 크기가 제한된 버퍼를 사용함.
  • 일반적인 하드디스크의 경우, 최대 기록 속도로 메시지를 기록하면 디스크를 가득 채우는 데 약 11시간이 걸림.
    • 그 이후에는 오래된 메시지를 덮어씀.
    • 로그는 일반적으로 하드디스크 버퍼에 수 일에서 수주간 메시지를 보관할 수 있음.
    • 로그 처리량은 일정하며, 큐가 작을 때는 빠르지만 디스크에 기록하기 시작하면 매우 느려짐.

소비자가 생산자를 따라갈 수 없을 때

  • 소비자가 메시지를 전송하는 생산자를 따라갈 수 없을 때 선택할 수 있는 선택지는 메시지 버리기, 버퍼링, 배압 적용하기임.
    • 로그 기반 접근법은 대용량이지만 고정 크기의 버퍼를 사용하는 버퍼링 형태임.
  • 소비자가 뒤처져 필요한 메시지가 디스크에 보유한 메시지보다 오래되면 필요한 메시지는 읽을 수 없음.
    • 그래서 브로커는 버퍼 크기를 넘는 오래된 메시지를 자연스럽게 버림.
    • 소비자가 로그의 헤드로부터 얼마나 떨어졌는지 모니터링하면 눈에 띄게 뒤처지는 경우 경고할 수 있음.
  • 어떤 소비자가 너무 뒤처져서 메시지를 잃기 시작해도 해당 소비자만 영향을 받고 다른 소비자들의 서비스를 망치지는 않음.
    • 이 동작은 전통적인 메시지 브로커와 대조적임.
    • 전통적 메시지 브로커는 소비자가 중단되면 그 소비자가 사용하던 큐를 삭제해줘야 함.

오래된 메시지 재생

  • 이전에 AMQP와 JMS 유형의 메시지 브로커에서 메시지를 처리하고 확인 응답하는 작업은 브로커에서 메시지를 제거하기 때문에 파괴적 연산이라 언급했음.
    • 반면 로그 기반 메시지 브로커는 메시지를 소비하는 게 오히려 파일을 읽는 작업과 더 유사한데 로그를 변화시키지 않는 읽기 전용 연산이기 때문임.
  • 소비자의 출력을 제외한 메시지 처리의 유일한 부수 효과는 소비자 오프셋 이동임.
    • 하지만 소비자 오프셋은 소비자 관리 아래에 있음.
    • 그래서 필요하다면 쉽게 조작할 수 있음.
    • ex) 한 소비자의 어제 오프셋을 복사했다가 전날 분량의 메시지를 재처리하기 위해 다른 위치에 출력을 기록할 수 있음.
      • 또한 몇 번이든지 처리 코드를 변경해 재처리할 수 있다.
  • 이 점은 지난 장에서 다룬 일괄 처리와 유사한 측면임.
    • 로그 기반 메시징과 일괄 처리는 변환 처리를 반복해도 입력 데이터에 영향을 전혀 주지 않고 파생 데이터를 만듦.
    • 로그 기반 메시징 시스템은 많은 실험을 할 수 있고 오류와 버그를 복구하기 쉽기 때문에 조직 내에서 데이터플로를 통합하는 데 좋은 도구임.

데이터베이스와 스트림

  • 로그 기반 메시지 브로커가 데이터베이스에서 아이디어를 얻어 메시징에 성공적으로 적용한 것처럼, 메시징과 스트림에서 아이디어를 가져와 데이터베이스에 적용할 수 있음.
    • 이벤트는 특정 시점에 발생한 사건을 기록한 레코드로, 데이터베이스에 기록하는 것일 수도 있음.
    • 이를 통해 데이터베이스와 스트림 사이의 연결점이 단지 디스크에 로그를 저장하는 물리적 저장소 이상임을 알 수 있음.
  • 복제 로그는 데이터베이스 기록 이벤트의 스트림임.
    • 데이터베이스가 트랜잭션을 처리할 때 리더는 데이터베이스 기록 이벤트를 생산하고, 팔로워는 기록 스트림을 해당 데이터베이스 복제본에 기록해 완전히 동일한 데이터 복사본을 만듦.
    • 복제 로그 이벤트는 데이터에 변경이 발생했음을 나타냄.
  • 상태 기계 복제의 원리는 모든 이벤트가 데이터베이스 쓰기를 나타내고, 모든 복제 장비에서 같은 이벤트는 동일한 순서로 처리된다면 복제 장비는 동일한 최종 상태로 끝난다는 것임(이벤트 처리는 결정적 연산이라 가정).
    • 즉, 상태 기계 복제도 이벤트 스트림의 일종임.
  • 이 절에서는 이종 데이터 시스템에서 발생하는 문제 한 가지를 먼저 살펴본 다음, 이벤트 스트림의 아이디어를 데이터베이스에 적용해 이 문제를 해결하는 방법을 찾는다.

시스템 동기화 유지하기

  • 실제로 대부분의 중요 애플리케이션이 요구사항을 만족하기 위해 다양한 기술의 조합이 필요함.
    • 이 시스템 각각은 데이터의 복제본을 가지고 있고 그 데이터는 목적에 맞게 최적화된 형태로 각각 저장됨.
    • 데이터베이스에 아이템 하나를 갱신하면 캐시와 색인과 데이터 웨어하우스도 마찬가지로 갱신해야 함.
  • 데이터 웨어하우스에서는 이 동기화 과정을 대개 ETL 과정에서 수행한다.
    • 주기적으로 데이터베이스 전체를 덤프하는 작업이 너무 느리면 대안으로 사용하는 방법으로 이중 기록(dual write)이 있음.
    • 그러나 이중 기록에는 몇 가지 심각한 문제가 있음.
      • 그중 하나는 경쟁 조건으로, 동시에 아이템 X를 업데이트하려 하는 두 클라이언트의 요청이 서로 교차하여 두 시스템이 영원히 서로 일치하지 않는 상태가 됨.
      • 이중 쓰기의 다른 문제는 한쪽 쓰기가 성공할 때 다른 쪽 쓰기는 실패할 수 있다는 점으로, 동시성 문제내결함성 문제로 두 시스템 간 불일치 현상이 발생함.
  • 단일 리더 복제 데이터베이스를 사용한다면 상태 기계 복제 방법은 데이터베이스 복제본 사이에서 작동함.
    • 그러나 단일 리더가 없는 경우 충돌이 발생할 수 있음.
    • 색인용 인덱스를 데이터베이스의 팔로워로 만들어 실제로 리더 하나만 존재하게 한다면 상황은 휠씬 나음. 하지만 실제로 가능한 이야기인가?

변경 데이터 캡처

  • 과거에는 많은 데이터베이스가 데이터 변경 로그를 얻는 방법에 대한 문서를 제공하지 않았음.
    • 그래서 데이터베이스에서 발생하는 데이터 변화를 감지해서 검색 색인, 캐시, 데이터 웨어하우스 같은 다른 저장 기술에 복제하기 어려웠음.
  • 최근에는 변경 데이터 캡처(change data capture, CDC)에 대한 관심이 높아지고 있음.
    • CDC는 데이터베이스에 기록하는 모든 데이터의 변화를 관찰해 다른 시스템으로 데이터를 복제할 수 있는 형태로 추출하는 과정임.
    • CDC는 데이터가 기록되자마자 변경 내용을 스트림으로 제공할 수 있으면 특히 유용함.
  • 예를 들어, 데이터베이스의 변경 사항을 캡처해 같은 변경 사항을 검색 색인에 꾸준히 반영할 수 있음.
    • 같은 순서로 로그 변경이 반영된다면 데이터베이스의 데이터와 색인이 일치할 것임.
    • 검색 색인 뿐만 아니라 다른 파생 데이터 시스템도 단지 변경 스트림의 소비자가 될 수 있음.

변경 데이터 캡처의 구현

  • 변경 데이터 캡처(CDC)는 파생 데이터 시스템이 레코드 시스템의 정확한 데이터 복제본을 가지게 하기 위해 레코드 시스템에 발생하는 모든 변경 사항을 파생 데이터 시스템에 반영하는 것을 보장하는 메커니즘임.
  • CDC는 본질적으로 변경 사항을 캡처할 데이터베이스 하나를 리더로 하고 나머지를 팔로워로 함.
    • 로그 기반 메시지 브로커는 원본 데이터베이스에서 변경 이벤트를 전송하기에 적합함.
    • 로그 기반 메시지 브로커는 메시지 순서를 유지하기 때문임.
  • CDC를 구현하는 데 데이터베이스 트리거를 사용하기도 함.
    • 하지만 이 방식은 고장 나기 쉽고 성능 오버헤드가 상당함.
    • 복제 로그를 파싱하는 방식은 스키마 변경 대응 등 해결해야 할 여러 문제가 있지만 트리거 방식보다 견고한 방법임.
  • 링크트인의 데이터버스(Databus), 페이스북의 웜홀(Wormhole), 야후!의 셰르파(Sherpa)에서 대규모 데이터를 다룰 때 이 아이디어를 사용함.
    • 보틀드 워터(Bottled Water)는 포스트그레스큐엘용 CDC를 구현하고, 멕스웰(Maxwell)과 디비지움(Debezium)은 마이SQL용 변경 데이터 캡처를 구현함.
    • 몽고리버(Mongoriver)는 몽고DB의 oplog를 읽고, 골든게이트(Golden Gate)는 오라클용으로 비슷한 기능을 제공함.
  • 변경 데이터 캡처는 메시지 브로커와 동일하게 비동기 방식으로 동작함.
    • 레코드 데이터베이스 시스템은 변경 사항을 커밋하기 전에 변경 사항이 소비자에게 적용될 때까지 기다리지 않음.
    • 운영상 이점이 있는 설계로 느린 소비자가 추가되더라도 레코드 시스템에 미치는 영향이 적음.
    • 하지만 복제 지연의 모든 문제가 발생하는 단점이 있음.

초기 스냅숏

  • 데이터베이스에서 발생한 모든 변경 로그가 있다면 로그를 재현해서 데이터베이스의 전체 상태를 재구축할 수 있음.
    • 그러나 대부분 모든 변경 사항을 영구적으로 보관하는 일은 디스크 공간이 너무 많이 필요하고 모든 로그를 재생하는 작업도 너무 오래 걸림.
    • 그래서 로그를 적당히 잘라야 함.
  • 전문 색인을 새로 구축할 때를 예로 들면 전체 데이터베이스 복사본이 필요함.
    • 최근에 갱신하지 않은 항목은 로그에 없기 때문에 최근 변경 사항만 반영하는 것으로는 충분하지 않음.
    • 따라서 전체 로그 히스토리가 없다면 일관성 있는 스냅숏을 사용해야 함.
  • 데이터베이스 스냅숏은 변경 로그의 위치나 오프셋에 대응돼야 함.
    • 그래야 스냅숏 이후에 변경 사항을 적용할 시점을 알 수 있음.
    • 일부 CDC 도구는 이런 스냅숏 기능을 내장하고 있으나 수작업으로 진행해야 하는 CDC 도구도 있음.

로그 컴팩션

  • 로그 히스토리의 양을 제한한다면 새로운 파생 데이터 시스템을 추가할 때마다 스냅숏을 만들어야 한다.
    • 하지만 로그 컴팩션(log compaction)이라는 대안이 있음.
  • 로그 컴팩션은 저장 엔진이 주기적으로 같은 키의 로그 레코드를 찾아 중복을 제거하고 각 키에 대해 가장 최근에 갱신된 내용만 유지함.
    • 컴팩션과 병합 과정은 백그라운드로 실행됨.
  • 로그 컴팩션의 원리를 CDC 시스템에서 적용할 경우, 특정 키에 대해 최신 쓰기만 유지하면 충분함.
    • 검색 색인과 같은 파생 데이터 시스템을 재구축할 때마다 새 소비자는 컴팩션된 로그 토픽의 오프셋 0부터 시작해서 순차적으로 데이터베이스의 모든 키를 스캔하면 됨.
    • 이렇게 하면 CDC 원본 데이터베이스의 스냅숏을 만들지 않고도 데이터베이스 콘텐츠 전체의 복사본을 얻을 수 있음.
  • 아파치 카프카는 로그 컴팩션 기능을 제공함.
    • 메시지 브로커는 일시적 메시징뿐만 아니라 지속성 있는 저장소로도 사용 가능함.

변경 스트림용 API 지원

  • 최근 데이터베이스들은 점진적으로 변경 스트림을 기본 인터페이스로서 지원하기 시작함.
    • ex) RethinkDB는 구독이 가능한 질의를 지원하며, Firebase와 CouchDB는 애플리케이션에도 사용 가능한 변경 피드 기반의 데이터 동기화를 지원함.
    • 또한, Meteor는 몽고DB의 oplog를 사용해 데이터 변경사항을 구독하거나 사용자 인터페이스를 갱신함.
  • 볼트DB(VoltDB)는 스트림 형태로 데이터베이스에서 데이터를 지속적으로 내보내는 트랜잭션을 제공함.
    • 외부 소비자는 이 로그를 비동기로 소비해 파생 데이터 시스템을 갱신하는 데 사용함.
  • 카프카 커넥트(Kafka Connect)는 카프카를 광범위한 데이터 시스템용 변경 데이터 캡처 도구로 활용하기 위한 노력의 일환임.
    • 변경 이벤트를 스트림하는 데 카프카를 사용하면 검색 색인과 같은 파생 데이터 시스템을 갱신하는 데 사용 가능하고 스트림 처리 시스템에도 이벤트 공급이 가능함.

댓글

Designed by JB FACTORY