공통/인프라 & 시스템 설계

[데이터 중심 애플리케이션 설계 정리] 스트림, Dual Write, CDC 정리

반응형
데이터 중심 애플리케이션 설계 중 일부를 정리한 내용입니다.

데이터베이스와 스트림

  • 이벤트는 특정 시점에 발생한 사건을 기록한 레코드.
  • 데이터베이스에 기록하는 것도 이벤트가 될 수 있다. (데이터베이스에 뭔가를 기록한다는 사실은 캡처해서 저장하고 처리할 수 있는 이벤트)
  • 복제 로그데이터베이스 기록 이벤트의 스트림이다.


시스템 동기화 유지하기

  • 데이터 저장과 질의, 처리 요구사항을 모두 만족하는 단일 시스템은 없고, 실제로 대부분의 애플리케이션이 요구사항을 만족하기 위해 몇 가지 다른 기술의 조합이 필요하다.
  • 사용자 요청에 대응하기 위한 OLTP 데이터베이스, 공통 요청의 응답 속도를 높이기 위한 캐시, 검색 질의를 다루기 위한 전문 색인, 분석용 데이터 웨어하우스 등이 각 예로, 이 시스템 각각은 데이터의 복제본을 가지고 있고 그 데이터는 목적에 맞춰 최적화된 형태로 각각 저장된다.

관련 있거나 동일한 데이터가 여러 다른 장소에 나타나기 때문에 서로 동기화가 필수다.

  • 데이터 웨어하우스에서는 이 동기화 과정을 대개 ETL과정에서 수행한다. (데이터베이스 전체를 복사하고 변환환 후 데이터 웨어하우스로 벌크 로드한다)

Dual Write

  • 주기적으로 데이터베이스 전체를 덤프하는 작업이 너무 느리면 대안으로 사용하는 방법으로 Dual write가 있다.
    • Dual write를 사용하면 데이터가 변할 때마다 애플리케이션 코드에서 명시적으로 각 시스템에 기록한다.
    • 그러나 Dual write에는 몇 가지 심각한 문제가 존재하는데, 그중 하나는 경쟁 조건이 발생할 수 있다.
    • 또 다른 이유는 한쪽 쓰기가 성공할 때 다른 쪽 쓰기는 실패할 수 있다는 점이다. (동시성 문제라기보다는 내결함성 문제로 두 시스템 간 불일치가 발생하는 현상)


CDC

  • 최근들어 CDC(change data capture)에 관심이 높아지고 있다.
  • CDC는 데이터베이스에 기록하는 모든 데이터의 변화를 관찰해 다른 시스템으로 데이터를 복제할 수 있는 형태로 추출하는 과정이다.
  • CDC는 데이터가 기록되자마자 변경 내용을 스트림으로 제공할 수 있으면 특히 유용하다.


CDC의 구현 

  • CDC는 본질적으로 변경 사항을 캡처할 데이터베이스 하나를 리더로 하고 나머지는 팔로워로 한다.
    • 로그 기반 메시지 브로커는 원본 데이터베이스에서 변경 이벤트를 전송하기에 적합하다. (로그 기반 메시지 브로커는 메시지 순서를 유지하기 때문)
  • 변경 데이터 캡처를 구현하는 데 데이터베이스 트리거를 사용하기도 한다.
    • 하지만 이 방식은 고장나기 쉽고 성능 오버헤드가 상당하다.
  • 복제 로그를 파싱하는 방식은 스키마 변경 대응 등 해결해야 할 여러 문제가 있지만 트리거 방식보다 견고한 방법이다
  • 링크드인의 데이터버스, 페이스북의 웜홀, 야후의 셰르파에서 대규모 데이터를 다룰 때 이 아이디어를 사용한다. 보틀드 워터는 쓰기 전 로그를 복호화하는 API를 사용해 Postgresql용 CDC를 구현하고, Maxwell과 Debezium은 binlog를 파싱 해 유사한 방식으로 MySQL용 CDC를 구현한다.
  • CDC는 메시지 브로커와 동일하게 비동기 방식으로 동작한다.
    • 레코드 데이터베이스 시스템은 변경 사항을 커밋하기 전에 변경 사항이 소비자에게 적용될 까지 기다리지 않는다.


초기 스냅숏

  • 데이터베이스에서 발생한 모든 변경 로그가 있다면 로그를 재현해서 데이터베이스의 전체 상태를 재구축할 수 있다. 그래서 대부분 모든 변경 사항을 영구적으로 보관하는 일디스크 공간이 너무 많이 필요하고 모든 로그를 재생하는 작업도 너무 오래 걸린다. 그래서 로그를 적당히 잘라야 한다.


