데이터 중심 애플리케이션 설계 - 스트림 처리(1)

문지은·2022년 4월 14일
0

일괄 처리는 입력을 사전에 알려진 유한한 크기로 한정. 하지만 실제로는 많은 데이터가 점진적으로 들어오기 때문에 입력의 크기를 한정지을 수 없고 입력의 변화가 너무 늦게 반영된다는 단점이 있다. 이를 위해서 고정된 시간 조각이라는 개념을 버리고 단순히 이벤트가 발생할 때마다 처리하는 것이 스트림의 개념이다.

이벤트 스트림 전송

스트림 처리에서 레코드는 이벤트로 표현
생산자가 이벤트 생성
소비자가 처리
토픽이나 스트림으로 이벤트 묶음

이벤트 알림 목적으로 특별하게 개발된 도구들을 살펴보자.

메시징 시스템

가장 간단한 방법은 생산자, 소비자 사이에 직접 통신 채널 사용하는 방법. 이 시스템을 확장하여 다수의 생산자가 하나의 토픽에 데이터를 전송할 수 있고 다수의 소비자가 하나의 토픽에서 데이터를 받아갈 수 있다.

다양한 툴에서 pub/sub 모델을 사용하고 있다. 여기서 생각해봐야 할 점 두가지가 있다.

  • 생산자가 소비자가 처리하는 메세지보다 더 빠르게 메세지를 전송한다면?
  • 노드가 죽거나 일시적으로 오프라인이 된다면?

이처럼 직접 메시징 시스템은 메시지가 유실될 수 있는 가능성을 고려해서 애플리케이션을 작성해야 한다. 이에 대한 대안점으로 메시지 브로커를 사용한다.

메시지 브로커

생산자와 소비자 사이에 브로커를 두어 위에서 생산자와 소비자가 책임져야 했던 이슈들을 브로커에게 위임할 수 있다. 일반적으로 소비자는 비동기로 동작한다.

메시지 브로커와 데이터베이스 비교

  • 데이터베이스
    데이터가 삭제될 때까지 보관
    데이터 전체 가지고 있음
    보조 색인 및 다양한 검색 지원
    질의 결과는 질의 시점의 스냅숏을 기준으로

  • 메시지 브로커
    소비자에게 데이터 전달되면 삭제
    큐 크기가 작다
    특정 패턴과 부합하는 토픽의 부분 집합을 구독하는 방식
    데이터가 변화면 클라이언트에게 알려줌

복수 소비자

복수 소비자가 데이터를 읽을 때 두가지 방식 존재

  • 로드 밸런싱
    메세지가 소비자 중 하나로 전달. 소비자들은 토픽의 메시지 처리 방법을 공유. 메시지 처리 비용이 비쌀 때 사용.
  • 팬 아웃
    메세지가 모든 소비자에게 전달.

이 방식은 함께 사용 가능.

확인 응답과 재전송

메시지를 잃어버리지 않기 위해 메시지 브로커는 확인 응답을 사용
부하 균형 분산과 결합할 때 이런 재전송 행위는 흥미롭게도 메시지 순서에 영향을 미친다.

파티셔닝된 로그

브로커는 메시지를 일시적으로 보관하는 개념. 데이터베이스와 파일 시스템의 접근법은 이와 반대
이런 개념의 차이는 파생 데이터를 생성하는 방식에 큰 영향을 미친다.
브로커가 확인 응답을 받으면 브로커에서 메시지를 삭제하기 때문에 이미 받은 메시지는 복구할 수 없다.

데이터베이스의 지속성 있는 저장 방법과 메시징 시스템의 지연 시간이 짧은 알림 기능을 조합할 수 는 없을까?
이것이 로그 기반 메시지 브로커(log-based message broker)의 기본 아이디어다.

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

로그는 단순히 디스크에 저장된 추가 전용 레코드의 연속
브로커를 구현할 때도 같은 구조를 사용한다. 생산자가 보낸 메시지는 로그 끝에 추가하고 소비자는 로그를 순차적으로 읽어 메시지를 받는다.

디스크 하나를 쓸 때보다 처리량을 높이기 위해 확장하는 방법으로 로그를 파티셔닝하는 방법이 있다. 다른 파티션은 다른 장비에서 서비스

각 파티션 내에서 브로커는 모든 메시지에 오프셋이라고 부르는 단조 증가하는 순번을 부여
단 다른 파티션 간 메시지의 순서는보장하지 않는다.

예시 :

  • 아파치 카프카(Apache Kafka)
  • 아마존 키네시스 스트림(Amazon Kinesis Stream)
  • 트위터의 분산 로그(DistributedLog)

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

로그 기반 접근법은 팬 아웃 메시징 방식을 제공
소비자가 서로 영향 없이 독립적으로 로그를 읽을 수 있고 메시지를 읽어도 로그에서 삭제되지 않기 때문

