모든 태그

# Data Engineering

20개의 글

[DE Ch-03] 멱등성과 재시도·백오프: 몇 번 돌려도 같은 결과와 흩어지는 재시도

오케스트레이터는 실패한 태스크를 처음부터 다시 돌립니다. 그래서 태스크는 몇 번을 돌려도 같은 결과를 내야 합니다. append와 파티션 덮어쓰기를 DuckDB로 비교하고 지운 직후 죽는 경우를 트랜잭션으로 막습니다. 지터 없는 지수 백오프가 서버가 살아나는 순간 요청을 한꺼번에 몰아오는 것과 지터가 그 대가로 늦추는 것을 시뮬레이션으로 셉니다.

[DE Ch-04] 논리 날짜와 backfill: Airflow 3에서 {{ ds }}가 가리키는 날이 바뀌었다

배치 작업에는 시계가 둘 있습니다. 언제 실행하는지와 어느 구간의 데이터를 처리하는지입니다. Airflow 2는 둘을 data interval로 묶었고 Airflow 3는 기본 timetable을 바꿔 매일 00시 run의 ds가 어제에서 오늘이 됐습니다. 이 차이를 DuckDB로 재현하고 catchup과 backfill이 지난 구간을 다시 돌리는 방식, Argo CronWorkflow가 같은 문제를 다루는 방식을 정리합니다.

[DE Ch-08] 지연은 백분위와 최댓값으로 본다: 평균이 숨기는 꼬리와 합칠 수 있는 히스토그램

평균이 같은 두 서버도 p99는 열 배 차이 날 수 있습니다. 지연은 정규분포를 따르지 않아서 평균과 표준편차가 분포를 설명하지 못합니다. 백분위 여러 개와 최댓값을 함께 보고 백분위는 평균 내지 말고 히스토그램을 합쳐서 구합니다. 팬아웃이 커지면 드문 꼬리가 대부분의 요청에 닿는 이유를 The Tail at Scale의 수치와 시뮬레이션으로 확인합니다.

[DE Ch-09] 측정의 함정: 부하 도구가 느린 순간을 지우는 coordinated omission

서버가 1초 멈췄는데 부하 도구가 p99를 1ms로 기록할 수 있습니다. 응답을 받아야 다음 요청을 보내는 도구는 서버가 멈춘 동안 요청을 보내지 않아서 가장 나쁜 순간의 표본이 빠집니다. 이 coordinated omission을 시뮬레이션으로 재현하고 open loop 부하와 보정 방법을 비교합니다. 워밍업 구간을 다루는 방법과, 두 기계의 시계로 지연을 잴 때 생기는 오차도 정리합니다.

[DE Ch-13] 전달 보장: exactly-once는 출력과 오프셋을 함께 커밋하는 일이다

at-most-once와 at-least-once는 컨슈머가 처리와 커밋 중 무엇을 먼저 하느냐로 갈립니다. Kafka의 exactly-once는 처리 결과와 읽은 위치를 한 트랜잭션으로 커밋해서 만듭니다. 컨슈머가 처리 도중 죽는 상황을 세 방식으로 재현하고 KIP-98의 트랜잭션 구조와 비용, read_committed 독자가 기다리는 시간, 외부 DB로 나갈 때 보장이 끝나는 지점을 공식 문서와 사례로 정리합니다.

[DE Ch-14] 스키마 레지스트리와 호환성: 누가 먼저 올라가는지가 모드를 정한다

메시지를 쓰는 쪽과 읽는 쪽은 서로 다른 시점에 배포됩니다. 스키마 레지스트리의 호환성 모드는 새 스키마로 옛 데이터를 읽을 수 있는지(BACKWARD), 옛 스키마로 새 데이터를 읽을 수 있는지(FORWARD)를 등록 시점에 검사합니다. 필드 추가, 삭제, 타입 변경이 각 방향에서 읽히는지를 fastavro로 직접 확인하고 Avro 스키마 해석 규칙, Confluent 레지스트리의 기본값과 업그레이드 순서 Protobuf와 JSON Schema의 차이, data contract까지 정리합니다.

[DE Ch-02] 오케스트레이터는 무엇을 맡나: cron 다음에 필요한 것들

