11장 스트림 처리
- ‘스트림’은 일반적으로 시간 흐름에 따라 점진적으로 생산된 데이터를 일컫는다.
- 일괄 처리와 달리 스트림은 데이터가 점진적으로 처리된다.
이벤트 스트림 전송
- 스트림 처리에서 입력은 보통 ‘이벤트’라고 한다.
- 특정 시점에 일어난 사건의 세부 사항
- 작고 독립된 불변 객체
- 이벤트 발생 타임스탬프를 포함
- 텍스트 문자열이나 JSON 또는 이진 형태 등으로 부호화된다.
- 이벤트 스트리밍에서도 한 번 기록한 이벤트를 여러 곳에서 읽을 수 있다.
- 생산자(producer)가 이벤트를 한 번 생성하면 여러 소비자(consumer)가 처리할 수 있다.
- 스트림 시스템에선 대개 토픽(topic)이나 스트림으로 관련 이벤트를 묶는다.
- 전통적 데이터베이스는 알림 메커니즘을 강력히 지원하지 않는다.
- 이론상으론 폴링을 사용하여 데이터베이스만 있다면 스트림 처리를 구현할 수 있지만 풀링은 비용이 크다.
- 트리거(trigger) 기능이 있긴 하지만 제한적이다.
메시징 시스템
- 메시징 시스템은 새로운 이벤트에 대해 소비자에게 알려주려고 쓰이는 방법이다.
- 다수의 생산자 노드가 같은 토픽으로 메시지 전송 가능
- 다수의 소비자 노드가 토픽 하나에서 메시지를 소비 가능
- 메시징 시스템에선 발행/구독(publish/subscribe) 모델을 사용한다.
- 많은 메시지 시스템은 중간 노드 없이 생산자와 소비자가 네트워크로 직접 통신한다.
- 낮은 지연이 필수인 주식 시장과 금융 산업에서 널리 사용된다. (UDP 멀티캐스트)
- ZeroMQ 같은 브로커가 필요 없는 메시징 라이브러리도 이 방식을 사용
- 메시지가 유실될 수도 있기에 애플리케이션 코드로 이를 보완해야하지만 상당히 제한적이다.
- 메시지 브로커(메시지 큐)
- 메시지 스트림 처리에 최적화된 데이터베이스의 일종이다.
- 생산자는 브로커로 메시지를 전송하고 소비자는 브로커에서 메시지를 읽는다.
- 브로커에 데이터가 모이기에 클라이언트(생산자/소비자)의 장애에 쉽게 대처 가능하다.
- 메시지를 메모리에만 보관할 수도 있지만 디스크에 메시지를 기록하는 브로커도 있다.
- 소비 속도가 느린 소비자가 있다면 일반적으로 큐가 제한 없이 계속 늘어난다.
- 소비자는 일반적으로 비동기로 동작한다.
- 메시지 브로커와 데이터베이스의 비교
- 데이터베이스와 달리 브로커 대부분은 소비자에게 배달 성공 후 자동으로 메시지를 삭제한다.
- 브로커 대부분은 메시지를 빨리 지우기에 큐 크기가 작다.
- 데이터베이스는 다양한 데이터 검색을 지원하는 반면 메시지 브로커는 특정 패턴과 부합하는 토픽의 부분 집합을 구독하는 방식을 지원한다.
- 메시지 브로커는 데이터베이스의 스냅샷 기반의 임의 질의를 하지는 않지만 데이터가 변하면 클라이언트에게 즉시 알려준다.
- 복수 소비자가 같은 토픽에서 메시지를 읽을 때 사용하는 주요 패턴 두 가지가 있다.
- 로드 밸런싱 - 소비자 중 하나로 전달
- 팬 아웃 - 모든 소비자에게 전달
- 확인 응답과 재전송
- 메시지 유실을 막기 위해 메시지 브로커는 확인 응답을 사용한다.
- 클라이언트는 메시지 처리가 끝나고 브로커가 큐에서 메시지를 제거할 수 있게 브로커에게 명시적으로 알림
- 브로커가 확인 응답을 받지 않으면 메시지가 처리되지 않았다고 가정하고 다른 소비자에게 다시 전송한다.
- 메시지 재전송으로 인해 메시지 순서가 순서대로 처리되지 않을 수도 있다.
- 소비자마다 독립된 큐를 사용하면 순서가 꼬이는 문제를 피할 수는 있다.
파티셔닝된 로그
- 일반적인 데이터베이스와 메시징 시스템은 데이터에 대한 접근법이 다르다.
- 데이터베이스나 파일 시스템은 기본적으로 모든 데이터를 명시적 삭제 이전까진 영구 보존한다.
- 메시징 시스템은 소비자에게 메시지를 전달 후 즉시 삭제하기에 한 번 지나간 메시지는 복구할 수 없다.
- 로그 기반 메시지 브로커(log-based message broker)
- 데이터베이스의 지속성과 메시징 시스템의 지연이 짧은 알림 기능을 조합한 것
- 생산자가 보낸 메시지를 로그 끝에 추가하고 소비자는 순차적으로 메시지를 받는다.
- ex) 아파치 카프카(Apache Kafka), 아마존 키네시스 스트림(Amazon Kinesis Stream) 등
- 로그 기반 메시징 시스템은 처리량을 높이기 위해 로그를 파티셔닝한다.
- 각 파티션은 다른 파티션과 독립적으로 읽고 쓰이는 분리된 로그가 된다.
- 토픽은 같은 형식의 메시지를 전달하는 파티션들의 그룹으로 정의한다.
- 각 파티션에는 모든 메시지에 오프셋이라는 단조 증가하는 순번을 부여하여 순서를 보장한다.
- 로그 방식과 전통적 메시징 방식의 비교
- 로그 기반 접근법
- 팬 아웃 방식을 제공해 각 소비자가 독립적으로 로그를 읽을 수 있게 할 수 있다.
- 메시지를 읽어도 로그에서 삭제되지 않는다.
- 소비자 그룹의 노드들에게 전체 파티션을 할당해 로드 밸런싱을 수행할 수 있다.
- 메시지 처리 속도가 빠르고 메시지 순서가 중요한 경우 효과적이다.
- 단 메시지 처리 비용이 상대적으로 높을 수 있다.
- 전통적 메시징 방식
- 메시지 순서가 중요하지 않은 경우 적합할 수 있다.
- 메시지 처리 속도가 상대적으로 느릴 수 있다.
- 로그를 계속 추가하다보면 결국 디스크 공간에 부담이 간다.
- 디스크 공간 재사용을 위해 로그를 여러 조각으로 나누고 가끔 오래된 조각을 삭제하거나 이동시킨다.
- 소비자 처리 속도가 너무 느린 경우 소비자 오프셋이 이미 삭제한 로그 조각을 가리킬 수 있어 메시지가 유실될 수 있다.
- 그래도 로그는 일반적으로 하드디스크 버퍼에 수 일에서 수주간 메시지를 보관할 수 있다.
- 로그 기반 메시징 시스템은 소비자가 생산자를 따라갈 수 없을 때 대용량 고정 크기의 버퍼링을 통해 대응한다.
- 로그는 크기가 제한된 버퍼로 구현하고 버퍼가 가득 차면 오래된 메시지 순서대로 버린다. (링 버퍼, 원형 버퍼)
- 어떤 소비자가 너무 뒤쳐져 메시지를 잃기 시작해도 해당 소비자만 영향 받고 다른 소비자들 서비스는 영향 받지 않는다.
- 확인 응답을 받아야했던 전통적 메시징 시스템과 달리 로그 기반 방식은 읽기 전용 연산으로 메시지를 읽는다.
- 메시지 처리의 유일한 부수 효과는 소비자 오프셋 이동이지만 오프셋 관리는 소비자 관리 아래 있어 관리가 용이하다.
데이터베이스와 스트림
시스템 동기화 유지하기
- 대부분 애플리케이션은 요구사항 만족을 위해 여러 기술의 조합이 필요한데 여러 데이터 저장소의 동기화는 필수다.
- ex) 데이터베이스를 갱신하면 캐시와 색인과 데이터 웨어하우스도 갱신해야 함
- 이중 기록(dual write)
- 데이터가 변할 때마다 애플리케이션 코드에서 명식적으로 각 시스템에 기록
- 이중 기록은 몇 가지 문제가 존재한다.
- 동시 쓰기 문제가 발생
- 내결함성 문제로 시스템 간 불일치가 발생할 수 있음
- 원자적 커밋 문제
변경 데이터 캡쳐
- 변경 데이터 캡쳐 (change data capture, CDC)
- 데이터베이스 기록의 모든 변화를 관찰해 다른 시스템으로 복제할 수 있는 형태로 추출하는 과정이다.
- 데이터베이스 변경사항을 실시간 스트림으로 제공
- CDC의 구현
- 캡쳐할 데이터베이스 하나를 리더로, 나머지를 팔로워로 지정
- 로그 기반 메시지 브로커는 원본 데이터베이스에서 변경 이벤트를 전송하기에 적합하다. (순서를 유지하기 때문)
- 데이터베이스 트리거를 사용할 수도 있지만 고장 나기 쉽고 성능 오버헤드가 상당하다.
- CDC는 비동기 방색으로 동작하기에 데이터베이스 변경 사항을 커미샇기 전에 소비자에게 적용될 때까지 기다리지 않는다.
- 일부 CDC 도구는 스냅숏 기능을 내장하고 있으나 수작업으로 진행해야 하는 CDC 도구도 있다.
- 데이터베이스의 모든 변경 로그를 통해 로그를 재현해 데이터베이스 전체 상태를 재구축할 수 있다.
- 대부분의 변경 사항을 영구적으로 보관할 순 없기에 일관성 있는 스냅숏이 필요하다.
- 데이터베이스 스냅숏은 변경 로그의 위치나 오프셋에 대응되어야 스냅숏 이후 변경에 대응할 수 있다.
- 로그 컴팩션
- 새로운 파생 데이터 시스템을 추가할 때마다 스냅숏을 만들어야 하는 번거로움을 해결하기 위한 대안
- 주기적으로 같은 키의 로그 레코드를 찾아 중복을 제거해 가장 최근 갱신 내용만 유지
- 컴팩션과 병합 과정은 백그라운드로 실행된다.
- CDC 시스템에서도 모든 변경에 기본키가 포함되게 하고 키의 모든 갱신이 해당 키의 이전 값을 교체한다면 특정 키에 대해 최신 쓰기만 유지하면 충분하다.
- 즉 CDC 원본 데이터베이스의 스냅숏 없이도 전체 복사본을 얻을 수 있다.
- 아파치 카프카는 로그 컴팩션 기능을 제공한다.
- 변경 스트림용 API 지원
- 리싱크DB(RethinkDB) - 질의 결과에 변경이 있을 때 알림을 받도록 구독이 가능한 질의를 지원
- 파이어베이스와 카우치 DB(CouchDB) - 애플리케이션에도 사용 가능한 변경 피드 기반 데이터 동기화를 지원
- 미티어(Meteor) - 몽고DB의 oplog를 사용해 데이터 변경사항을 구독하거나 사용자 인터페이스를 갱신
- 카프카 커넥트(Kafka Connect)
- 카프카를 광범위한 데이터 시스템용 변경 데이터 캡쳐 도구로 활용 가능
- 파생 데이터 시스템 갱신에 사용 가능하다.
- 스트림 처리 시스템에도 이벤트 공급이 가능
이벤트 소싱
- 이벤트 소싱(event sourcing)
- 도메인 주도 설계에서 개발한 기법
- 애플리케이션 상태 변화를 모두 변경 이벤트 로그로 저장한다.
- 이벤트 소싱과 변경 데이터 캡쳐는 유사하지만 아이디어를 적용하는 추상화 레벨이 다르다.
- 변경 데이터 캡쳐
- 데이터베이스를 변경 가능한 방식으로 사용해 레코드를 자유롭게 갱신하고 삭제한다.
- 변경 로그는 데이터베이스에서 저수준으로 추출한다.
- 애플리케이션은 데이터베이스가 CDC가 실행 중인지 알 필요가 없다.
- 이벤트 소싱
- 애플리케이션 로직이 이벤트 로그에 기록된 불변 이벤트를 기반으로 명시적으로 구축한다.
- 이벤트 저장은 단지 추가만 가능하고 갱신/삭제는 권장하지 않는다.
- 애플리케이션 수준에서 상태 변경을 반영한다.
- 이벤트 소싱은 데이터 모델링에 쓸 수 있는 강력한 기법이다.
- 어떤 상황 발생 후 상황 파악이 쉽고 디버깅에 도움이 된다.
- 애플리케이션을 지속해서 개선하기가 매우 유리한 것
- 이벤트 로그에서 현재 상태 파생하기
- 이벤트 로그 자체는 그렇게 유용하지 않기에 이벤트 로그를 가져와 사용자에게 보여주기 위한 애플리케이션 상태로 변환해야 한다.
- 변환 과정은 재수행해도 동일한 상태를 만드는 결정적 과정이 되어야 한다.
- 이벤트 소싱은 상위 수준에서 모델링하기에 CDC에서처럼 로그 컴팩션을 할 수 없다.
- 이벤트 소싱은 보통 로그에서 파생된 상태의 스냅숏을 저장하는 메커니즘으로 모든 로그를 반복해서 재처리하는 부하를 막는다.
- 스냅숏이 있더라도 이는 읽기 성능 최적화일 뿐, 모든 원시 이벤트를 영구히 저장한다.
- 이벤트 소싱 철학은 이벤트와 명령(command)을 구분하는 데 주의한다.
- 사용자 요청이 처음 도착했을 때 이 요청은 명령이다.
- 애플리케이션이 명령의 무결성을 검증하여 명령이 승인되면 명령은 지속성 있는 불변 이벤트가 된다.
- 이벤트는 생성 시점에 사실(fact)이 되어 나중에 데이터가 변경되더라도 그 시점에 해당 이벤트가 발생했다는 사실은 여전히 진실이다.
상태와 스트림 그리고 불변성
- 일괄 처리에서 입력 파일이 주는 불변성은 큰 장점이었지만 갱신과 삭제를 지원하는 데이터베이스는 불변성과 어떻게 어울릴 수 있을까?
- 모든 변경 로그(changelog)는 시간이 지남에 따라 바뀌는 상태를 나타낸다.
- 상태가 어떻게 바뀌었든 변화를 일으킨 일련의 이벤트들은 사건이 취소되더라도 엄연한 사실이다.
- 변경 로그를 지속성 있게 저장한다면 상태를 간단히 재생성할 수 있다.
- 불변 이벤트의 장점
- 불변 이벤트를 쌓다가 실수가 발생해도 실수를 보완하는 로그를 추가하면 된다.
- 쇼핑 사이트 등에서 고객이 특정 항목을 구매하려 했다가 취소하면 해당 로그는 모두 남기에 유용한 정보로 사용할 수 있다.
- 불변 이벤트 로그에서 가변 상태를 분리하면 동일 이벤트로 여러 읽기 뷰를 만들 수 있다.
- 기존 데이터를 새롭게 표현하는 새 기능을 추가하려면 이벤트 로그를 통해 최적화된 뷰를 구축할 수 있다.
- 기존 시스템을 수정할 필요가 없다.
- 명령과 질의의 책임 분리(CQRS)를 통해 상당한 유연성을 얻을 수 있다.
- 질의를 받게 될 형식과 같은 형식으로 데이터 스키마를 설계해야한다는 전통적인 접근법과는 다른 것
- 동시성 제어
- 이벤트 소싱과 CDC의 가장 큰 단점은 로그의 소비가 대개 비동기로 이루어져 기록이 읽기에 바로 반영되지 않을 수 있다는 것이다.
- 반면 이벤트 소싱은 동시성 제어가 단순해진다.
- 사용자 동작은 이벤트 로그 추가로만 표현되어 원자적으로 만들기 쉽다.
- 불변성의 한계
- 매우 빈번히 갱신과 삭제를 하는 데이터는 불변 히스토리가 너무 커지거나 파편화 문제가 발생할 수도 있다.
- 컴팩션, 가비지 컬렉션의 성능 문제도 상당할 것이다.
- 관리상의 이유로 특정 데이터를 완전히 삭제할 필요가 있을 때 (ex. 개인 정보) 이벤트를 추가한다고 해결되지 않는다.
- 여러 곳에 흩어진 모든 이벤트 로그를 삭제하는 작업은 매우 어렵다.
스트림 처리
- 스트림을 처리하는 방법에는 크게 세 가지가 있다.
- 이벤트에서 데이터를 꺼내 다른 저장소 시스템에 기록하고 다른 클라이언트가 이 시스템에 해당 데이터를 질의
- 이벤트를 사용자에게 직접 보낸다. 이 경우 사람이 스트림의 최종 소비자가 된다. (ex. 이메일 전송)
- 하나 이상의 입력 스트림을 처리해 하나 이상의 출력 스트림을 생성
- 지금부턴 스트림으로 다른 파생 스트림을 생산하는 3번 방법에 대해 설명한다.
- 일괄 처리 작업과 크게 다른 점은 스트림은 끝나지 않는다는 점
스트림 처리의 사용
- 복잡한 이벤트 처리 (complex event processing, CEP)
- 특정 이벤트 패턴을 검색해야 하는 애플리케이션에 적합
- 정규식처럼 스트림에서 특정 이벤트 패턴을 찾는 규칙을 규정할 수 있다.
- 규칙을 정할 때 SQL 같은 고수준 언어나 그래픽 사용자 인터페이스를 사용하기도 한다.
- CEP 엔진은 질의(규칙)를 저장하고 입력 스트림으로부터의 이벤트에서 이벤트 패턴에 매칭되는 질의를 찾는다.
- CEP 구현에는 에스퍼(Esper), IBM 인포스피어 스트림, 아파마(Apama), SQL 스트림 등이 있다.
- 스트림 분석
- 분석은 CEP와 비슷하지만 대량의 이벤트를 집계하고 통계 지표를 뽑는 것을 더 우선한다.
- 특정 유형의 이벤트 빈도를 측정
- 특정 기간에 걸친 값의 이동 평균 계산
- 이전 시간 간격과 현재 통계값의 비교 (추세 감지)
- 아파치 스톰(Apache Storm), 스파크 스트리밍(Spark Streaming), 쌈자(Samza), 카프카 스트림(Kafka Sttreams) 등이 분석 용도로 설계되었다.
- 구체화 뷰 유지하기
- 스트림을 통해 캐시, 검색 색인, 데이터 웨어하우스 같은 파생 데이터 시스템에 원본의 최신 내용을 따라잡게 하는 데 쓸 수 있다.
- 쌈자와 카프카 스트림은 카프카 로그 컴팩션 지원을 기반으로 구체화 뷰 유지 용도로 사용할 수 있다.
- 스트림 상에서 검색하기
- CEP 외에도 전문 검색 질의와 같은 복잡한 기준을 기반으로 이벤트를 검색해야 하는 경우도 있다.
- 스트림 검색은 질의를 먼저 저장하고 문서는 질의를 지나가면서 실행된다.
- 먼저 문서를 색인하고 색인을 통해 질의를 실행하는 전통적 검색 엔진과는 다르다.
- 메시지 전달 시스템을 RPC 대안으로 사용할 수 있다.
- 이런 시스템은 메시지/이벤트에 기반을 두지만 일반적으로 이것들을 스트림 처리자로 생각하진 않는다.
- 액터 프레임워크는 주로 동시성을 관리하고 통신 모듈을 분산 실행하는 메커니즘이지만 스트림 처리는 기본적으로 데이터 관리 기법이다.
- 액터 간 통신은 주로 단기적/일대일인 반면 이벤트 로그는 지속적/다중 구독이 가능
- 액터는 임의 방식(순환/응답 패턴)이 가능하지만 스트림 처리자는 대개 비순환 파이프라인에 설정된다.
- 아파치 스톰에 분산 RPC 기능이 있어 이벤트 스트림을 처리하는 노드 집합에 질의를 맡길 수 있다.
시간에 관한 추론
- 스트림 처리에서 시간은 매우 중요한 요소인데 특히 분석 목적으로 사용하는 경우에 그렇다.
- 주로 ‘지난 5분 동안 평균’ 같은 시간 윈도우를 자주 사용
- 윈도우 - 이벤트 수를 세거나 윈도우 내 평균 값을 구하는 등 집계를 할 때 사용
- 이벤트 시간 대 처리 시간
- 이벤트 발생 시간과 실제 처리 시간 사이에 지연이 있으면 문제가 될 수 있다.
- 처리가 지연되는 데는 큐 대기, 네트워크 결함, 경쟁 상태, 스트림 소비자의 재시작, 결함에서의 복구 등 원인은 다양하다.
- 메시지가 지연되면 메시지 순서를 예측하지 못할 수도 있다.
- 이벤트 시간 기준으로 윈도우 정의 시 특정 윈도우에서 모든 이벤트가 다 도착했다거나 계속 들어오고 있는지를 확신할 수 없다.
- ex) 분당 요청 수를 세기 위해 1분 윈도우에 이벤트를 그룹화한다고 했을 때 특중 n분 윈도우는 언제 종료를 선언하고 카운트 값을 출력해야 할까
- 타임아웃을 설정하고 종료를 선언할 수 있지만 지연 때문에 어딘가에서 버퍼링됐을 가능성도 존재한다.
- 이러한 낙오자 이벤트를 처리하는 두 가지 방법이 있다.
- 낙오자 이벤트는 대체로 적은 비율을 차지하기에 무시한다.
- 낙오자 이벤트가 포함된 윈도우를 기준으로 갱신된 값으로 수정 값을 발행한다.
- 이벤트가 시스템의 여러 지점에 버퍼링됐을 때 이벤트에 타임 스탬프를 할당하는 것은 더 어렵다.
- 따라서 스트림 처리에서 시계를 조정하는 방법으로 세 가지 타임스탬프를 로그로 남기는 것이다.
- 이벤트 발생 시간: 실제 이벤트 발생 시간
- 이벤트를 서버로 보낸 시간
- 서버에서 이벤트를 받은 시간
- 두 번째와 세 번째 타임스탬프 차이를 구하면 장치 시계와 서버 시계 간의 오프셋을 추정할 수 있다.
- 윈도우 유형
- 텀플링 윈도우 (Tumbling Window)
- 고정된 크기의 시간 간격으로 데이터를 나눈다.
- 윈도우 간에 겹치지 않고 각 이벤트는 정확히 한 윈도우에 속한다.
- ex) 5분마다 데이터를 집계하는 경우
- 홉핑 윈도우 (Hopping Windo
- 고정된 크기의 윈도우가 일정 간격으로 이동한다.
- 윈도우 간에 겹침이 있을 수 있어 한 이벤트가 여러 윈도우에 포함될 수 있다.
- ex) 10분 길이의 윈도우를 5분마다 생성
- 슬라이딩 윈도우 (Sliding Window)
- 고정된 크기위 윈도우가 연속적으로 이동한다.
- 새로운 이벤트가 도착할 때마다 윈도우가 업데이트된다.
- 실시간 분석에 적합하다.
- ex) 최근 5분 동안의 데이터를 지속적으로 분석하는 경우
- 세션 윈도우 (Session Window)
- 고정된 크기가 없으며 이벤트 활동을 기반으로 동적으로 생성된다.
- 일정 시간 동안 이벤트가 없으면 세션이 종료된다.
- 사용자 활동 분석에 유용하다.
- ex)웹 사이트 방문자의 세션 분석
스트림 조인
- 스트림 상에선 새로운 이벤트가 언제든 나타날 수 있기에 일괄 처리보다 조인 처리가 더 어렵다.
- 스트림 스트림 조인(윈도우 조인)
- 2개의 실시간 데이터 스트림을 결합하여 새로운 스트림을 생성하는 기술
- 정의된 시간 윈도우 내에서 조인이 이루어진다.
- 조인을 위해 스트림 처리자가 일정 시간 상태를 유지한다.
- ex) 웹사이트에 검색 기능이 있고 거기서 검색된 결과의 클릭률을 파악하고자 할 때
- 지난 시간에 발생한 모든 이벤트를 세션 ID로 색인
- 검색 이벤트나 클릭 이벤트 발생할 때마다 해당 색인에 추가
- 스트림 처리자는 같은 세션 ID로 도착한 이미 다른 이벤트가 있는지 확인
- 이벤트가 매칭되면 검색한 결과를 클릭했다는 이벤트를 방출
- 검색 이벤트가 클릭 이벤트 없이 만료되면 클릭되지 않았다 이벤트를 방출
- 스트림 테이블 조인(스트림 강화)
- 실시간 이벤트 스트림에 정적인 데이터베이스 정보를 결합하여 이벤트를 강화하는 기법
- ex) 사용자 활동 이벤트 스트림이 있고 스트림 처리자가 지속적으로 이벤트를 받을 때마다 데이터베이스에서 추가적인 사용자 정보를 조회해 이벤트에 추가
- 다만 매번 데이터베이스에 원격 질의를 하면 과부하를 줄 위험이 있다.
- 네트워크 왕복 없이 로컬에 데이터베이스 사본을 두는 방법이 있지만 로컬 사본을 주기적으로 최신화 해야 하는 과제가 있다. (CDC 기법으로 최신화)
- 테이블 테이블 조인(구체화 뷰 유지)
- 두 개의 변경 가능한 테이블을 실시간으로 조인하는 방법
- 조인된 결과는 새로운 변경 스트림으로 출력되는데 ‘구체화 뷰’로 볼 수 있다.
- ex) 트위터에서 사용자가 자신의 홈 타임라인을 볼 때 팔로우한 사람들의 모든 트윗을 봐야하는 경우
- 트윗 생성/삭제에 따라 모든 팔로워 타임라인에 트윗을 추가해야 함
- 팔로우/언팔로우에 따라 해당 사용자의 최근 트윗을 타임라인에 추가 또는 제거해야 함
- 매번 데이터를 병합할 수 없기에 캐시를 유지해야 한다.
- 캐시를 유지하려면 트윗 이벤트 스트림과 팔로우 관계 이벤트 스트림이 필요하다.
- 한쪽 데이터 스트림에 변경이 발생할 때마다 해당 레코드와 관련된 다른 데이터를 조회하여 조인
- 조인의 시간 의존성
- 세 가지 조인 방식의 공통점
- 하나의 조인 입력을 기반으로 한 특정 상태를 유지해야 함
- 다른 조인 입력에서 온 메시지에 그 상태를 질의
- 복수 개의 스트림에 걸친 이벤트 스트림에 순서가 결정되지 않으면 조인도 비 결정적이다.
- 이런 문제를 데이터 웨어하우스에선 천천히 변하는 차원(slowly changing dimension, SCD)라 한다.
- 조인되는 레코드에 유일 식별자를 사용해 순서를 정할 수 있지만 레코드의 모든 버전을 보유해야 하기에 로그 컴팩션이 불가능하다.
내결함성
- 일괄 처리의 경우에는 일부 태스크가 실패해도 일괄 처리 작업 결과가 결국은 동일 입력으로부터 동일 결과를 보장한다.
- 정확히 한 번 시맨틱(exactly-once semantics)
- 결과적으로 한 번(effectively-once)
- 마이크로 일괄 처리
- 스트림을 작은 블록으로 나누고 각 블록을 소형 일괄 처리와 같이 다루는 방법
- 일괄 처리 크기와 같은 텀블링 윈도우를 암묵적으로 지원한다.
- 아파치 플링크는 주기적으로 상태의 롤링 체크포인트를 생성하고 장애 발생 시 스트림 연산자는 가장 최근 체크포인트에서 재시작 하는 방법을 사용한다.
- 마이크로 일괄 처리와 체크포인트는 일괄 처리처럼 정확히 한 번 시맨틱을 지원한다.
- 그러나 출력이 스트림 처리자를 떠나면 스트림 처리 프레임워크는 실패한 일괄 처리 출력을 지울 수 없고 실패 태스크를 재시작하면 부수 효과가 두 번 발생하는 한계가 있다.
- 장애 발생 시 정확히 한 번 처리되는 것처럼 보이려면 처리가 성공했을 때만 모든 출력과 부수 효과가 발생하게 해야 한다.
- 원자적으로 일어나거나 또 모두 일어나지 않아야 하고 동기화가 깨지면 안 된다.
- 두 번 이상 처리되는 것을 막기 위해 멱등성(idempotence)에 의존할 수 있다.
- 멱등 연산은 여러 번 수행해도 오직 한 번 수행한 것과 같은 효과를 내는 연산이다.
- 연산 자체가 멱등하지 않아도 여분 메타데이터로 멱등적으로 만들 수도 있다.
- ex) 카프카로부터 메시지를 소비할 때 단조 증가하는 오프셋을 사용해 같은 갱신이 수행되는 것을 막는다.
- 상태가 필요한 스트림 처리는 실패 후에도 해당 상태가 복구됨을 보장해야 한다.
- 원격 데이터 저장소에 상태를 유지하고 복제하는 방법이 있지만 성능이 좋지 않다.
- 스트림 처리자의 로컬에 상태를 유지하고 주기적으로 복제하면 성능은 좋지만 최신화에 유의해야 한다.
- ex) 플링크는 주기적으로 연산자 상태 스냅숏을 캡쳐해 HDFS 같은 저장소에 기록한다.
- ex) 카프카 스트림은 로그 컴팩션을 사용하는 상태 복제 전용 카프카 토픽에 상태 변화를 보내 복제한다.
- 입력 스트림을 통해 상태를 재구축할 수 있는 경우엔 상태 복제가 필요 없을 수 있다.
- 작은 크기의 윈도우를 집계해 만든 상태라면 이벤트를 재생해도 충분히 빠르다.
- 이 모든 트레이드 오프는 기반 인프라 성능 특성에 달려있다.