브로커는 소비자 그룹 간 로드 밸런싱하기 위해 소비자 그룹의 노드들에게 전체 파티션을 할당할 수 있다. 각 클라이언트는 할당된 파티션의 메시지를 모두 소비. 이런 거친 방식의 로드 밸 런싱 방법은 몇 가지 불리한 면이 있다.

  • 토픽 하나를 소비하는 작업을 공유하는 노드 수는 많아야 해당 토픽의 로그 파티션 수로 제한
  • 특정 메시지 처리가 느리면 파티션 내 후속 메시지 처리가 지연

즉, 메시지를 처리하는 비용이 비싸고 메시지 단위로 병렬화 처리하고 싶지만 메시지 순서는 그렇게 중요하지 않다면 JMS/AMQP 방식의 메시지 브로커가 적합하다.
반면 처리량이 많고 메시지를 처리하는 속도가 빠르지만 메시지 순서가 중요하다면 로그 기반 접근법이 효과적이다.

소비자 오프셋

파티션 하나를 순서대로 처리하면 메시지를 어디까지 처리했는지 알기 쉽다. 브로커는 모든 개별 메시지마다 보내는 확인 응답을 추적할 필요가 없이 주기적으로 소비자 오프셋을 기록하면 된다
이 방법을 이용하면 추적 오버헤드가 감소하고 일괄 처리와 파이프라이닝을 수행할 수 있는 기회를 제공해 로그 기반 시스템의 처리랑을 늘리는 데 도움을 준다.

로그 순차 번호(log sequence number)와 상당히 유사

디스크 공간 사용

로그를 계속 추가한다면 결국 디스크 공간을 전부 사용. 디스크 공간을 재사용하기 위해 실제로는 로그를 여러 조각으로 나누고 가끔 오래된 조각을 삭제하거나 보관 저장소로 이동.

소비자 처리 속도가 느려 메시지가 생산되는 속도를 따라잡지 못하면 소비자가 너무 뒤처져 소비자 오프셋이 이미 삭제한 조각을 가리킬 수도 있다. -> 형 버퍼(circular buffer) 또는 링 버퍼(ring buffer)

이 시스템은 큐가 작을 때는 빠르지만 디스크에 기록하기 시작하면 매우 느려진다.

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

메시지 버리기,버퍼링,배압 적용하기

로그 기반 접근법을 이 방식으로 분류하자면 대용량이지만 (가능한 디스크 공간으로 제한된) 고정 크기의 버퍼를 사용하는 버퍼링 형태. 어떤 소비자가 너무 뒤처져서 메시지를 잃기 시작해도 해당 소비자만 영향을 받고 다른 소비자들의 서비스를 망치지는 않는다.
이 동작은 전통적인 메시지 브로커와 대조적. 전통적 메시지 브로커는 소비자가 중단되면 그 소 비자가 사용하던 큐를 삭제해줘야 한다.그렇지 않으면 큐에 불필요한 메시지가 누적되고 여전히 활 성화된 소비자로부터 메모리를 계속 벳어가게 된다.

데이터베이스와 스트림

이종 데이터 시스템에서 발생하는 문제 한 가지를 먼저 살펴본 다음 이벤트 스트림의 아이디어를 데이터베이스에 적용해 이 문제를 해결하는 방법을 찾는다.

시스템 동기화 유지하기

대부분의 중요 애플리케이션이 요구사항을 만족하기 위해 몇 가지 다른 기술의 조합이 필요. 서로 동기화가 필수.

데이터 웨어하우스에서는 이 동기화 과정을 대개 ETL 과정에서 수행.

너무 느리면 대안으로 사용하는 방법으로 이중 기록(dual write) 사용 : 데이터가 변할 때마다 애플리케이션 코드에서 명시적으로 각 시스템에 기록
몇가지 문제 존재

  • 경쟁 조건 이슈 -> 데이터 불일치
  • 한쪽 쓰기가 성공할 때 다른 쪽 쓰기는 실패 이슈

색인용 인덱스를 데이터베이스의 팔로워로 만들어 실제로 리더 하나만 존재하게 한다면 상황은 훨
씬 낫다. 하지만 실제로 가능한 이야기일까?

변경 데이터 캡처

변경 데이터 캡처는 데이터베이스에 기록하는 모든 데이터의 변화를 관찰해 다른 시스템으로 데이터를 복제할 수 있는 형태로 추출하는 과정

변경 데이터 캡처는 본질적으로 변경 사항을 캡처할 데이터베이스 하나를 리더로 하고 나머지를 팔로워로 한다.

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

-> 로그 컴팩션(log compaction) : 저장 엔진은 주기적으로 같은 키의 로그 레코드를 찾아 중복을 제거하고 각 키에 대해 가장 최근에 갱신된 내용만 유지

파생 데이터 시스템을 재구축할 때마다 새 소비자는 컴팩션된 로그 토픽의 오프셋 0부터 시작해서 순차적으로 데이터베이스의 모든 키를 스캔하면 된다.

profile
백엔드 개발자입니다.

0개의 댓글