cron으로 스크립트를 시간 차를 두고 돌리면 의존성, 재시도, 실행 기록, 지난 날짜 재실행, 실패 알림이 빠집니다. Airflow 3와 Argo Workflows가 이 일을 어떤 부품으로 나눠 맡는지, 실행 상태를 어디에 두는지, 부품 하나가 죽으면 무엇이 멈추는지 공식 문서와 기술블로그로 정리합니다.

[DE Ch-05] 스케줄 트리거와 이벤트 트리거: 시각을 기다릴지, 데이터를 기다릴지

데이터가 언제 도착할지 모르면 cron은 너무 이르거나 너무 늦습니다. 센서로 폴링하면 확인 비용과 발견 지연이 생기고 이벤트로 받으면 중복과 유실을 따로 다뤄야 합니다. Airflow 3의 Asset과 AssetWatcher, Argo Events가 이벤트를 어떻게 합치고 거르는지 공식 문서로 정리하고 폴링 비용을 시뮬레이션으로 셉니다.

[DE Ch-06] 동시성 제한, 중복 실행, 사람 승인: 오케스트레이터가 막아 주는 것과 맡겨 두는 것

Airflow는 동시 실행을 스케줄러, DAG, run, 태스크, pool 다섯 층에서 제한합니다. 그런데 max_active_runs=1은 겹침을 막을 뿐 같은 일이 두 번 도는 것은 막지 않습니다. pool로 외부 API를 보호하는 효과와 이벤트 키로 run_id를 정하는 방법을 시뮬레이션으로 확인하고 오케스트레이터의 락이 왜 최종 방어선이 될 수 없는지 분산 락 이론으로 정리합니다. 사람 승인 단계는 Airflow 3.2와 3.3, Argo suspend를 비교합니다.

[DE Ch-18] 테이블 포맷이 필요한 이유: Iceberg는 디렉터리 대신 파일 목록을 기록한다

Hive 방식 테이블은 디렉터리를 나열해서 테이블의 상태를 알아냅니다. 그래서 원자적 커밋이 없고 객체 스토리지에서 계획 시간이 파티션 수에 비례합니다. Iceberg는 테이블에 속한 파일을 메타데이터 계층으로 기록하고 메타데이터 포인터를 원자적으로 바꿔 커밋합니다. PyIceberg로 로컬 Iceberg 테이블을 만들어 커밋마다 생기는 파일과 스냅샷을 직접 보고 낙관적 동시성, 숨은 파티셔닝, 스키마 진화, 행 단위 삭제를 명세 기준으로 정리합니다.

[DE Ch-07] 태스크 하나가 Pod 하나가 될 때: 기동 시간을 구간으로 나누기

KubernetesExecutor와 Argo Workflows는 태스크마다 Pod를 띄웁니다. 쿠버네티스의 공식 Pod 기동 SLO는 p99 5초인데, 이 숫자는 이미지 pull과 init 컨테이너를 뺀 값입니다. Pod가 생성되어 컨테이너가 시작하기까지를 condition, 이벤트, kubelet 메트릭으로 나누는 방법과, 이미지 pull, Pod 생성 호출, init 컨테이너가 각각 어디서 시간을 쓰는지, 짧은 태스크가 많을 때의 대안을 정리합니다.

[DE Ch-19] Iceberg 유지보수: 시간 여행이 남긴 파일은 누군가 치워야 한다

Iceberg는 커밋마다 스냅샷을 남겨 과거 시점의 테이블을 그대로 읽게 해 줍니다. 그 대가로 스냅샷이 참조하는 옛 파일과 메타데이터가 계속 쌓입니다. PyIceberg로 스냅샷마다 시간 여행을 해 보고 스냅샷을 만료해도 데이터 파일은 남는다는 것을 확인합니다. 스냅샷 만료, 오래된 메타데이터 정리, 고아 파일 삭제, 컴팩션의 기본값과 주의점, 작은 파일과 스냅샷이 쌓인 실제 사례를 정리합니다.

[DE Ch-20] CDC로 레이크하우스 채우기: 조회는 상태를 보고 로그는 변경을 본다

원천 DB의 변경을 레이크하우스로 옮기는 방법은 updated_at을 주기적으로 조회하는 방식과 DB의 변경 로그를 읽는 방식으로 나뉩니다. 조회 방식은 삭제와 중간 상태를 놓친다는 것을 SQLite로 재현하고 Debezium이 binlog와 WAL을 읽는 구조, 초기 스냅샷, at-least-once 보장, PostgreSQL replication slot이 디스크를 채우는 경로, Iceberg에 upsert로 반영할 때의 조건을 공식 문서와 사례로 정리합니다.

