[{"content":"CDC 파이프라인을 Debezium과 Flink로 재설계한 이유에서 Debezium + Flink 기반 CDC 파이프라인을 소개했습니다. Debezium이 DB 변경분을 캡처하고, Kafka를 거쳐, Flink가 5분마다 S3에 Parquet 파일로 저장하는 구조입니다.\n파이프라인은 1년 넘게 잘 동작했습니다. 데이터 정합성도 검증됐고, 운영도 안정적이었습니다. 문제는 데이터 플랫폼 TCO 분석을 하면서 S3 비용을 뜯어보기 전까지는 보이지 않았습니다.\nS3 비용, 스캔량만 보면 안 됩니다 AWS에서 Athena를 쓰는 팀이라면 대부분 이렇게 알고 있습니다:\n\u0026ldquo;Athena 비용 = 스캔한 데이터량 × $5/TB\u0026rdquo;\n틀린 말은 아닙니다. 하지만 이건 Athena 서비스 요금일 뿐입니다. 실제로 Athena가 쿼리를 실행할 때 일어나는 일은 이렇습니다:\n대상 파일 목록을 S3에서 조회합니다 (LIST) 각 파일을 열어서 읽습니다 (GET) 결과를 S3에 저장합니다 (PUT) 이 중 GET request는 대상 파일 수에 비례해서 증가합니다. LIST는 prefix 단위, PUT은 결과 저장으로 쿼리당 1회 수준이지만, GET은 파일 하나하나를 열어야 하기 때문입니다. GET request 1,000건당 $0.0004 (ap-south-1 기준) — 작아 보이지만, 파일이 수십만 개면 얘기가 달라집니다.\n저희 데이터 플랫폼의 S3 비용을 API operation별로 뜯어봤습니다:\n월 Storage GetObject 총 비용 GET 비율 2025-07 $28,081 $4,308 $36,339 12% 2025-08 $25,986 $4,149 $34,475 12% 2025-09 $30,083 $4,671 $39,414 12% 2025-10 $29,782 $9,320 $43,508 21% 2025-11 $31,068 $10,267 $45,503 23% 2025-12 $31,729 $12,683 $48,121 26% 2026-01 $33,607 $20,929 $58,832 36% 2026-02 $36,960 $30,292 $72,107 42% 2026-03 $42,498 $34,147 $83,286 41% 2026-04 $46,006 $33,171 $86,619 38% 2026-05 $40,788 $39,853 $86,726 46% 10개월 사이 스토리지는 45% 늘었지만, GetObject 비용은 825% 폭등했습니다. S3 비용의 절반 가까이가 파일을 읽는 데서 발생하고 있었습니다.\n범인을 찾기까지 비용이 어디서 나오는지 정확히 파악하려면 \u0026ldquo;어떤 경로의 파일이 얼마나 읽히는지\u0026quot;를 알아야 합니다. 문제는 Cost Explorer가 S3 서비스 전체의 비용만 보여준다는 것입니다 — 어떤 버킷이, 어떤 경로가 비용을 유발하는지는 알 수 없습니다.\n이걸 해결하기 위해 세 가지를 조합했습니다.\n1. S3 Cost Allocation Tag + Cost Explorer S3 버킷에 태그를 달고 (예: purpose: datalake), Billing 콘솔의 Cost Allocation Tags에서 해당 태그를 활성화합니다. 활성화하지 않으면 Cost Explorer에서 태그별 필터링이 되지 않습니다. 활성화 후 Cost Explorer에서 태그별로 그룹핑하면 버킷 단위로 스토리지 vs Request 비용 비율을 파악할 수 있습니다.\n2. S3 Storage Class Analysis 버킷 내 prefix별 접근 패턴을 분석할 수 있는 기능입니다. 콘솔에서는 제한된 시각화만 제공하지만, destination bucket을 설정하면 날짜별로 prefix별 GetRequestCount, DataRetrieved_MB, Storage_MB 등을 CSV로 export할 수 있습니다. 매일 append되므로 추이 분석이 가능합니다.\n29개 prefix에 대해 분석 그룹을 만들어 돌렸습니다. 동시에 여러 최적화가 진행되고 있었기 때문에, 다른 요인을 배제하기 위해 compaction 직전 안정 구간(6/5~6/8)의 평균을 기준으로 분석했습니다:\n주의: GetRequestCount 컬럼은 이름과 달리 GET + PUT 합산입니다. AWS 문서에도 mislabelled라고 명시되어 있으니, 정확한 비용 산정 시 이 점을 감안해야 합니다.\nPrefix Requests (M/day) 일간 조회량 비율 service_a.cdc 718 117 TB 92.7% service_a.db 14 12 TB 1.9% service_b.db 9 7 TB 1.2% 나머지 26개 prefix 34 32 TB 4.2% 합계 775 168 TB service_a.cdc 하나가 전체 request의 93%를 차지하고 있었습니다.\n3. 스키마별 파일 분석 Storage Class Analysis가 비용의 \u0026ldquo;어디\u0026quot;를 알려줬다면, \u0026ldquo;왜\u0026quot;를 알기 위해서는 실제 파일 구조를 봐야 했습니다. S3의 CDC 경로를 분석했습니다:\n지표 값 총 파일 수 (1주일분) 454,572 총 크기 145 GB 평균 파일 크기 335 KB 100KB 미만 비율 79% 평균 335KB. Parquet 파일로 운영하기에는 명백히 비효율적인 크기였습니다.\nSmall File은 왜 생기는가 이건 CDC 파이프라인의 구조적 문제입니다.\nFlink는 checkpoint interval마다 파일을 커밋합니다. 저희 설정은 5분입니다. 하루 288번, 테이블 300개 이상, 각 테이블마다 dt 파티션 — 이걸 곱하면 하루에만 수만 개의 파일이 생깁니다.\n그리고 이 파일들은 \u0026ldquo;그 5분 동안 해당 테이블에 발생한 변경분\u0026quot;입니다. 대부분의 테이블은 5분에 수십~수백 건밖에 안 바뀝니다. 결과적으로 수 KB에서 수백 KB짜리 파일이 끝없이 쌓이게 됩니다.\n파이프라인 가동 약 1년 2개월, CDC _changes 경로에 3,140만 개의 파일이 쌓여 있었습니다.\n비용뿐만이 아닙니다 Small File 문제는 비용만의 문제가 아닙니다.\nS3에서 파일을 읽으려면 파일마다 HTTP connection을 열어야 합니다. 128MB 파일 1개를 읽는 것과 335KB 파일 400개를 읽는 것은 전송할 데이터 총량은 비슷해도 오버헤드가 수백 배 다릅니다.\n파티션/prefix의 파일 목록을 조회한 뒤, 각 object를 개별적으로 열어야 함 파일마다 GET 또는 range GET, Parquet footer/metadata 확인, schema 처리, task scheduling 오버헤드 발생 connection 재사용이 안 되면 TCP handshake + TLS 협상까지 매번 발생 Athena든 Spark든, 엔진이 아무리 빨라도 수십만 개 파일을 열어야 하면 느려질 수밖에 없습니다.\n해결: Daily Compaction 해결 방법은 세 가지를 검토했습니다.\n1. Flink checkpoint interval 증가\n장점: 가장 단순함 단점: 데이터 freshness 저하, 비즈니스 모니터링·CDC 기반 복제 등 다운스트림 전체에 영향 2. Flink → Iceberg 직접 적재\n장점: table format 차원에서 small file 관리 가능 단점: Flink sink 재설계 필요, migration 리스크 큼 3. 사후 Daily Compaction\n장점: 기존 ingestion 경로 변경 없음, 점진적 적용 가능 단점: 추가 batch 비용, 원본 파일 교체 시 안정성 검증 필요 3번을 선택했습니다. 기존 파이프라인을 건드리지 않고, 가장 낮은 리스크로 같은 효과를 낼 수 있는 방법이었습니다:\n파티션(dt) 단위로 모든 Small File을 읽어 128MB 단위로 재작성 binlog position 기반 정렬 보존 Airflow에서 매일 CDC 적재 완료 후 트리거 Compaction은 원본 파일을 직접 교체하는 작업이므로, 데이터를 잃지 않기 위한 안전 장치가 필요합니다. Safe Swap 패턴을 적용했습니다:\ncompaction 결과를 임시 경로(_tmp_compact/)에 먼저 작성 임시 경로에 파일이 정상 생성되었는지 검증 검증 통과 후 원본 삭제 → 임시 파일을 원래 경로로 복사 → 임시 파일 삭제 만약 3단계 중간에 장애가 발생하더라도, _tmp_compact/에 데이터가 남아있어 복구할 수 있습니다. 또한 compaction 전후 row count를 비교하여 데이터 유실이 없는지 검증합니다.\n다만 S3는 원자적 rename/replace를 제공하지 않기 때문에, active partition에는 적용하지 않았습니다. CDC 적재가 완료된 전날 파티션만 대상으로 삼고, 쿼리와 충돌 가능성이 낮은 시간대에 실행했습니다.\n단일 파티션 테스트 결과: 72,885개 → 839개 (99% 감소)\n520일치 백필도 진행했습니다. 3,140만 개 파일이 36.9만 개로 줄었습니다 (98.8% 감소).\n결과 지표 Before After 변화 CDC _changes 파일 수 31,400,000 369,000 -98.8% 평균 파일 크기 335 KB 파티션별 상이 Compaction target size - 128 MB Compaction이 진행되면서 조회량 대비 요청 수(Requests/GB)가 꾸준히 줄어드는 것을 확인할 수 있습니다. 같은 데이터를 더 적은 요청으로 읽게 되었다는 뜻이며, compaction 효과를 가장 직접적으로 보여주는 지표입니다 (Storage Class Analysis 기준):\n날짜 Requests Data Retrieved Requests/GB Compaction 상태 6/5 749.1M 124,013 GB 6,041 6/6 722.4M 120,199 GB 6,010 6/7 724.0M 120,407 GB 6,013 6/8 709.8M 118,313 GB 5,999 백필 시작 (66/520) 6/9 596.5M 104,129 GB 5,729 백필 진행 (213/520) 6/10 271.1M 57,482 GB 4,717 백필 진행 (427/520) 6/11 34.1M 19,708 GB 1,731 백필 완료 (520/520) 6/12 22.4M 17,296 GB 1,298 안정화 6/13 22.4M 17,515 GB 1,282 안정화 6/14 21.9M 17,325 GB 1,264 안정화 6/11까지는 백필 작업의 PUT 요청이 섞여 있어 수치가 높지만, 6/12부터는 백필이 끝난 뒤 순수 운영 수치입니다. 6/12~14에서 안정화되어 Requests/GB가 6,016에서 1,281로 79% 감소 — 같은 양의 데이터를 읽는 데 필요한 요청 수가 대폭 줄었습니다.\n조회량이 날마다 다르기 때문에, 동일 조회량(100,000GB) 기준으로 비용을 환산하면:\n지표 Before (6/5~6/8 avg) After (6/12~14 avg) 변화 Requests/GB 6,016 1,281 -79% Requests per 100TB 601.6M 128.1M -79% 월간 request 비용 (추정) ~$7,220/month ~$1,537/month -$5,683/month 비용 산정: requests × 30일 × $0.0004/1,000 (ap-south-1 기준). 실제 쿼리량 변동이 있을 수 있어 청구액과 1:1로 대응되지는 않습니다.\n현재는 매일 자동으로 전날 파티션이 compaction되므로, Small File이 다시 누적되지 않습니다.\n성능은 얼마나 개선되었는가 비용뿐 아니라 쿼리 성능도 확인하기 위해 벤치마크를 진행했습니다. S3 versioning으로 보존된 compaction 전 원본 파일(45,008개)을 별도 버킷에 복원하고, compaction 후 파일(310개)과 동일한 Athena 쿼리로 비교했습니다. 두 데이터셋의 row count는 59,184,128건으로 완전히 일치합니다.\n-- Q1: 전체 COUNT (155일) SELECT count(*) FROM loan WHERE dt \u0026gt;= \u0026#39;2026-01-01\u0026#39; AND dt \u0026lt;= \u0026#39;2026-06-05\u0026#39; → Before: 4,542ms / After: 1,403ms (-69%)\n-- Q2: 필터 COUNT SELECT count(*) FROM loan WHERE dt \u0026gt;= \u0026#39;2026-01-01\u0026#39; AND dt \u0026lt;= \u0026#39;2026-06-05\u0026#39; AND status = \u0026#39;CLOSED\u0026#39; → Before: 2,138ms / After: 1,041ms (-51%)\n-- Q3: GROUP BY 집계 SELECT status, count(*) FROM loan WHERE dt \u0026gt;= \u0026#39;2026-01-01\u0026#39; AND dt \u0026lt;= \u0026#39;2026-06-05\u0026#39; GROUP BY status ORDER BY 2 DESC → Before: 2,752ms / After: 1,243ms (-55%)\n-- Q4: 단일 날짜 COUNT SELECT count(*) FROM loan WHERE dt = \u0026#39;2026-03-01\u0026#39; → Before: 826ms / After: 560ms (-32%)\n쿼리 Before (45,008 files) After (310 files) 개선 Q1. COUNT(*) 전체 4,542ms 1,403ms -69% Q2. COUNT + 필터 2,138ms 1,041ms -51% Q3. GROUP BY 집계 2,752ms 1,243ms -55% Q4. 단일 날짜 COUNT 826ms 560ms -32% 전체 스캔, 필터, 집계 쿼리 전반에서 32~69% 성능 개선이 확인되었습니다. 스캔해야 할 데이터 총량은 동일하지만, 파일을 여는 오버헤드만으로 이 정도 차이가 발생합니다.\n돌이켜보면 \u0026ldquo;Athena 비용 = 스캔량\u0026quot;이라는 상식이 오히려 문제를 늦게 발견하게 만들었습니다. Athena 서비스 요금만 보면 월 수백 달러 수준이라 심각해 보이지 않습니다. 하지만 그 뒤에서 S3 GET request가 하루 수억 건씩 일어나고 있었고, 그 비용은 S3 청구서에 묻혀 있었습니다.\nS3에 데이터를 적재하는 스트리밍 파이프라인을 운영하고 계시다면 한 번쯤 확인해보시길 권합니다:\nS3 버킷의 Request 비용이 Storage 비용 대비 어느 정도인지 Storage Class Analysis로 어떤 prefix가 GET을 많이 유발하는지 스트리밍 경로의 평균 파일 크기가 얼마인지 답이 \u0026ldquo;Request가 전체 S3 비용의 40% 이상\u0026rdquo;, \u0026ldquo;request가 특정 prefix에 집중\u0026rdquo;, \u0026ldquo;평균 파일 크기 1MB 미만\u0026quot;이라면 — 아마 같은 문제를 겪고 있을 것입니다.\nCDC 파이프라인 아키텍처에 대한 상세한 내용은 Afinit 팀 블로그에서 확인할 수 있습니다.\n","permalink":"https://backpressured.dev/posts/cdc-small-file-hidden-cost/","summary":"\u003cp\u003e\u003ca href=\"https://blog.afinit.com/cdc-pipeline-debezium-flink\"\u003eCDC 파이프라인을 Debezium과 Flink로 재설계한 이유\u003c/a\u003e에서 Debezium + Flink 기반 CDC 파이프라인을 소개했습니다. Debezium이 DB 변경분을 캡처하고, Kafka를 거쳐, Flink가 5분마다 S3에 Parquet 파일로 저장하는 구조입니다.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"CDC Pipeline Architecture\" loading=\"lazy\" src=\"/images/cdc-pipeline-architecture.png\"\u003e\u003c/p\u003e\n\u003cp\u003e파이프라인은 1년 넘게 잘 동작했습니다. 데이터 정합성도 검증됐고, 운영도 안정적이었습니다. 문제는 데이터 플랫폼 TCO 분석을 하면서 S3 비용을 뜯어보기 전까지는 보이지 않았습니다.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"s3-비용-스캔량만-보면-안-됩니다\"\u003eS3 비용, 스캔량만 보면 안 됩니다\u003c/h2\u003e\n\u003cp\u003eAWS에서 Athena를 쓰는 팀이라면 대부분 이렇게 알고 있습니다:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u0026ldquo;Athena 비용 = 스캔한 데이터량 × $5/TB\u0026rdquo;\u003c/p\u003e","title":"CDC 파이프라인의 숨겨진 비용: Small File이 만드는 S3 Request 폭탄"},{"content":"안녕하세요, 장재혁입니다.\n인도 핀테크 회사에서 데이터 플랫폼을 만들고 운영하고 있습니다. Spark, Flink, Debezium, Airflow, Iceberg 같은 것들을 주로 다루고, 요즘은 데이터 스택 모던화와 AI-ready 데이터 플랫폼 설계에 관심이 많습니다.\n운영하면서 배운 것들을 틈틈이 기록해보려 합니다.\nTech Stack Processing: Spark, Flink, dbt Streaming \u0026amp; CDC: Debezium, Kafka, Kinesis, NiFi Storage \u0026amp; DW: S3, Athena, Iceberg, DynamoDB Orchestration: Airflow DevOps \u0026amp; Infra: Docker, Jenkins, SAM, CloudFormation, Lake Formation Monitoring: Prometheus, Grafana, CloudWatch, Kibana Cloud: AWS Languages: Python, SQL Writing CDC가 데이터 플랫폼을 바꾸는 방식: CDC-based Incremental Replication CDC 파이프라인을 Debezium과 Flink로 재설계한 이유 NiFi에서 Apache Flink로, 실시간 SMS 파이프라인 개선기 Contact LinkedIn GitHub Email: jjhyuk92@gmail.com ","permalink":"https://backpressured.dev/about/","summary":"\u003cp\u003e안녕하세요, 장재혁입니다.\u003c/p\u003e\n\u003cp\u003e인도 핀테크 회사에서 데이터 플랫폼을 만들고 운영하고 있습니다. Spark, Flink, Debezium, Airflow, Iceberg 같은 것들을 주로 다루고, 요즘은 데이터 스택 모던화와 AI-ready 데이터 플랫폼 설계에 관심이 많습니다.\u003c/p\u003e\n\u003cp\u003e운영하면서 배운 것들을 틈틈이 기록해보려 합니다.\u003c/p\u003e\n\u003ch2 id=\"tech-stack\"\u003eTech Stack\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eProcessing:\u003c/strong\u003e Spark, Flink, dbt\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStreaming \u0026amp; CDC:\u003c/strong\u003e Debezium, Kafka, Kinesis, NiFi\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eStorage \u0026amp; DW:\u003c/strong\u003e S3, Athena, Iceberg, DynamoDB\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eOrchestration:\u003c/strong\u003e Airflow\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDevOps \u0026amp; Infra:\u003c/strong\u003e Docker, Jenkins, SAM, CloudFormation, Lake Formation\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMonitoring:\u003c/strong\u003e Prometheus, Grafana, CloudWatch, Kibana\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCloud:\u003c/strong\u003e AWS\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLanguages:\u003c/strong\u003e Python, SQL\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"writing\"\u003eWriting\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://blog.afinit.com/cdc-incremental-replication\"\u003eCDC가 데이터 플랫폼을 바꾸는 방식: CDC-based Incremental Replication\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.afinit.com/cdc-pipeline-debezium-flink\"\u003eCDC 파이프라인을 Debezium과 Flink로 재설계한 이유\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://blog.afinit.com/nifi-apache-flink-sms-pipeline\"\u003eNiFi에서 Apache Flink로, 실시간 SMS 파이프라인 개선기\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"contact\"\u003eContact\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://linkedin.com/in/jaehyuk-jang-bab185178\"\u003eLinkedIn\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/jaehyukjang\"\u003eGitHub\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eEmail: \u003ca href=\"mailto:jjhyuk92@gmail.com\"\u003ejjhyuk92@gmail.com\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"About"}]