List
지난 글에서는 Apache Flink의 내부 아키텍처와 Kafka 기반 기술들의 구조적 차이점을 살펴보았습니다.
이번 글에서는 Confluent Cloud for Apache Flink 워크샵(
confluent-cloud-flink-workshop)을 통해 실제 실무 환경에서 Flink를 어떻게 활용하는지 단계별로 살펴보겠습니다. 특히 실시간 데이터 처리와 중복 제거라는 현실적인 문제를 어떻게 해결하는지 자세히 다뤄보겠습니다.

실시간 데이터 처리의 현실적 문제: 중복 데이터 처리
실제 운영 환경에서는 네트워크 지연, 시스템 재시작, 장애 복구 등 다양한 이유로 중복 데이터가 발생합니다. 특히 주문 처리 시스템에서는 같은 주문이 여러 번 처리되면 심각한 문제가 될 수 있습니다.
이번 워크샵에서는 이러한 실무적인 문제를 Flink SQL로 어떻게 해결하는지 구체적으로 알아보겠습니다.
워크샵 시나리오: 제3자 리셀러 데이터 처리
이 워크샵은 Amazon, Walmart 같은 대형 벤더의 제품을 판매하는 제3자 리셀러의 데이터 처리 시나리오를 기반으로 합니다.
핵심 데이터 구조 이해하기
실제 운영 환경과 유사한 다양한 데이터 소스를 다룹니다.
데이터 소스 | 설명 | 실무 활용 포인트 |
orders | 실시간 주문 거래 로그 | 중복 데이터 처리가 핵심 과제 |
customers | 고객 CRM 데이터 | 개인정보 보호 정책 준수 필요 |
products | 제품 카탈로그 정보 | 재고 관리와 상품 정보 제공 |
clicks | 고객 클릭 추적 데이터 | 실시간 사용자 행동 분석 |
payments | 결제 기록 정보 | 재무 분석과 매출 추적 |
Confluent Cloud 기반 Flink 실행 환경 이해하기
Kafka와 Flink의 매핑 관계
기존 Kafka 환경에서 Flink를 활용할 때 이해해야 할 핵심 개념입니다.
Kafka | Flink | 실무 의미 |
Environment | Catalog | 전체 환경 단위 관리 |
Cluster | Database | 클러스터 단위 리소스 관리 |
Topic + Schema | Table | 실제 데이터 처리 단위 |
1.
Environment
Catalog (전체 환경 단위 관리)
a.
Confluent Cloud의 Environment란 개발팀이나 부서별로 완전히 분리된 작업 공간을 의미
i.
실제 의미: 개발(Development), 테스트(Test), 운영(Production) 환경을 구분하는 최상위 논리적 단위
ii.
포함 구성 요소: 여러 개의 Kafka 클러스터, Schema Registry, ksqlDB, Connect 등을 포함하는 완전한 생태계
iii.
관리 목적: 팀 간 간섭 방지, 데이터 격리, 보안 정책 분리
b.
Flink의 Catalog는 데이터베이스 관리 시스템에서 메타데이터를 저장하는 저장소와 같은 개념
i.
실제 의미: 모든 데이터베이스 객체(테이블, 뷰, 함수 등)의 정의와 위치 정보를 관리하는 중앙 저장소
ii.
핵심 기능: 스키마 정보, 테이블 구조, 연결 정보 등을 저장하여 Flink SQL 엔진이 외부 데이터에 접근할 수 있도록 지원
iii.
실무 활용: USE CATALOG my_env와 같은 명령어로 작업할 환경을 선택
2.
Cluster
Database (클러스터 단위 리소스 관리)
a.
Kafka 클러스터는 여러 브로커(서버)가 협력하여 데이터를 분산 저장하고 처리하는 물리적 단위입니다
i.
실제 의미: 실제로 메시지를 저장하고 처리하는 서버들의 집합
ii.
구성 요소: 여러 개의 브로커 노드로 구성되며, 각 브로커는 토픽의 파티션을 분산 저장
iii.
확장성: 브로커를 추가하여 수평 확장 가능
iv.
내구성: 데이터 복제를 통해 장애 시에도 데이터 손실 방지
b.
Flink의 Database는 관련된 테이블들을 논리적으로 그룹화하는 네임스페이스입니다.
i.
실제 의미: 특정 Kafka 클러스터에 속한 모든 토픽들을 하나의 데이터베이스로 표현
ii.
활용 방식: USE marketplace와 같은 명령어로 특정 클러스터의 토픽들에 접근
iii.
관리 편의성: 클러스터별로 토픽을 체계적으로 관리하여 운영 복잡성 감소
3.
Topic + Schema
Table (실제 데이터 처리 단위)
a.
Kafka 토픽은 관련된 메시지들을 논리적으로 묶어 저장하는 메시지 채널
i.
실제 의미: 비즈니스 도메인별로 분류된 메시지 스트림 (예: 주문, 결제, 고객 정보)
ii.
파티션 구조: 각 토픽은 여러 파티션으로 나뉘어 병렬 처리와 확장성 제공
iii.
순서 보장: 파티션 내에서는 메시지 순서가 보장되지만, 파티션 간에는 보장되지 않음
iv.
오프셋: 각 메시지는 파티션 내에서 고유한 오프셋(순서 번호)을 가짐
b.
Schema Registry는 메시지의 구조를 정의하고 관리하는 중앙 저장소
i.
실제 의미: 프로듀서와 컨슈머 간의 데이터 형식 규약을 관리하는 시스템
ii.
핵심 기능: 스키마 버전 관리, 호환성 검사, 데이터 직렬화/역직렬화 지원
iii.
데이터 품질: 잘못된 형식의 메시지 전송을 방지하여 데이터 무결성 보장
c.
Flink의 Table은 스트리밍 데이터를 관계형 테이블 형태로 추상화한 개념입니다.
i.
실제 의미: Kafka 토픽의 실시간 메시지 스트림을 SQL로 조회할 수 있는 테이블로 변환
ii.
동적 특성: 정적인 데이터베이스 테이블과 달리 시간에 따라 계속 변화하는 “동적 테이블”
iii.
SQL 호환성: 일반적인 SQL 쿼리(SELECT, JOIN, GROUP BY 등)를 실시간 스트림에 적용 가
iv.
상태 관리: 집계나 조인 연산을 위해 필요한 상태를 Flink가 자동으로 관리
Flink Compute Pool 활용하기 - 서버리스 컴퓨팅의 장점
Confluent Cloud Flink Compute Pool은 자체 관리형 Flink 클러스터 없이도 확장 가능한 서버리스 컴퓨팅 환경을 제공합니다.
•
data-generation: 실습 데이터 생성용 (백그라운드 실행)
•
default: 실제 실습 작업용 (메인 처리)
(최대 CFU(Confluent Flink Units) 수만 설정하면 자동으로 리소스가 확장되고, 실제 사용한 만큼만 비용이 부과됩니다.)
핵심 실습: 중복 데이터 제거 (Deduplication)
중복 데이터 확인
먼저 orders 테이블에서 실제로 중복 데이터가 존재하는지 확인합니다.
-- 중복 데이터 확인
SELECT
order_id,
COUNT(*) as duplicate_count
FROM orders
GROUP BY order_id
HAVING COUNT(*) > 1;
SQL
복사
ROW_NUMBER()를 활용한 중복 제거
Flink에서는 ROW_NUMBER() 함수를 사용하여 효과적으로 중복을 제거할 수 있습니다.
-- 중복 제거 쿼리 (각 order_id에 대해 첫 번째 이벤트만 유지)
SELECT
order_id,
product_id,
customer_id,
price
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY `$rowtime` ASC
) AS rownum
FROM orders
)
WHERE rownum = 1;
SQL
복사
중복 제거 로직의 핵심
1.
PARTITION BY order_id: 주문 ID별로 데이터를 그룹화
2.
ORDER BY $rowtime ASC: 시간 순서대로 정렬하여 가장 먼저 도착한 데이터 선택
3.
WHERE rownum = 1: 각 그룹에서 첫 번째 레코드만 유지
추가 설명 - 이벤트 시간과 $rowtime 의 역할
$rowtime은 Apache Flink SQL에서 제공하는 이벤트 시간(Event Time)을 나타내는 시스템 컬럼입니다.
이벤트 시간이란, 데이터가 실제로 발생한 시점을 의미하며, 다음과 같은 이유로 중요합니다.
1.
실제 발생 순서 보장
a.
네트워크 지연이나 시스템 장애로 메시지가 뒤바뀌어 도착하더라도, $rowtime을 기준으로 원래 발생 시점 순서대로 처리할 수 있음
2.
시간 기반 연산에 필수
a.
윈도우 집계(TUMBLE, HOP 등), 지연 데이터 처리(late data), 워터마크(Watermark)와 같은 기능들이 모두 이벤트 시간을 토대로 동작
3.
메시지 타임스탬프 매핑
a.
Confluent Cloud for Apache Flink 환경에서는 Kafka 레코드에 포함된 타임스탬프 필드가 자동으로 $rowtime에 매핑되므로, 별도 변환 작업 없이 바로 이벤트 시간을 사용할 수 있음
$rowtime의 내부 동작 방식은 아래와 같습니다.
•
$rowtime의 데이터 타입은 TIMESTAMP_LTZ(3)이며, 밀리초 정밀도로 이벤트 발생 시각을 기록
•
워터마크 설정 시에도 $rowtime을 사용하여 “이 시점까지 도착한 데이터는 모두 반영되었음”을 Flink에 알려줌
데이터 프로덕트 생성: First-Seen-Wins 전략
-- 상태 관리 설정
SET 'client.statement-name' = 'unique-orders-maintenance';
SET 'sql.state-ttl' = '1 hour';
-- unique_orders 테이블 생성
CREATE TABLE unique_orders
AS
SELECT
order_id,
product_id,
customer_id,
price
FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY `$rowtime` ASC
) AS rownum
FROM orders
)
WHERE rownum = 1;
SQL
복사
1.
client.statement-name: 쿼리 작업 이름 지정 → 모니터링·디버깅 시 식별
2.
sql.state-ttl: 1시간 동안만 상태 유지 → 오래된 주문 상태 자동 만료
3.
First-Seen-Wins: 각 주문 ID별 첫 이벤트만 상태로 기록 → 중복 제거
이 설정을 통해, 장시간 실행되는 실시간 애플리케이션에서도 효율적이고 안정적인 상태 관리로 메모리 과다 사용을 방지하면서 정확한 중복 제거를 보장할 수 있습니다.
추가 설명 - 상태 관리(State Management)
스트림 처리에서 상태(state)는 중복 제거, 집계, 조인 등 연산을 위해 Flink가 내부에 유지하는 중간 연산 결과를 뜻합니다. Lab 1의 First-Seen-Wins 전략에서는 각 주문 ID별로 “첫 번째 이벤트”를 기억하는 상태를 활용합니다.
sql.state-ttl 설정의 의미
•
설정 예시
SET 'sql.state-ttl' = '1 hour';
SQL
복사
•
기능
◦
TTL(Time-To-Live) 값(여기서는 1시간) 동안 갱신되지 않은 상태는 자동으로 삭제
▪
중복 제거용 내부 상태(이전에 본 주문 ID 목록)에만 적용
▪
해당 상태가 마지막 접근 시점으로부터 1시간이 지나면 Flink 내부에서 자동으로 삭제
▪
그 이후에 동일한 order_id 이벤트가 들어오면, Flink는 이를 처음 보는 주문으로 간주하여 unique_orders 테이블에 다시 한 번 레코드를 삽입
◦
오래된 상태가 계속 쌓이지 않도록 하여 메모리·스토리지 사용을 최적화
•
효과
1.
메모리·디스크 사용량 절감 → 체크포인트 생성·복구 속도 향상
2.
GC 부하 감소 → 전반적인 처리 성능 향상
3.
관리형 서비스 요금 절감 → 사용된 상태 저장량 만큼만 과금
실시간 집계와 시간 윈도우 처리
기본 집계 연산
-- 브랜드별 모델 수와 벤더 수 집계
SELECT
brand as brand_name,
COUNT(DISTINCT name) as models_by_brand,
COUNT(DISTINCT vendor) as available_vendors
FROM products
GROUP BY brand;
SQL
복사
1.
GROUP BY brand: 브랜드별로 레코드를 그룹화
2.
COUNT(DISTINCT name): 각 브랜드에 속한 고유 모델 개수를 계산
3.
COUNT(DISTINCT vendor):각 브랜드의 판매 벤더 수를 계산
4.
이 연산은 정적인 데이터베이스에서와 동일하게, 실시간 스트림에서도 현재 시점까지 수집된 모든 products 레코드를 기준으로 집계된 결과를 반환
시간 윈도우를 활용한 실시간 분석
스트림은 무한히 흘러가기 때문에, 시간 구간별로 집계하려면 윈도우 연산을 사용해야 합니다. Flink SQL에서는 $rowtime 기반의 TUMBLE(고정 크기)와 HOP(슬라이딩) 윈도우를 지원합니다.
Tumbling Window (고정 크기 윈도우)
-- 1분 간격으로 주문 수 집계
SELECT
window_time,
COUNT(DISTINCT order_id) AS num_orders
FROM TABLE(
TUMBLE(
TABLE unique_orders,
DESCRIPTOR(`$rowtime`),
INTERVAL '1' MINUTES
)
)
GROUP BY window_start, window_end, window_time;
SQL
복사
1.
INTERVAL '1' MINUTES: 윈도우 크기: 1분
2.
window_start, window_end: 각 윈도우 구간의 시작·종료 시간
3.
window_time: 결과 레코드의 이벤트 시간 컬럼
4.
동작 방식
a.
스트림을 1분 단위로 분할(TUMBLE)
b.
각 구간 내 고유 order_id 개수를 계산
c.
구간이 끝나면 결과를 한 번씩만 출력
예시 데이터 (초 단위)
order_id | $rowtime |
101 | 12:00:10.000 |
102 | 12:00:55.000 |
103 | 12:01:15.000 |
윈도우 결과
window_start | window_end | num_orders |
12:00:00 | 12:01:00 | 2 |
12:01:00 | 12:02:00 | 1 |
Hopping Window (슬라이딩 윈도우)
-- 5분씩 전진하는 10분 윈도우로 트렌드 분석
SELECT
window_start,
window_end,
COUNT(DISTINCT order_id) AS num_orders
FROM TABLE(
HOP(
TABLE unique_orders,
DESCRIPTOR(`$rowtime`),
INTERVAL '5' MINUTES,
INTERVAL '10' MINUTES
)
)
GROUP BY window_start, window_end, window_time;
SQL
복사
1.
INTERVAL '10' MINUTES: 윈도우 크기: 10분
2.
INTERVAL '5' MINUTES: 슬라이딩 크기: 5분
3.
동작 방식
a.
첫 윈도우: 0–10분 구간, 두 번째 윈도우: 5–15분 구간, …
b.
겹치는 구간별로 중복 없이 고유 주문 수 집계
c.
슬라이드마다 결과를 출력
예시 데이터 (초 단위)
order_id | $rowtime |
201 | 12:00:10.000 |
202 | 12:04:55.000 |
203 | 12:06:15.000 |
Hopping 윈도우 구간
•
윈도우 1: 12:00:00 ~ 12:10:00
•
윈도우 2: 12:05:00 ~ 12:15:00
결과 예시
window_start | window_end | num_orders |
12:00:00 | 12:10:00 | 3 |
12:05:00 | 12:15:00 | 1 |
정리: Tumbling vs Hopping
항목 | Tumbling Window | Hopping Window |
윈도우 크기 | 고정 | 고정 |
구간 중첩 | 없음 | 있음 |
슬라이드 간격 | 윈도우 크기와 동일 | 사용자 정의 가능 |
한 이벤트 포함 횟수 | 1번 | 여러 번 |
활용 예시 | 매 분 매출 집계 | 트렌드 변화 감지, 최근 10분 내 누적 수치 |
마무리
이번 글에서는 Confluent Cloud for Apache Flink를 활용한 실제 실습을 통해 실시간 데이터 처리의 현실적인 문제를 어떻게 해결하는지 살펴보았습니다. 특히 중복 데이터 제거라는 실무에서 자주 마주치는 문제를 체계적으로 해결하는 과정을 통해 Flink의 실용성을 확인할 수 있었습니다.
다음 글에서는 Lab 2의 고급 기능들을 다루면서, 더 복잡한 비즈니스 로직을 Flink로 구현하는 방법을 살펴보겠습니다.