DataStream API 기본 변환 — map/filter
직관적으로
컨베이어 벨트 위를 흐르는 부품들을 상상해보자. map은 지나가는 부품 하나하나를 가공해서 형태를 바꾸는 작업이고, filter는 조건에 안 맞는 부품을 벨트에서 골라내 버리는 작업이다. DataStream API의 변환은 이 컨베이어 벨트 비유 그대로 동작한다.
실제 사례
study_de의 Spark 학습 콘텐츠에서 다룬 RDD/DataFrame의 map/filter와 코드 감각이 거의 동일하다 — `stream.map(x -> x * 2)`, `stream.filter(x -> x > 0)`처럼 쓴다. 다른 점은 Spark의 map/filter가 '그 시점에 존재하는 데이터 집합'에 적용되는 배치적 개념인 반면, Flink의 map/filter는 '끝없이 흘러오는 각 레코드'에 하나씩 적용된다는 것이다.
정확한 원리
DataStream API의 모든 변환은 레코드 단위 함수로 정의된다 — Flink는 이 함수들을 체이닝해 하나의 실행 그래프(JobGraph)로 만들고, 각 레코드가 이 그래프를 순서대로 흘러가며 처리된다. map은 입력 1개당 출력 1개(1:1 변환), filter는 입력 1개당 출력 0개 또는 1개(조건부 통과)라는 카디널리티 차이가 있다.
흔한 오해
"map/filter는 Spark와 완전히 똑같은 개념이니 그냥 문법만 외우면 된다"고 생각하기 쉽지만, 정확히는 **연산 자체의 의미(1:1 변환, 조건부 필터링)는 같아도 실행되는 방식(배치 단위 vs 레코드 단위)이 다르다**는 걸 이해해야 한다 — 이게 나중에 상태(state) 관리와 시간(time) 개념을 이해하는 데 핵심 전제가 된다.
확인 문제
`stream.filter(x -> x.amount > 1000)` 다음에 `.map(x -> x.amount * 0.1)`을 체이닝했다고 하자. 원본 스트림에 amount가 500, 1500, 2000인 레코드 3개가 순서대로 들어온다면, 최종 출력에는 몇 개의 레코드가 몇 번째부터 나올까?
Answer Guide
500은 filter에서 걸러짐(500 > 1000 거짓), 1500과 2000만 통과해 각각 *0.1이 적용되어 150, 200이 출력됨 — 총 2개의 출력.