[DE Ch-10] MQTT: QoS는 한 구간씩, 세션은 끊긴 동안을 맡는다

MQTT는 연결이 자주 끊기는 기기를 위한 가벼운 메시징 프로토콜입니다. QoS 0, 1, 2는 발행자에서 브로커, 브로커에서 구독자 구간마다 따로 적용되고 구독 쪽이 낮으면 낮은 쪽으로 내려갑니다. QoS 2의 정확히 한 번도 한 구간 안의 이야기입니다. MQTT 5.0 명세로 토픽 와일드카드, QoS 흐름, retained 메시지, 세션을 정리하고 Mosquitto 설정과 MQTT에서 Kafka로 넘길 때의 전달 보장을 봅니다.

[DE Ch-11] Kafka 로그 구조: 순서는 파티션 안에서만, 파티션을 늘리면 키가 옮겨 간다

Kafka의 토픽은 파티션으로 나뉜 추가 전용 로그입니다. 순서는 파티션 안에서만 보장되고 같은 키는 murmur2 해시로 같은 파티션에 갑니다. 파티션 수를 6개에서 8개로 늘리면 키의 74.6%가 다른 파티션으로 옮겨 간다는 것을 Kafka와 같은 해시로 확인합니다. 세그먼트와 보존, 오프셋 저장, 컨슈머 그룹과 리밸런스 프로토콜의 변화, 복제와 ISR까지 Kafka 4.3 문서 기준으로 정리합니다.

[DE Ch-12] 프로듀서 설정과 지연: linger.ms는 기다림을 사서 요청 수를 줄인다

Kafka 프로듀서는 레코드를 배치로 모아 보냅니다. linger.ms만큼 더 기다리면 배치가 커지고 요청 수가 줄어 오히려 지연이 줄기도 합니다. Kafka 4.0에서 linger.ms 기본값이 0에서 5로 바뀐 이유와, acks, 멱등성, max.in.flight가 내구성과 순서를 어떻게 정하는지 공식 문서와 측정 글로 정리하고 메시지 유입량에 따라 linger가 의미를 갖는 구간을 단순 모델로 셉니다.

[DE Ch-15] Flink의 시간과 watermark: 얼마나 기다릴지가 지연과 완전성을 맞바꾼다

끝이 없는 입력에서는 "이 구간의 데이터가 다 왔다"를 알 수 없어서 watermark라는 추정을 씁니다. watermark를 늦게 잡으면 결과가 늦고 빨리 잡으면 늦게 온 데이터를 버립니다. 허용치별로 버려지는 이벤트와 결과 지연을 시뮬레이션으로 세고 조용한 파티션 하나가 전체 watermark를 붙잡는 현상과 idleness 설정을 Flink 2.3 문서와 사례로 정리합니다.

[DE Ch-16] 윈도와 interval join: 시간 범위가 짝을 정하고 상태 크기도 정한다

끝이 없는 스트림을 집계하려면 시간으로 잘라야 하고 두 스트림을 이으려면 시간 범위 안에서 짝을 찾아야 합니다. Flink의 텀블링, 슬라이딩, 세션, 글로벌 윈도와 trigger, evictor가 결과를 내는 시점을 정리하고 주문과 결제를 interval join으로 이을 때 범위를 넓힐수록 짝지어지는 비율과 붙들고 있어야 하는 상태가 함께 늘어나는 것을 시뮬레이션으로 셉니다.

[DE Ch-17] 체크포인트와 백프레셔: 간격은 재처리량과 결과가 보이는 시점을 함께 정한다

Flink는 배리어를 데이터 사이에 흘려 전체 상태의 스냅샷을 찍고 장애가 나면 마지막 스냅샷부터 다시 처리합니다. exactly-once 싱크는 체크포인트가 끝날 때 결과를 커밋하므로 간격이 곧 결과가 보이기까지의 지연이 됩니다. 간격별 가시성 지연과 장애 때 재처리량을 시뮬레이션으로 세고 백프레셔가 배리어를 늦춰 체크포인트가 길어지는 경로와 unaligned 체크포인트, buffer debloating, buffer timeout을 Flink 2.3 문서로 정리합니다.