스트림 처리란 무엇인가 — 무한 데이터셋과 세 가지 프로그래밍 패러다임
Kafka: The Definitive Guide (O'Reilly, 2017, 1st Edition) — Neha Narkhede, Gwen Shapira, Todd Palino Chapter 11: Stream Processing — 도입부 / What Is Stream Processing? (pp.247-250)
'스트림 처리'라는 말은 구현 세부사항, 성능 요구, 데이터 모델이 뒤섞여 오해되기 쉽다. 이 모듈은 데이터 스트림을 '무한(unbounded) 데이터셋의 추상화'로 정의하는 데서 출발해, 이벤트 스트림이 가진 세 가지 속성(순서, 불변성, 재생 가능성)과 그것이 데이터베이스 테이블과 어떻게 다른지를 정리한다. 이어서 request-response, batch, stream processing 세 패러다임을 지연·처리량·차단 여부 축에서 비교해, 스트림 처리가 소프트웨어 아키텍처에서 어느 자리를 차지하는지 밝힌다.
Kafka가 스트림 처리를 가능하게 한 배경
Kafka는 전통적으로 강력한 메시지 버스로 여겨졌다. 이벤트 스트림을 전달할 수는 있지만 처리나 변환 기능은 없는 시스템이라는 인식이었다. 그런데 바로 그 신뢰성 있는 스트림 전달 능력 덕분에 Kafka는 스트림 처리 시스템에게 완벽한 데이터 소스가 되었다. Apache Storm, Apache Spark Streaming, Apache Flink, Apache Samza를 비롯한 여러 스트림 처리 시스템이 Kafka를 사실상 유일한 신뢰 가능한 데이터 소스로 두고 만들어졌다.
일부 업계 분석가는 이 시스템들이 20년 전부터 있던 복합 이벤트 처리(CEP, complex event processing) 시스템과 다를 바 없다고 말한다. 책의 견해는 다르다. 스트림 처리가 인기를 얻은 것은 Kafka 이후에 만들어졌고 따라서 Kafka를 신뢰 가능한 이벤트 스트림 소스로 쓸 수 있었기 때문이라는 것이다. Kafka가 단순 메시지 버스에서 데이터 통합 시스템으로 자리를 넓히면서, 많은 회사가 '흥미로운 데이터의 스트림이 오랜 기간 저장되어 있고 완벽하게 정렬되어 있으며, 그저 처리해 줄 프레임워크가 나타나기를 기다리는' 상태가 되었다. 데이터베이스가 발명되기 전에 데이터 처리가 훨씬 어려웠던 것처럼, 스트림 처리도 스트림 처리 플랫폼이 없어서 발이 묶여 있었던 셈이다.
버전 0.10.0부터 Kafka는 신뢰 가능한 데이터 소스 제공을 넘어, 클라이언트 라이브러리 모음의 일부로 강력한 스트림 처리 라이브러리를 함께 담기 시작했다. 개발자는 외부 처리 프레임워크에 의존하지 않고 자기 애플리케이션 안에서 이벤트를 소비하고 처리하고 생산할 수 있게 되었다.
핵심 포인트
- Kafka의 신뢰성 있는 스트림 전달이 여러 스트림 처리 프레임워크의 공통 데이터 소스가 되었다
- 스트림 처리의 확산은 CEP의 재탕이 아니라 '재생 가능한 이벤트 저장소'가 생긴 결과라는 것이 책의 관점
- 0.10.0부터 Kafka는 클라이언트 라이브러리의 일부로 스트림 처리 라이브러리(Kafka Streams)를 제공한다
데이터 스트림 = 무한 데이터셋의 추상화
데이터 스트림(이벤트 스트림, 스트리밍 데이터)이란 무엇인가? 무엇보다 먼저, 데이터 스트림은 무한(unbounded) 데이터셋을 나타내는 추상이다. 무한하다는 것은 끝이 없고 계속 자란다는 뜻이며, 시간이 흐르면서 새 레코드가 계속 도착하기 때문에 데이터셋이 무한해진다. 이 정의는 Google, Amazon을 비롯해 사실상 모두가 공유한다.
이 단순한 모델(이벤트의 스트림)로 우리가 분석하고 싶은 거의 모든 비즈니스 활동을 표현할 수 있다. 신용카드 거래의 스트림, 주식 거래, 소포 배송, 스위치를 통과하는 네트워크 이벤트, 제조 설비의 센서가 보고하는 이벤트, 발송된 이메일, 게임 안의 수(手) 등이다. 거의 모든 것이 이벤트의 연속으로 볼 수 있기 때문에 예시는 끝이 없다.
중요한 점은, 이벤트 스트림의 정의도 뒤에 나올 속성들도 이벤트에 담긴 데이터의 종류나 초당 이벤트 건수에 대해서는 아무 말도 하지 않는다는 것이다. 데이터는 시스템마다 다르다. 이벤트는 몇 바이트에 불과할 만큼 작을 수도 있고 헤더가 많은 XML 메시지처럼 매우 클 수도 있다. 완전히 비구조적일 수도, 키-값 쌍일 수도, 반구조적 JSON일 수도, Avro나 Protobuf처럼 구조화된 메시지일 수도 있다. 데이터 스트림 하면 흔히 초당 수백만 건의 '빅데이터'를 떠올리지만, 여기서 다루는 기법들은 초당 또는 분당 몇 건에 불과한 작은 스트림에도 똑같이(그리고 종종 더 잘) 적용된다.
핵심 포인트
- 데이터 스트림은 '무한하고 계속 자라는 데이터셋'의 추상이다
- 거의 모든 비즈니스 활동을 이벤트의 연속으로 모델링할 수 있다
- 정의는 데이터의 형태나 초당 건수를 규정하지 않는다 — 작은 스트림에도 똑같이 적용된다
이벤트 스트림의 세 가지 속성
무한하다는 성질 외에 이벤트 스트림 모델에는 세 가지 속성이 더 있다.
**순서가 있다.** 어떤 이벤트가 다른 이벤트보다 먼저 또는 나중에 일어났다는 관념이 본질적으로 들어 있다. 금융 이벤트를 보면 가장 분명하다. 계좌에 먼저 돈을 넣고 나중에 쓴 순서와, 먼저 쓰고 나중에 입금해 빚을 메운 순서는 전혀 다르다. 후자는 초과 인출 수수료가 붙고 전자는 붙지 않는다. 이것이 이벤트 스트림과 데이터베이스 테이블의 차이 중 하나다. 테이블의 레코드는 언제나 정렬되지 않은 것으로 간주되며, SQL의 'order by' 절은 관계 모델의 일부가 아니라 리포팅을 돕기 위해 나중에 추가된 것이다.
**레코드가 불변이다.** 이벤트는 한번 일어나면 절대 수정될 수 없다. 취소된 금융 거래는 사라지지 않고, 대신 이전 거래의 취소를 기록하는 이벤트가 스트림에 추가로 기록된다. 고객이 상품을 반품하면 앞서 그 상품이 그에게 팔렸다는 사실을 지우는 것이 아니라, 반품을 별도 이벤트로 기록한다. 이것도 테이블과의 차이다. 테이블에서는 레코드를 지우거나 갱신할 수 있지만, 그 동작들 자체가 데이터베이스에서 일어난 또 하나의 트랜잭션이고 따라서 모든 트랜잭션을 기록하는 이벤트 스트림으로 표현될 수 있다. binlog, WAL, redo log에 익숙하다면 이해가 쉽다. 레코드를 넣었다가 나중에 지우면 테이블에는 그 레코드가 더 이상 없지만, redo log에는 insert와 delete 두 개의 트랜잭션이 남는다.
**재생 가능하다.** 이것은 '바람직한' 속성이다. 재생 불가능한 스트림도 얼마든지 상상할 수 있지만(소켓을 흐르는 TCP 패킷은 일반적으로 재생 불가능하다), 대부분의 비즈니스 애플리케이션에서는 몇 달 전, 때로는 몇 년 전에 일어난 원시 이벤트 스트림을 다시 재생할 수 있는 것이 결정적으로 중요하다. 오류를 바로잡거나, 새로운 분석 방법을 시도하거나, 감사를 수행하기 위해서다. 책이 Kafka가 현대 비즈니스에서 스트림 처리를 성공시켰다고 보는 이유가 바로 이것이다. Kafka는 이벤트 스트림을 붙잡아 두고 재생할 수 있게 한다. 이 능력이 없었다면 스트림 처리는 데이터 과학자의 실험실 장난감 이상이 되지 못했을 것이다.
핵심 포인트
- 순서: 테이블 레코드는 무순서지만 이벤트 스트림에는 선후 관계가 내재한다 (SQL의 order by는 관계 모델의 일부가 아니다)
- 불변성: 취소·반품도 삭제가 아니라 추가 이벤트로 기록된다 (redo log 비유)
- 재생 가능성: 오류 수정·새 분석·감사를 위해 필수이며, Kafka가 스트림 처리를 실용화한 핵심 이유
세 가지 프로그래밍 패러다임 속의 스트림 처리
스트림 처리란 하나 이상의 이벤트 스트림을 지속적으로 처리하는 것을 말한다. 스트림 처리는 request-response나 배치 처리와 마찬가지로 하나의 프로그래밍 패러다임이다. 세 패러다임을 비교하면 스트림 처리가 소프트웨어 아키텍처에서 어디에 들어맞는지가 분명해진다.
**Request-response**는 지연이 가장 낮은 패러다임으로, 응답 시간이 밀리초 이하에서 몇 밀리초 범위이며 보통 응답 시간이 매우 일관되기를 기대한다. 처리 방식은 대개 차단(blocking)형이다. 애플리케이션이 요청을 보내고 처리 시스템의 응답을 기다린다. 데이터베이스 세계에서는 OLTP(online transaction processing)로 알려져 있고, POS 시스템·신용카드 처리·근태 관리 시스템이 이 방식으로 동작한다.
**배치 처리**는 고지연·고처리량 선택지다. 처리 시스템이 정해진 시각에 깨어난다 — 매일 새벽 2시, 매시 정각 같은 식이다. 필요한 입력을 모두 읽고(지난 실행 이후의 전체 데이터, 이달 초부터의 전체 데이터 등) 필요한 출력을 모두 쓴 다음, 다음 예정 시각까지 사라진다. 처리 시간은 몇 분에서 몇 시간이고, 사용자는 결과를 볼 때 오래된 데이터를 보게 되리라 예상한다. 데이터베이스 세계에서는 데이터 웨어하우스와 BI 시스템이 여기 해당한다 — 하루 한 번 거대한 배치로 데이터를 적재하고 리포트를 생성하면, 다음 적재 때까지 사용자는 같은 리포트를 본다. 이 패러다임은 효율과 규모의 경제가 뛰어나지만, 최근 기업들이 더 짧은 주기로 데이터를 필요로 하면서 규모의 경제를 노려 만들어진 시스템에 큰 압박이 가해지고 있다.
**스트림 처리**는 그 사이를 메우는 비차단(nonblocking) 선택지다. 처리에 2밀리초가 걸리는 이벤트를 기다리는 request-response 세계와, 하루 한 번 데이터를 처리하는 데 8시간이 걸리는 배치 세계 사이의 틈이다. 대부분의 비즈니스 프로세스는 밀리초 단위의 즉각적인 응답을 요구하지 않지만 다음 날까지 기다릴 수도 없다. 대부분의 비즈니스 프로세스는 연속적으로 일어나며, 리포트가 계속 갱신되고 업무 애플리케이션이 계속 반응할 수만 있다면 누구도 특정 응답을 밀리초 안에 기다릴 필요가 없다. 수상한 신용거래나 네트워크 활동에 대한 경보, 수요와 공급에 따른 실시간 가격 조정, 소포 배송 추적 등이 연속적이면서 비차단적인 처리에 자연스럽게 들어맞는 예다.
중요한 것은 이 정의가 특정 프레임워크나 API, 기능을 요구하지 않는다는 점이다. 무한 데이터셋에서 데이터를 지속적으로 읽고, 무언가를 하고, 출력을 내보내고 있다면 그것이 스트림 처리다. 다만 처리는 연속적이고 계속 진행되어야 한다. 매일 새벽 2시에 시작해 스트림에서 500건을 읽고 결과를 내놓고 사라지는 프로세스는 스트림 처리라고 하기 어렵다.
핵심 포인트
- request-response = 저지연·차단형(OLTP), batch = 고지연·고처리량, stream = 연속적·비차단
- 스트림 처리는 '밀리초는 필요 없지만 내일까지는 못 기다리는' 비즈니스 프로세스의 자리
- 정의는 프레임워크를 규정하지 않는다 — 무한 데이터셋을 '연속적으로' 읽고 처리하고 내보내면 스트림 처리다
- 정해진 시각에 깨어나 일정량을 읽고 끝나는 프로세스는 스트림 처리가 아니다
이 모듈과 연관된 문항 2개가 문제 은행에 있습니다.
이 내용으로 문제 풀어보기