일괄 처리의 문제점은 시간이 지나야 반영된다는 것인데, 이런 시간 지체를 줄이려면 좀더 자주 처리를 실행해야 한다.
고정된 시간 개념을 버리고 이벤트가 발생할 때마다 처리해야 하는데, 이 방법이 스트림 처리의 기본 개념이다
이벤트 스트림 전송
스트림 처리 문맥에서 레코드는 보통 이벤트라고 한다.
특정 시점에 일어난 사건에 대한 세부 사항을 포함하는 작고 독립된 불변 객체이다.
이벤트는 일반적으로 일기준 시계를 따르는 이벤트발생 타임스탬프를 포함한다
이벤트는 텍스트 문자열이나 JSON 또는 이진 형태 등으로 부호화 된다
생산자(발행자, 발송자)가 이벤트를 한번 만들면 해당 이벤트를 복수의 소비자(구독자, 수신자)가 처리할수 있다
파일 시스템에선 관련 레코드 집합을 파일 이름으로 식별하지만 스트림 시스템에서는 대개 토픽이나 스트림으로 관련 이벤트를 묶는다
메시징 시스템
새로운 이벤트를 소비자에게 알려주는 일반적인 방법은 메시징 시스템을 사용하는 것이다
생산자는 이벤트를 포함한 메시지를 전송하고, 소비자에게 전달된다.
메시징 시스템을 구축하는 가장 간단한 방법은 생산자-소비자 사이에 유닉스 파이프나 TCP 연결 같은 직접 통신 채널을 사용하는 방법이다.
대부분 메시징 시스템은 이 기본 모델을 확장한다.
메시징 시스템에선 몇가지 고려할 사항이 있다
- 소비자가 메시지를 처리하는 속도보다 생산자가 빠르게 메시지를 전송한다면?
- 메시지를 버리거나, 큐에 버퍼링 하거나 (배압, 흐름제어)
- 큐에 버퍼링 될때 큐 크기가 메모리보다 커지면 어떻게 될까?
- 시스템 중단, 디스크쓰기 등..
- 노드가 죽거나 일시적으로 오프라인이 된다면? 메시지 손실은?
생산자에서 소비자로 메시지를 직접 전달하기
많은 메시지 시스템은 중간 노드를 통하지 않고 생산자-소비자가 네트워크로 직접 통신한다
직접 메시징 시스템은 일반적으로 메시지가 유실될수 있다.
메시지 브로커
직접 메시징 시스템의 대안으로 많이 사용된다.
메시지 브로커는 메시지 스트림을 처리하는 데 최적화된 일종의 데이터베이스이다.
생산자는 브로커로 메시지를 전송하고 소비자는 브로커에서 메시지를 읽는다
브로커에 데이터가 모이기 때문에 클라이언트의 상태 변경에 쉽게 대처할수 있다.
소비자는 일반적으로 비동기로 동작한다.
생산자가 메시지를 보낼 때 생산자는 브로커가 메시지를 버퍼에 넣었는지만 확인하고 소비자를 기다리지 않는다.
메시지가 소비자로 전달되는 것은 떄로는 상당히 늦을 수도 있다.
복수 소비자
복수 소비자가 같은 토픽에서 메시지를 읽을땐 로드밸런싱, 팬아웃을 사용한다
- 로드밸런싱 : 각 메시지는 소비자 중 하나로 전달된다. 소비자들은 해당 토픽의 메시지를 처리하는 작업을 공유한다. 메시지 처리 비용이 비싸서 병렬화 할때 유용하다.
- 팬아웃 : 각 메시지는 모든 소비자에게 전달된다. 여러 독립적인 소비자가 메시지를 간섭없이 청취할수 있다.
확인 응답과 재전송
소비자는 언제라도 장애가 발생할수 있다.
메시지를 잃어버리지 않기 위해 메시지 브로커는 확인 응답을 사용한다.
클라이언트는 메시지 처리가 끝났을때 브로커가 메시지를 큐에서 제거할 수 있게 브로커에게 알려야 한다.
파티셔닝된 로그
데이터베이스의 지속성 있는 저장 방법과 메시징 시스템의 지연 시간이 짧은 알림 기능을 조합할수 없을까?
이것이 로그 기반 메시지 브로커의 기본 아이디어다
로그를 사용한 메시지 저장소
생산자가 보낸 메시지는 로그 끝에 추가하고 소비자는 로그를 순차적으로 읽어 메시지를 받는다.
소비자가 로그 끝에 도달하면 새 메시지가 추가됐다는 알림을 기다린다.
디스크 하나를 쓸 때보다 처리량을 높이기 위해 확장하는 방법으로 로그를 파티셔닝 하는 방법이 있다.
각 파티션은 다른 파티션과 독립적으로 읽고 쓰기가 가능한 분리된 로그가 된다.
각 파티션 내 브로커는 모든 메세지에 단조 증가하는 순번인 오프셋을 부여한다.
다른 파티션간 메시지 순서는 보장하지 않는다
로그 기반 접근법은 팬아웃 메시지 방식을 제공한다.
소비자가 서로 영향 없이 독립적으로 로그를 읽고 메시지를 읽어도 삭제되지 않는다.
디스크 공간 사용
로그를 계속 추가하면 결국 디스크 공간을 전부 사용하게 된다.
디스크 공간을 재사용하기 위해 로그를 여러 조각으로 나누고 가끔 오래된 조각을 삭제하거나 보관 저장소로 이동한다
로그는 크기가 제한된 버퍼로 구현하고 버퍼가 가득 차면 오래된 메시지 순서대로 버린다.
이런 버퍼를 원형 버퍼 또는 링 버퍼 라 한다.
소비자 처리 속도가 느려 메시지가 생산되는 속도를 따라잡지 못하면 소비자 오프셋이 이미 삭제한 조각을 가리킬 수도 있다.
데이터베이스와 스트림
이벤트는 특정 시점에 발생한 사건을 기록한 레코드다.
이벤트는 데이터베이스에 기록하는 것일 수도 있다.
시스템 동기화 유지하기
데이터베이스에 아이템 하나를 갱신하면, 캐시, 색인, 데이터 웨어하우스도 갱신해야 한다.
주기적으로 데이터베이스 전체를 덤프하는 작업이 느리면 대안으로 이중 기록을 사용할 수 있다.
이중기록은 데이터가 변할 때매다 애플리케이션에서 명시적으로 각 시스템에 기록하는 것이다.
이 경우 몇가지 심각한 문제가 있는데,
- 경쟁 조건
- 일부 쓰기가 실패 등이 있다.
변경 데이터 캡처
데이터베이스에 기록하는 모든 데이터 변화를 관찰해 다른 시스템으로 데이터를 복제하는 과정인 변경 데이터 캡처가 있다.
파생 데이터 시스템이 원본 시스템의 정확한 데이터 복제본을 가지게 하기 위해 발생하는 모든 변경 사항을 파생 데이터 시스템에 반영하는 것을 보장하는 메커니즘 이다.
변경 사항을 캡처할 데이터베이스 하나를 리더로 하고 나머지를 팔로워로 한다.
이는 메시지 브로커와 동일하기 비동기 방식으로 동작한다.
최근 데이터베이스들은 변경 스트림을 기본 인터페이스로 지원하기 시작했다.
이벤트 소싱
이벤트 소싱은 CDC와 유사하게 상태 변화를 모두 변경 이벤트 로그로 저장한다.
가장 큰 차이점은 아이디어를 적용하는 추상화 레벨이 다르다는 점이다.
- CDC
변경 로그는 데이터베이스에서 저수준으로 추출한다.
애플리케이션은 데이터베이스를 변경 가능한 방식으로 사용해 레코드를 자유롭게 갱신하고 삭제한다 - 이벤트 소싱
애플리케이션 로직은 이벤트 로그에 기록된 불변 이벤트를 기반으로 명시적으로 구축한다.
이벤트 저장은 추가만 가능하고 갱신이나 삭제는 권장하지 않는다.
저수준에서 상태 변경을 반영하는 것이 아니라 애플리케이션 수준에서 발생한 일을 반영하게끔 설계됐다.
이벤트 로그는 그 자체로는 유용하지 않다.
이벤트 소싱을 사용하는 애플리케이션은 시스템에 기록한 데이터를 표현한 이벤트 로그를 가져와
사용자에게 보여주기에 적당한 상태로 변환해야 한다.
명령과 이벤트
이벤트 소싱 철학은 이벤트와 명령을 구분한다.
사용자 요청이 처음 들어왔을 때 이는 명령이다. 이 시점에선 명령이 실패할 수도 있다.
무결성이 검증되고 명령이 승인되면 명령은 지속성 있는 불변 이벤트가 된다.
이벤트는 생성 시점에 사실이 된다.
불변 이벤트 로그에서 동일한 이벤트 로그로 다른 여러 읽기 전용 뷰를 만들 수 이싿.
데이터를 쓰는 형식과 읽는 형식을 분리해 다양한 읽기 뷰를 허용한다면 상당한 유연성을 얻을 수 있다.
이 개념을 명령과 질의 책임의 분리 라 한다
스트림 처리
스트림을 처리하는 방법에는 크게 세가지가 있다.
- 이벤트에서 데이터를 꺼내 저장소에 기록하고 다른 클라이언트가 해당 데이터를 질의한다.
- 이벤트를 사용자에게 직접 보낸다.
- 하나 이상의 입력 스트림을 처리해 하나 이상의 출력 스트림을 생성한다.
스트림을 처리해 다른 파생 스트림을 생산하는 연산자 혹은 작업 이라 불리는 몇가지 예를 살펴보자
- 복잡한 이벤트 처리 (complex event processing, CEP)
특정 이벤트 패턴을 검색하는 경우 적합하다.
CEP 는 정규 표현식과 유사하게 스트림에서 특정 이벤트 패턴을 찾는 규칙을 규정할수 있다. - 스트림 분석
대량의 이벤트를 집계하고 통계적 지표를 뽑는다.
ex) 특정 유형의 이벤트 빈도 측정, 특정 기간에 걸친 통계값 추출 등
스트림 조인
스트림에서도 조인이 필요한 경우가 있다.
하지만 새로운 이벤트가 언제든 나타날수 있는 특성상 일괄 처리보다 조인을 어렵게 만든다
스트림 스트림 조인
스트림 처리자가 상태를 유지해야 한다.
예를 들어 지난 시간에 발생한 모든 이벤트를 세션 ID 로 색인한다.
검색/클릭 이벤트가 발생할 때마다 해당 색인에 추가하고 같은 세션ID로 이미 도착한 다른 이벤트가 있는지 확인한다.
스트림 테이블 조인
스트림 처리는 한 번에 하나의 이벤트를 대상으로 데이터베이스에서 정보를 찾는다.
이는 데이터베이스에 부하를 줄수 있으므로, 스트림 처리자 내부에 데이터베이스 사본을 적재하는 방법도 있다.
스트림 처리는 오랜 시간 수행하기 때문에 시간이 흘러가며 로컬에 올린 데이터가 최신본이 아닐 수 있다.
따라서 스트림 처리자가 사용하는 복사본을 최신 상태로 유지해야 한다.
테이블 테이블 조인
?? 잘 이해가 안간다
내결함성
스트림 처리자는 어떻게 결함에 견딜수 있을까?
일괄 처리가 결함을 견디는 접근법은 일부 태스크가 실패할지라도 일괄 처리 작업 결과가 아무 문제 없는 결과와 동일함을 보장한다.
이 원리는 정확히 한번 시맨틱 이라 하지만 결과적으로 한번 이라는 용어가 더 적합하다.
스트림 처리에선 태스크가 완료될때까지 무한정 기다릴순 없다
마이크로 일괄 처리와 체크포인트
한가지 해결책은 스트림을 작은 블록으로 나누고 각 블록을 소형 일괄 처리와 같이 다루는 마이크로 일괄 처리다. (스파크 스트리밍에서 사용)
일괄 처리 크기가 작을수록 스케줄링과 코디네이션 비용이 커진다.
일괄 처리 크기가 클수록 스트림 처리 결과를 보기까지 지연시간이 길어진다.
아파치 플링크는 주기적으로 상태의 롤링 체크포인트를 생성하고 지속성 있는 저장소에 저장한다.
스트림 연산자에 장애가 발생하면 가장 최근 체크포인트에서 재시작하고 해당 체크포인트와 장애 발생 사이의 출력은 버린다.
그러나 이 방법만으론 출력이 스트림 처리자를 떠난 후에 대한건 보장할수 없다.
처리가 성공했을 때만 모든 출려과 이벤트 처리의 부수 효과가 발생하게 해야 한다.
이런 효과는 원자적으로 모두 일어나거나 모두 일어나지 않아야 한다.
스트림 처리 프레임워크 내에서 분산 트랜잭션을 위한 내부 기능을 지원한다.
가능하면 연산이 반복적으로 일어나도 한번 수행한 것과 같은 효과를 내는 연산인 멱등성에 의존하는 방법도 있다.
'책 정리와 리뷰' 카테고리의 다른 글
[테크 커리어] 책 리뷰 (0) | 2023.12.11 |
---|---|
[퓨처 셀프] 책 리뷰 (1) | 2023.11.27 |
[돈, 뜨겁게 사랑하고 차갑게 다루어라] 책 리뷰 (0) | 2023.10.12 |
[데이터중심 어플리케이션 설계] 10장 정리 (1) | 2023.10.09 |
[데이터중심 어플리케이션 설계] 9장 정리 (0) | 2023.10.03 |