같은 데이터를 보는 두 가지 방식. 스트림은 변경을 유발하는 이벤트의 연속이라 변경 내역을 저장하고, 테이블은 여러 상태 변경의 결과물인 현재 상태를 저장한다.
은행 계좌로 보면 명확하다. 입출금 내역이 스트림이고 잔액이 테이블이다. 둘은 다른 데이터가 아니라 하나를 다른 하나로 변환할 수 있는 관계다.
테이블을 스트림으로, 스트림을 테이블로
테이블을 스트림으로 바꾸려면 변경 내역을 잡아내면 된다. 모든 추가, 변경, 삭제 이벤트를 스트림에 저장한다. 많은 DB가 이것을 위한 CDC(Change Data Capture) 솔루션을 제공하고, 그 변경점을 카프카로 보내주는 커넥터가 많이 있다.
스트림을 테이블로 바꾸려면 스트림의 모든 변경 사항을 테이블에 적용한다. 이것을 스트림을 구체화한다(materialize)고 한다. 메모리든 내부 저장소든 외부 DB든 테이블을 만들고, 스트림의 이벤트를 처음부터 끝까지 읽어서 상태를 변경한다. 끝나면 특정 시점의 상태를 나타내는 테이블이 나온다.
카프카 스트림즈에서 KTable이 이것이다. 변경 스트림에 의해 갱신되는 구체화된 저장소다.
체인지 로그와 상태 복구
상태 복구가 이 이원성 위에 서 있다. 로컬 상태의 변경을 체인지 로그 토픽으로 보내두면 그것은 테이블을 스트림으로 바꾼 것이고, 장애가 나면 그 스트림을 처음부터 읽어 다시 테이블로 구체화한다. 로그 압착이 여기 정확히 맞물린다. 각 키의 최신 값만 남기므로 스트림의 크기가 테이블의 크기로 수렴하면서도 언제든 구체화할 수 있다.
인터랙티브 쿼리
구체화의 값어치는 조회에서도 드러난다. 처리 결과가 테이블 형태인 경우가 흔하고, 결과 스트림이 곧 테이블에 대한 업데이트 스트림이므로 결과 토픽을 읽는 것보다 애플리케이션의 상태에서 테이블을 바로 읽는 것이 훨씬 빠르고 쉽다. 카프카 스트림즈가 이 인터랙티브 쿼리를 위한 API를 제공한다.
조인
조인이 셋으로 갈린다.
스트림-테이블 조인
외부 검색을 대신한다. 데이터를 확장하려고 매 레코드마다 외부 DB를 조회하면 레코드당 5~15밀리초의 지연이 붙고, 처리량도 맞지 않는다. 스트림 처리 시스템은 초당 10만에서 50만 개를 처리하는데 DB는 초당 1만 개 가량이 보통이다. 그래서 DB의 데이터를 애플리케이션 안에 캐시해야 하는데, 캐시 관리가 다시 문제가 된다. 테이블의 모든 변경점을 CDC로 이벤트 스트림에 담으면 스트림 처리 작업이 그것으로 캐시를 갱신한다. 스트림 중 하나가 로컬에 캐시된 테이블에 대한 변경사항을 나타내는 것, 이것이 스트림-테이블 조인이다.
테이블-테이블 조인
윈도우 처리되지 않은 연산이다. 작업이 실행되는 시점의 양 테이블 현재 상태를 조인한다. 동일한 방식으로 파티션된 동일한 키를 가진 두 테이블에 대해 동등 조인을 할 수 있고, 그래서 조인 연산이 많은 인스턴스와 장비에 효율적으로 분산된다. 스트림즈는 한쪽의 키와 다른 쪽의 임의 필드를 잇는 외래 키 조인도 지원한다.
스트림-스트림 조인
여기가 다르다. 테이블에서는 현재 상태만 관심사라 과거 이벤트를 무시할 수 있는데, 두 스트림을 조인하면 같은 키의 이벤트를 같은 시간 윈도우 안에서 맞춰야 하므로 과거와 현재를 전체 조인하게 된다. 그래서 윈도우 조인이라고도 부르고, 스트림즈는 조인 윈도우를 RocksDB 상태 저장소에 유지한다. 이 조인이 요구하는 파티셔닝 조건은 조인이 만드는 제약에 적었다.