일괄 처리의 한계 및 스트림이 필요한 이유
일괄 처리는
- 사전에 입력을 유한한 크기로 한정한다.
- 데이터는 끊임없이 많기 때문에 일 단위 나 일정 기간씩 청크를 나눈다.
- 문제는 하루가 지나야 반영된다는 것이다.
대신 스트림은
고정된 시간 조각의 개념을 버리고 이벤트가 발생할 때마다 처리한다.
스트림 표현/저장/전송 하기
이벤트 스트림 생김새
- 하나의 레코드를 이벤트라고 한다.
- 작고 독립된 불변 객체이며 타임스탭프를 포함한다.
- 텍스트 문자열 or JSON or 이진 형태 등으로 부호화된다.
이벤트 전송
컨슈머 쪽에서 폴링하며 새 이벤트를 직접 가져올 수도 있지만 오버헤드가 커지기 때문에 프로듀서가 알려주는 편이 낫다.
메시징 시스템
- 이벤트 알림 전달 목적으로 개발된 발행/구독 모델
- 여러 프로듀서가 같은 토픽으로 전송할 수 있고, 여러 컨슈머가 토픽 하나에서 받아갈 수 있다.
메시지 브로커(메시지 큐)
어떻게 생겼냐면
- 프로듀서와 컨슈머가 직접 전달하면 메시지 읽어버릴수도 있기 때문에 중간에 브로커를 두는 시스템이다.
- 메시지 브로커는 서버이고, 프로듀서/컨슈머는 클라로 연결된다.
- 프로듀서는 브로커에 메시지를 전송하고 컨슈머는 브로커에서 메시지를 읽는다.
이렇게 하면 특징
- 프로듀서/컨슈머의 접속 해제과 장애 같은 문제에 쉽게 대처할 수 있다.
- 컨슈머 소비 속도가 느리더라도 브로커의 큐에 보관할 수 있다.
- 프로듀서는 비동기로 동작. 큐에만 넣어진거 확인하고 컨슈머가 처리했는진 확인하지 않음
메시지 브로커 VS. 데이터베이스
- DB는 삭제 명령 날리기 전까지 메시지를 보관한다. 브로커는 대부분 컨슈머가 소비 완료 시 삭제한다.
- 브로커는 메시지를 빨리 지우기 때문에 작업 집합이 작다 가정하고 큐 크기가 작다.
- DB 는 보조색인과 검색을 지원한다. 브로커는 토픽의 부분 집합을 구독하는 방식 (필요한 데이터를 가져간다는 관점에서)
- DB 는 클라가 데이터를 보려면 질의를 해야하며 데이터가 변한걸 확인하려면 재질의해야한다. 브로커는 데이터가 변하면 클라에게 알려준다.
→ JMS, AMQP 라는 표준으로 만들어짐. RabbitMQ, 구글 클라우드 Pub/Sub 같은 소프트웨어로 구현됨
복수 소비자
팬 아웃
모든 컨슈머에게 메시지가 동일하게 전달된다.
서로 간섭 없이 메시지를 가질 수 있다.
로드 밸런싱
- 한 토픽의 메시지를 여러 컨슈머가 분산해서 가짐
- =공유 구독, shared subscription
- 메시지 처리 비용이 비싸서 병렬화 할 때 유용하다.
- 근데 ack 를 못 받아 재전송을 다른 컨슈머로 하는 경우엔 메시지 순서가 꼬일 수 있는 문제가 있다.
로그 기반 메시지 브로커 (파티셔닝된 로그)
: DB의 지속성 있는 저장 방법과 메시징 시스템의 지연 시간 짧은 알림 기능을 조합한 메시지 브로커
- 프로듀서가 보낸 메시지는 로그 끝에 추가하고 컨슈머는 순차적으로 읽는다.
- 다른 파티션은 다른 장비에서 서비스할 수 있기 때문에 처리량을 확장할 수 있다.
- 각 파티션에 컨슈머가 어디까지 읽었는지를 오프셋으로 표현한다.
- 파티션 간 메시지 순서는 보장하지 않는다.
- 팬 아웃의 메시징 방식 제공
- ex. 아파치 카프카, 아마존 키네시스 스트림
즉, 메시지를 처리하는 비용이 비싸고 메시지 단위로 병렬화 처리하고 싶지만 순서는 그렇게 안 중요하면 JMS/AMQP 방식의 메시지 브로커가 적합하다.
반면 처리량이 많고 메시지 처리 속도가 빠르지만 순서가 중요하면 로그 기반 접근법이 효과적이다.
(엘스부) JMS/AMQP 방식의 메시지 브로커가 뭐지..? 각 특징은 로드밸런서랑 팬아웃의 특징같은데 이걸로 나뉜다는건가?
JMS/AMQP 방식의 메시지 브로커-RabbitMQ. RabbitMQ 는 컨슈머가 메시지 받아가면 바로 삭제하는게 메인이라고 한다. (남겨두는 설정도 있긴하지만)
프로듀서가 너무 빨라 컨슈머가 따라잡을 수 없을 땐?
세 가지 방법: 메시지 버리기, 버퍼링, 배압 적용
로그 기반 접근법(ex. 카프카) 는 버퍼링을 사용한다.
근데 버퍼가 꽉 차면 프로듀서는 가장 오래된 메시지를 삭제하고 채워야하는데, 컨슈머가 너무 느려 버퍼에 있는 가장 오래된 메시지도 처리 못하는 상황이라면?
사용자에게 미리 경고를 해서 사전에 처리할 수 있게 한다.
그래도 장점은 해당 소비자만 영향을 받기 때문에 개발, 테스트, 디버깅 등 다양한 목적으로 소비자를 만들 수 있다.
또 장점은 데이터를 소비해도 삭제 안 되고 남아있기 때문에 소비자 오프셋만 이동해서 데이터를 재처리할 수 있다는 것이다.
DB 와 스트림
“DB 에 뭔가 기록한다” 라는 행동도 저장하고 처리할 수 있는 하나의 이벤트이므로 로그 저장소 이상으로 스트림과 관련이 있다.
= 변경 데이터 캡쳐 (CDC)
CDC(Change Data Capture) 가 필요한 이유
상황: DB 에 기록 후 색인, 웨어하우스 같은 파생 데이터 시스템에 복제를 해야함. 두 클라가 동시에 요청한 상태
as-is 이중 기록
DB에 한 번, 색인에 한 번 이중 기록을 클라쪽에서 하면 동시 쓰기 시 시점이 꼬여 다른 값으로 저장될 수 있다.
혹은 DB는 쓰기 성공했으나 색인 쓰기가 실패해서 다른 값으로 저장될 수도 있다.
to-be CDC
DB 변경 내용을 로그 기반 메시지 브로커를 통해 스트림으로 소비자(파생 데이터 시스템)에게 제공.
DB 에 쓰인 결과를 보내므로 이중 기록 보다 동기화가 잘 된다.
CDC 는 비동기로 실행해 요청에 영향 없게 함 (대신 복제 지연 문제는 발생 가능)
파생시스템 초기 데이터 구축
풀텍스트 색인 같은 파생시스템의 초기 데이터 구축하는 법
- 초기 스냅숏
- 일관성 있는 스냅숏 사용 (리더의 일정 시점 스냅숑 복사 후 변경 사항 따라잡기)
- 로그 컴팩션
- 키 중복 제거된 최신 값만 복제
이벤트 소싱(Event Sourcing)
DDD 에서 나온 기법이지만 스트리밍 시스템 관련된 유용한 아이디어 포함한다.
CDC 와 유사하게 상태 변화를 모두 이벤트 로그로 저장한다.
차이점은
- CDC: DB 의 복제 로그같은 저수준으로 이벤트를 추출한다. 레코드 갱신, 삭제 가능
- 이벤트 소싱: 앺 수준에서 발생한 일이 이벤트이다. 불변 이벤트라 갱신, 삭제 불가 하고 추가만 가능하다.
명령과 이벤트 - 명령이 실행가능하다고 판단되면 이벤트로 전환된다.
불변 이벤트의 장점: 문제 상황 진단과 복구가 쉽다. 단점: 불변 히스토리가 매우 커질 수도 있다. 컴팩션, GC 성능 문제도 있다.
스트림으로 할 수 있는 일
1. 복잡한 이벤트 처리 (CEP, Complex Event Processing)
스트림에서 특정 이벤트를 패턴을 찾는 규칙을 규정한다. 스트림 이벤트가 들어오면 이 질의를 지나 흘러가면서 매칭되는 이벤트를 찾는다.
ex. Esper, Apama, SQLstream
2. 스트림 분석
대량의 이벤트를 집계하고 통계적 지표 뽑기
- 시간당 얼마나 자주 발생하는지
- 이동 평균
- 이전 시간과 현재의 추세 비교
일반적으로 고정된 시간 간격 기준으로 계산하며, 집계 시간 간격을 윈도우(window)라고 한다.
3. 구체화 뷰 유지하기
캐시, 검색 색인, 데이터웨어하우스 같이 원본과 동기화되논 파생 데이터 시스템, 즉 구체화 뷰를 유지하는데 쓰인다.
데이터셋에 대한 다른 뷰를 만들 수 있어 효율적으로 질의할 수 있다.
4. 스트림 상에서 검색하기
CEP 외에 전문 검색 질의 같은 복잡한 기준으로도 검색하는데 쓰인다.
질의를 먼저 저장하고 CEP 와 같이 문서는 질의를 지나가면서 실행된다.
5. 메시지 전달과 RPC
아파치 스톰의 분산 RPC 를 사용하면 이벤트 스트림을 처리하는 노드 집합에 질의를 맡길 수 있다. 입력 스트림에 질의 후 결과르르 취합해 사용자에게 돌려준다.
스트림 조인 3종류
① 스트림 스트림 조인 (윈도우 조인)
이벤트와 이벤트의 조인이다.
ex. 검색 결과에서 URL 클릭율을 알고 싶을 때, 세션ID 기준으로 검색 활동 이벤트와 클릭 활동 이벤트를 조인한다.
이벤트가 실시간으로 끊임없이 들어오므로 이벤트 간의 차가 1시간 이내인 것들만 대상 같은 시간 제한이 필요하고, 이 시간 간격을 윈도우라고 한다.
② 스트림 테이블 조인 (스트림 강화)
이벤트에 테이블의 부가 데이터를 더해 정보를 강화한다는 뜻이다.
ex. 들어오는 사용자 활동 이벤트와 사용자 프로필 DB 를 조인하여 입력은 활동 이벤트 스트림, 출력은프로필 정보가 추가된 활동 이벤트이다.
DB는 원격DB를 사용할 수도 있지만 스트림마다 조회하기엔 느리고 과부화를 줄 수 있어 로컬에서 스트림 처리자 내부에 DB 사본을 둔다.
DB 사본도 최신을 유지해야하므로 프로필이 수정되면 스트림 처리자는 로컬 복사본을 갱신한다.
③ 테이블 테이블 조인 (구체화 뷰 유지)
ex. 캐시 생성을 위해 테이블끼리 조인한다.
트위터에서 내가 홈 타임라인을 볼 때 팔로워들의 트윗을 보여줘야한다. 홈에 진입할 때마다 모든 팔로워의 트윗을 조회하는 건 비용이 많이 들므로 사용자들이 새로 트윗 보낼 때 그 사용자를 팔로잉 하는 사람들의 타임라인에 트윗을 추가하는 방향으로 간다.
그 때 트위터 테이블과 팔로워 테이블을 조인해 홈 타임라인에 나올 트윗 캐시를 조회한다.
내결함성
일괄 처리는 장애가 나면 재시도를 하여 내결함성(결함에 인내하는 성질)을 지닌다. 그렇게 하더라도 결과적으로 한 번(effectively-once) 실행되는 것 처럼 보이는 exactly-once semantics (정확히 한 번 시맨틱)효과가 나타난다. 입력 파일이 불변하고 작업이 성공이어야만 그 결과를 볼 수 있기 때문이다.
스트림에서는 어떻게 내결함을 지닐까?
마이크로 일괄 처리와 체크포인트
스트림을 쪼개 소형 일괄 처리 처럼 다룬다.
실패한 일괄 처리 출력(브로커에 메시지보낸거나, 이메일 보내거나) 을 지울 수는 없다.
원자적 커밋 재검토
모두 일어나거나 모두 일어나지 않게 해 처리가 성공했을 때만 모든 출력과 부수효과가 발생하게 한다. (푸시 알림, DB 쓰기 등)
멱등성
결국 목표는 처리 효과가 두번 일어나는 일 없이 안전하게 재처리하기 위해 실패한 태스크의 부분 출력을 버리는 것이다.
단조 증가 오프셋은 여러번 수행해도 결과가 똑같은 멱등성을 보장하지 않으므로 외부 DB 기록 시 기록할 오프셋을 포함한다. 그럼 재수행해도 들어오는 오프셋을 덮어쓰면 되므로 멱등성을 보장할 수 있다.
실패 후 상태 재구축
상태가 필요한 스트림 처리는 실패 후에도 해당 상태가 복구됨을 보장해야 한다.
ex. 카프카 스트림은 상태 복제 전용 카프카 토픽에 상태 변화를 보내서 복제한다.
레퍼런스
데이터 중심 애플리케이션 설계 11장
'SearchDeveloper > 데이터 중심 애플리케이션 설계' 카테고리의 다른 글
[12] 데이터 시스템의 미래 (끝) (0) | 2023.09.26 |
---|---|
[10] 일괄 처리 (0) | 2023.08.14 |
[9] 일관성과 합의 (0) | 2023.08.10 |
[8] 분산 시스템의 골칫거리 3가지 (0) | 2023.08.06 |
[7] 트랜잭션 (0) | 2023.07.22 |