스트림 데이터를 어떻게 쓸 것인가?
├─ 목적지(S3/Redshift/OpenSearch)로 자동 적재만 필요 → Firehose
│ · 소비자 코드 없음 · 버퍼링(크기 또는 시간) · Parquet 변환·압축 가능
└─ 여러 소비자가 같은 스트림을 다르게 처리
또는 커스텀 처리·장애 시 재생(replay)이 필요 → Data Streams
· 샤드 관리 · 보관(retention) · 소비자별 독립 오프셋
import boto3, jsonkinesis = boto3.client("kinesis")# Data Streams에 이벤트 넣기 — PartitionKey로 샤드 분배kinesis.put_record( StreamName="clickstream", Data=json.dumps({"user_id": "u123", "event": "click", "ts": 1719300000}), PartitionKey="u123", # 같은 키는 같은 샤드 → 순서 보장 단위)
# Firehose: 코드 없이 S3로 자동 배달 + 버퍼링 설정 (전송 스트림 구성 예)firehose = boto3.client("firehose")firehose.put_record( DeliveryStreamName="to-datalake", Record={"Data": json.dumps({"user_id": "u123", "amount": 42.0}) + "\n"},)# Firehose가 버퍼(예: 5MB 또는 60초)를 채우면 S3에 Parquet으로 변환·압축 적재
KDS는 샤드(shard) 단위로 처리량을 확장한다. 샤드 하나가 쓰기 1MB/s·1,000 records/s, 읽기 2MB/s를 처리하므로 트래픽이 늘면 샤드를 늘려야 한다(또는 on-demand 모드). 또한 KDS는 데이터를 보관(retention)해 여러 소비자가 같은 데이터를 독립적으로 읽고 재처리할 수 있다(replay). 반면 Firehose는 보관·재생이 없고 목적지 적재만 책임지는 "fire and forget" 파이프다.
⚠️ 함정: "실시간이니까 무조건 KDS"는 오답 유도다. 지문에 소비자가 하나뿐이고 "코드 없이", "관리 부담 최소"라는 단서가 있으면 Firehose가 정답이다. 반대로 "재처리", "여러 팀이 각자 소비"가 보이면 Firehose는 탈락한다.
Glue의 DynamicFrame은 Spark DataFrame의 ML/ETL 친화 확장으로, 스키마가 제각각인 반정형 데이터(JSON 등)를 스키마 강제 없이 다룰 수 있다(스키마 불일치 행을 버리지 않고 보존). 정형 변환이 끝나면 toDF()로 일반 Spark DataFrame으로 바꿔 익숙한 연산을 쓴다. Crawler가 채운 Data Catalog 덕에 Athena로 즉시 SQL 탐색이 가능해, ML 전처리 전 데이터 이해(EDA) 단계가 빨라진다.
스트리밍이 꼭 필요한 경우는 두 가지다. ① 실시간 피처(최근 5분 거래 횟수)를 추론에 써야 할 때, ② 사기 탐지처럼 즉시 점수가 필요할 때.
[람다 아키텍처]
이벤트 ──┬─▶ KDS ─▶ Flink(윈도우 집계) ─▶ 실시간 피처 ─▶ 추론 엔드포인트
│ (스피드 레이어: 저지연·근사)
└─▶ Firehose ─▶ S3 데이터 레이크 ─▶ 배치 재학습 ─▶ 모델 갱신
(배치 레이어: 정확·완전)
람다 아키텍처는 같은 데이터를 스피드 레이어(스트리밍, 저지연 근사)와 배치 레이어(주기적, 정확·완전)로 동시에 처리해 둘의 장점을 합친다. ML에서는 Firehose로 모든 이벤트를 S3에 축적(배치 재학습용)하면서, KDS+Flink로 실시간 피처를 계산(즉시 추론용)하는 형태로 자주 쓰인다. 다만 두 경로의 피처 계산 로직이 어긋나면 training-serving skew가 생기므로 일관성 관리가 중요하다.
람다 아키텍처의 진짜 비용은 인프라가 아니라 로직 이중 관리다. 실시간 경로와 배치 경로에서 "최근 5분 거래 횟수"를 다르게 계산하면 모델은 학습 때 본 적 없는 값을 추론 때 받는다. Feature Store로 정의를 한 곳에 모으는 것이 정석 대응이다.