DT Platform
1편 · 당근의 데이터 전송 플랫폼
- Haram Lee
- 2026-07-03
- work / Daangn / data-team-notes
원문: 당근 200+개 DB를 옮기는 ELT 플랫폼, DT Platform을 만든 이야기 (2026-06-04)
서비스 DB → BigQuery 전송 파이프라인을 코드 PR 중심으로 관리하던 구조에서, UI로 선언하고 Airflow/Spark가 실행하는 사내 no-code ELT 플랫폼으로 바꾼 이야기.
이 글이 당근 데이터 시스템에서 차지하는 위치
이 글은 당근에서 서비스 DB 데이터를 BigQuery로 옮기는 전체 전송 시스템을 다룬다. 큰 그림에서 DT Platform은 이 위치에 있다.
서비스 DB → [DT Platform: 전송 설정] → Airflow → Spark → BigQuery한마디로 DT Platform은 데이터 전송 파이프라인의 control plane에 가깝다. 실제 데이터를 옮기는 건 Spark이고 스케줄링은 Airflow가 하지만, “어떤 DB의 어떤 테이블을 어떤 주기로 어디로 보낼 것인가"를 선언하고 관리하는 중심은 DT Platform이다.
당근이 마주한 문제 — 설정과 코드의 강결합
당근은 글로벌 서비스 전반에서 200개 이상의 DB로부터 BigQuery로 데이터를 전송한다. 리전(한국·일본·캐나다)도, 환경(alpha·prod)도, 도메인별 요구사항도 제각각이다.
기존 구조의 근원 문제는 파이프라인 정의(설정)와 실행 코드가 하나의 레포에 강하게 묶여 있었다는 점이다. 테이블 하나를 옮기는 데에도 이런 과정이 필요했다.
서비스 팀이 코드 레포 이해 → 설정 코드 수정 → PR 생성
→ 데이터 가치화팀 리뷰 → 배포 → Airflow/Spark 실행이게 만든 Pain Point는 두 방향이었다.
- 데이터 가치화팀: 수백 개 파이프라인의 상태를 한눈에 볼 방법이 없었다. “이 테이블은 어떤 주기로 싱크되지?”, “파티션 키가 뭐지?”, “증분 적재인가 전체 덮어쓰기인가?“를 알려면 Python·Scala 코드를 직접 읽어야 했다. → 리뷰 병목.
- 서비스 팀: 자기 서비스 테이블 하나 추가하려고 관련 없는 ELT 코드베이스를 학습해야 했다. → 결국 요청이 데이터 가치화팀으로 몰리고 병목은 더 심해지는 악순환.
해결 구조 — 설정과 실행의 분리
핵심 아이디어는 단순하다. 설정은 UI에서 선언하게 하고, 실행은 기존 Airflow + Spark가 하게 하자. 이미 잘 돌던 실행 레이어는 그대로 두고, 그 위에 선언적 파이프라인 정의 계층만 새로 만든 것이다.
사용자 경험은 이렇게 바뀐다.
웹 UI에서 Source DB 선택 → 전송할 테이블(스트림) 체크
→ BigQuery Destination 선택 → 스케줄 설정 → 리뷰 요청
→ (데이터 가치화팀 Approve) → 이후 사람 손 필요 없음내부 아키텍처를 한 줄로 그리면:
DT Platform UI
→ 승인 시 설정을 JSON DSL 로 변환해 저장
→ S3 에 DSL Object 저장
→ Airflow Synchronizer DAG 가 10분마다 S3에서 가져옴
→ Scheduler 가 Dynamic DAG Generation 으로 파이프라인별 DAG 생성
→ 스케줄 시점에 EMR on EKS 에 Spark Job 제출
→ Spark 가 Source DB → BigQuery 적재이 그림에서 가장 중요한 연결 지점은 JSON DSL이다. 파이프라인 정의(데이터포털)와 실행(Airflow+Spark)이 S3 위의 JSON DSL 한 장으로 연결된다. 이게 “코드와 설정의 강결합"을 끊어낸 핵심이다. 데이터포털은 UI/워크플로에만, Airflow·Spark는 실행에만 집중하도록 책임이 깨끗하게 분리됐다.
DSL은 Spark가 하던 모든 액션을 표현할 수 있게 설계됐다. 열 제외(dropColumn), 행 필터(filter), DB 부하를 위한 병렬성 제한, JDBC Partition 튜닝, Spark SQL을 통한 변환(transform)까지. 원하면 ELT를 넘어 ETL 플랫폼으로도 쓸 수 있고, 신경 쓰기 싫으면 팀 기본값으로 돌려도 된다. 중요한 건 이 모든 게 no-code로 가능하다는 점이다.
마이그레이션 자동화
이미 프로덕션에서 돌던 수백 개 파이프라인을 옮겨야 했다. 사람이 하나씩(수개월) vs 단순 스크립트(예외가 너무 많음) 둘 다 한계였다. 그래서 Claude Code 기반 마이그레이션 에이전트 + 스킬을 만들었다.
기존 Python/Scala 코드 파싱 → 의미 추출
→ DT Platform API 호출로 파이프라인 생성
→ 기존 DAG에 "Migration 완료" 표시 (중복 처리 방지)여러 엔지니어가 동시에 에이전트를 돌리므로, 중복 마이그레이션을 막기 위해 **Notion DB를 “작업 단위 레지스트리”**로 썼다. 각 row가 “어떤 파이프라인을 누가 언제 처리 중인지"를 나타내고, 에이전트는 시작 전 Notion MCP로 row를 선점(in-progress)한다. Notion이 Single Source of Truth가 되어 충돌을 자연스럽게 직렬화한다. 이 방식으로 MySQL/PostgreSQL 파이프라인 203개를 약 2주 만에 마이그레이션했다.
주요 개념 정리
ELT / ETL ETL은 Extract → Transform → Load(중간에서 변환 후 적재), ELT는 Extract → Load → Transform(먼저 저장소에 적재 후 그 안에서 변환). DT Platform은 서비스 DB → BigQuery 적재를 ELT 맥락으로 다룬다.
Control Plane / Execution Plane Control plane은 “무엇을 할지"를 정의·관리하는 층(DT Platform: 설정·승인 워크플로), Execution plane은 “실제로 어떻게 실행할지"를 담당하는 층(Airflow: 스케줄, Spark: 처리). 온보딩할 때 이 두 층을 구분하면 시스템이 잘 보인다.
DSL / JSON DSL DSL(Domain Specific Language)은 특정 도메인의 설정·로직을 표현하는 언어/스키마. DT Platform은 파이프라인 설정을 JSON DSL로 표현해, 설정이 코드에 흩어지지 않고 하나의 표준 스키마 객체로 관리된다. (source, destination, schedule, partition, transform, filter 등)
Dynamic DAG Generation Airflow DAG를 사람이 하나씩 작성하는 대신, 설정 파일을 읽어 DAG를 동적으로 생성하는 방식. 파이프라인이 많아져도 코드 파일을 늘리지 않고 설정 중심으로 관리할 수 있다.
Self-service Platform 담당 팀에 매번 요청하지 않고, 정해진 UI·워크플로 안에서 사용자가 직접 필요한 작업을 하는 플랫폼. 데이터 가치화팀은 모든 설정을 대신 작성하는 사람이 아니라 플랫폼과 가드레일을 운영하는 역할로 바뀐다.
성과와 이후
- 파이프라인 추가/변경 리드타임 감소. 동시에 요청 수는 오히려 증가 → 연동 허들이 낮아졌다는 신호로 해석.
- 팀은 Web UI로 파이프라인 상태를 코드 없이 파악. 서비스 팀은 코드베이스 학습 없이 설정 생성/변경, 리뷰 요청은 Slack 자동 알림.
- 설정이 하나의 스키마로 통일된 덕분에 그 위에 올릴 수 있는 것이 늘었다: 실행 성공/실패 가시화, 비용 가시화, PII 컬럼 자동 제외, Credential 관리, Spark 튜닝 자동화, Data Transfer MCP.
온보딩 관점 정리
당근 데이터 가치화팀은 파이프라인을 직접 운영하기만 하는 팀이 아니라, 데이터 전송을 조직 전체가 쓰는 플랫폼으로 추상화하는 팀이다. DT Platform은 서비스 DB와 BigQuery 사이의 데이터 이동을 표준화하고, 설정과 실행을 분리하며, 전송 업무를 self-service화한 시스템이다.
더 공부할 것
아래는 원문 밖에서 직접 찾아본 답변. 온보딩용 감을 잡는 수준이고, 실제 구현은 팀 코드로 확인 필요.
Airflow Dynamic DAG Generation은 실제로 어떻게 구현할까?
Airflow는 DAG 파일을 import할 때 top-level 파이썬 코드를 실행한다. 그래서 그 시점에 외부 설정(JSON/YAML/DB)을 읽어 루프를 돌며 globals()[dag_id] = create_dag(cfg)로 DAG 객체를 전역에 등록하면, 스케줄러가 각각을 개별 DAG로 인식한다. 핵심 주의점은 top-level에서 무거운 I/O를 하지 말 것 — 스케줄러가 dagbag을 주기적으로 재파싱하기 때문에 매번 S3를 읽으면 파싱 지연·rate limit이 생긴다. 당근이 Synchronizer DAG으로 S3 → 로컬을 10분마다 내려두고 DAG 생성 코드는 로컬 스냅샷만 읽게 한 게 이 문제를 피하는 패턴이다. (파일별 .py 생성, dag-factory 같은 YAML 팩토리도 대안)
JSON DSL 스키마는 어떤 식으로 설계해야 유지보수하기 좋을까?
최상단에 version 필드를 둬서 스키마 진화에 대응하고, 논리 정의(무엇을)와 실행 세부(리소스·병렬성)를 분리한다. JSON Schema로 유효성 검증을 걸고, 사용자가 안 채운 필드는 스키마 레벨에서 팀 기본값을 주입한다. 하위호환은 additive-only 원칙(필드 추가는 optional, 삭제·의미변경은 version bump)으로 지키고, DB credential 같은 시크릿은 DSL에 직접 넣지 않고 id로 참조(credential store)한다.
Spark JDBC Partitioning은 어떤 기준으로 나누는 게 좋을까?
Spark는 partitionColumn·lowerBound·upperBound·numPartitions로 read를 병렬화하는데, 각 파티션이 WHERE col >= x AND col < y range 쿼리로 나간다. 좋은 파티션 컬럼은 숫자/날짜형 + 인덱스 존재 + 균등 분포여야 한다(인덱스 없으면 full scan 반복, 분포 치우치면 skew). 보통 auto-increment PK나 시간 컬럼을 쓴다. lower/upperBound는 실제 min/max를 미리 조회해 잡아야 하고, numPartitions는 executor 수 및 DB 커넥션 한도와 균형을 맞춘다 — 너무 크면 DB 커넥션이 폭증해 오히려 부하가 된다(그래서 DSL에 병렬성 제한 옵션이 있는 것). skew가 심하면 균등 range 대신 predicate 리스트를 직접 지정한다.
데이터 전송 플랫폼에서 approval workflow는 어디까지 자동화할 수 있을까? 스키마 유효성·PII 컬럼 감지·예상 스캔량/비용 추정·네이밍 규칙·리소스 상한 같은 기계적 검증은 전부 자동화할 수 있고, 정책 위반 시 자동 반려·경고까지 가능하다. 반면 새 DB 접근 권한, 민감 데이터 destination 결정, 비용 큰 파이프라인의 비즈니스 판단은 사람이 남긴다. 발전 방향은 low-risk 변경(기존 파이프라인 컬럼 추가 등)은 auto-approve, high-risk(신규 소스·PII)만 human-in-the-loop으로 두는 것 — 정책을 코드로(policy-as-code) 관리하면 점진적으로 자동화 범위를 넓힐 수 있다.
Data Transfer MCP가 생기면 어떤 API가 필요할까?
DT Platform REST API를 MCP tool로 감싸서 에이전트가 “이 테이블 추가해줘 → DSL 생성 → cost preview → 리뷰 요청"까지 수행하게 하는 그림이다. 필요한 tool은 대략 list_pipelines / get_pipeline / list_streams(source) / preview_cost(dsl) / get_run_status / get_metrics 같은 read·진단 계열과 create_pipeline(dsl) / update_pipeline / suggest_optimization 같은 write 계열. read와 write를 분리하고, write는 승인 게이트를 유지하는 게 안전하다.