로그 컴팩션

  • 주기적으로 같은 키의 로그 레코드를 찾아 중복을 제거하고 각 키에 대한 가장 최근에 갱신된 내용만 유지한다. (컴팩션과 병합 과정은 백그라운드로 실행된다)
  • 로그 구조화 저장 엔진에서 특별한 널 값 (tombstone)으로 갱신하는 것은 키의 삭제를 의미하고 로그 컴팩션을 수행할 때 실제로 값을 제거한다.
  • 그러나 tombstone은 키를 덮어쓰거나 삭제하지 않는 한 영구적으로 유지한다. (같은 키를 여러 번 덮어썻다면 이전 값은 결국 가비지 컬렉션 되고 최신 값이 유지된다)
  • 마찬가지로, CDC 시스템에서 모든 변경에 기본키가 포함되게 하고 키의 모든 갱신이 해당 키의 이전 값을 교체한다면 특정 키에 대한 최신 쓰기만 유지하면 충분하다.


변경 스트림용 API 지원

  • 최근 데이터베이스들은 기능 개선이나 리버스 엔지니어링을 통해 CDC 지원을 하기보다 점진적으로 변경 스트림을 기본 인터페이스로 지원하기 시작했다.
  • RethinkDB는 질의 결과에 변경이 있을 때 알림을 받을 수 있게 구독이 가능한 질의를 지원하고, Firebase와 CouchDB는 애플리케이션에도 사용 가능한 변경 피드 기반 데이터 동기화를 지원한다. 그리고 Meteor는 몽고DB의 oplog를 사용해 데이터 변경사항을 구독하거나 사용자 인터페이스를 갱신한다.
  • 카프카 커넥트는 카프카를 광범위한 데이터 시스템용 CDC로 활용하기 위한 노력의 일환이다.
    • 변경 이벤트를 스트림 하는 데 카프카를 사용하면 검색 색인과 같은 파생 데이터 시스템을 갱신하는 데 사용 가능하고 스트림 처리 시스템에도 이벤트 공급이 가능하다.


이벤트 소싱

  • 이벤트 소싱은 CDC와 유사하게 애플리케이션 상태 변화를 모두 변경 이벤트 로그로 저장한다. (큰 차이점은 추상화 레벨이 다르다는 점)
  • 변경 로그는 데이터베이스에서 저수준으로 추출한다. (예를 들어 복제 로그를 파싱)
    • 변경 로그는 데이터베이스에서 추출한 쓰기 순서가 실제로 데이터를 기록한 순서와 일치하고, 경쟁 조건이 나타나지 않도록 보장한다.
  • 이벤트 소싱에서 애플리케이션 로직은 이벤트 로그에 기록된 불변 이벤트를 기반으로 명시적으로 구축한다.
    • 이벤트는 저수준에서 상태 변경을 반영하는 것이 아니라 애플리케이션 수준에서 발생한 일을 반영하게끔 설계됐다.


이벤트 로그에서 현재 상태 파싱 하기

  • CDC와 마찬가지로 이벤트 로그를 재현하면 현재 시스템 상태를 재구성할 수 있지만, 다르게 처리해야 한다.
  • 레코드 갱신용 CDC 이벤트는 일반적으로 레코드의 가장 새로운 버전을 보유한다.
    • 그래서 기본키의 현재 값은 전적으로 기본키의 가장 최신 이벤트로 결정되고 같은 키의 이전 이벤트는 로그 컴팩션을 통해 버린다.
  • 이벤트 소싱은 이벤트를 보다 상위 수준에서 모델링한다. 대게 사용자 행동 결과로 발생한 상태 갱신 메커니즘이 아닌 사용자 행동 의도를 표현한다. 이 경우 뒤에 발생한 이벤트가 앞선 이벤트를 덮어쓰지 않는다.
    • 그래서 마지막 상태를 재구축하기 위해서는 이벤트의 전체 히스토리가 필요하다.


동시성 제어

  • 이벤트 소싱과 CDC의 가장 큰 단점은 이벤트 로그의 소비가 대게 비동기로 이뤄진다는 점이다.
    • 그래서 사용자가 로그에 이벤트를 기록하고 이어서 로그에서 파생된 뷰를 읽어도 기록한 이벤트가 아직 읽기 뷰에 반영되지 않았을 가능성이 있다.
  • 해결책 하나는 읽기 뷰의 갱신과 로그에 이벤트를 추가하는 작업을 동기적으로 수행하는 방법.
    • 여러 쓰기를 원자적 단위로 결합해야 하므로 이벤트 로그와 읽기 뷰를 같은 저장 시스템에 담아야 한다.
    • 다른 시스템에 있다면 분산 트랜잭션이 필요하다
  • 반면 이벤트 로그로 현재 상태를 만들면 동시성 제어 측면이 단순해진다.
    • 다중 객체 트랜잭션은 단일 사용자 동작이 여러 다른 장소의 데이터를 변경해야 할 때 필요하다.

 

  • 이벤트 로그와 애플리케이션 상태를 같은 방식으로 파티셔닝 하면 간단한 단일 스레드 로그 소비자는 쓰기용 동시성 제어는 필요하지 않다.

 

 

반응형