왜 CDC가 필요한가?
운영 데이터베이스(OLTP)에서는 데이터가 끊임없이 INSERT, UPDATE, DELETE됩니다. 이러한 변경사항을 분석 시스템(데이터 레이크하우스)에 반영해야 하는데, 매번 전체 테이블을 복사하는 것은 비효율적입니다.💡 CDC(Change Data Capture) 는 소스 데이터베이스에서 발생하는 변경사항만 감지하여 타겟 시스템에 반영하는 기술입니다. 전체 데이터를 매번 복사하는 대신, 변경된 부분만 캡처하므로 네트워크 대역폭, 처리 시간, 비용을 크게 절감할 수 있습니다.
전체 복사 vs CDC 비교
| 비교 항목 | 전체 복사 (Full Load) | CDC (변경분만) |
|---|---|---|
| 데이터 전송량 | 전체 테이블 크기 | 변경분만 (전체의 1~5% 수준) |
| 처리 시간 | 테이블 크기에 비례 | 변경량에 비례 |
| 소스 부하 | 높음 (전체 스캔) | 낮음 (로그 기반) |
| 실시간성 | 낮음 (배치 주기에 의존) | 높음 (실시간/준실시간 가능) |
| 이력 추적 | 불가능 (최신 스냅샷만) | 가능 (변경 이력 보존) |
CDC 데이터 흐름
| 단계 | 구성 요소 | 설명 |
|---|---|---|
| 1 | 운영 DB (MySQL, PostgreSQL 등) | 변경 로그(Binlog, WAL)를 생성합니다 |
| 2 | CDC 도구 (Debezium, Lakeflow Connect) | CDC 이벤트(JSON/Avro)를 전달합니다 |
| 3 | Bronze (원본 CDC 레코드) | 원본 CDC 레코드를 저장합니다 |
| 4 | Silver (최신 상태 또는 이력) | APPLY CHANGES INTO로 최신 상태 또는 이력을 관리합니다 |
| 5 | Gold (비즈니스 뷰) | 비즈니스 요구에 맞는 뷰를 생성합니다 |
SCD(Slowly Changing Dimension) 유형
CDC로 캡처한 변경사항을 타겟 테이블에 어떻게 반영 할지 결정하는 전략을 SCD라고 합니다.💡 SCD(Slowly Changing Dimension) 는 데이터 웨어하우스에서 “서서히 변하는 데이터”(예: 고객 주소, 직원 부서)를 어떻게 관리할지 정의하는 설계 패턴입니다. Ralph Kimball이 제시한 개념으로, Type 0부터 Type 4까지 다양한 유형이 있습니다.
SCD 유형 비교
| 유형 | 전략 | 이력 보존 | 설명 | 예시 |
|---|---|---|---|---|
| Type 0 | 변경 금지 | 해당 없음 | 최초 값을 유지하며, 절대 변경하지 않습니다 | 고객 최초 가입일 |
| Type 1 | 최신값 덮어쓰기 | ❌ | 기존 값을 새 값으로 교체합니다. 이전 값은 사라집니다 | 고객 현재 주소 |
| Type 2 | 이력 추적 | ✅ | 변경 시 새 행을 추가하고, 기존 행을 만료 처리합니다 | 고객 주소 변경 이력 |
| Type 3 | 이전/현재 컬럼 | 부분적 | 이전 값과 현재 값을 별도 컬럼에 저장합니다 | 이전 부서 + 현재 부서 |
| Type 4 | 별도 이력 테이블 | ✅ | 현재 테이블과 이력 테이블을 분리합니다 | 현재 테이블 + 이력 테이블 |
💡 SDP에서는 가장 많이 사용되는 Type 1 과 Type 2 를 APPLY CHANGES INTO 구문으로 직접 지원합니다.
APPLY CHANGES INTO 구문 상세
기본 구조
주요 절(Clause) 설명
| 절 | 필수 여부 | 설명 |
|---|---|---|
KEYS | ✅ 필수 | 소스와 타겟에서 레코드를 매칭하는 키 컬럼입니다. 복합 키 가능합니다 |
SEQUENCE BY | ✅ 필수 | 동일 키에 대한 여러 변경 이벤트의 순서를 결정합니다. 타임스탬프 또는 시퀀스 번호를 사용합니다 |
COLUMNS | 선택 | COLUMNS *(전체), COLUMNS * EXCEPT (...), COLUMNS (a, b, c) 형태로 지정합니다 |
WHERE | 선택 | 소스 데이터에서 특정 조건의 레코드만 처리합니다 |
APPLY AS DELETE WHEN | 선택 | 조건이 참인 레코드를 삭제로 처리합니다 |
APPLY AS TRUNCATE WHEN | 선택 | 조건이 참이면 타겟 테이블 전체를 삭제합니다 |
STORED AS SCD TYPE | ✅ 필수 | 1 (최신값 덮어쓰기) 또는 2 (이력 추적)를 지정합니다 |
TRACK HISTORY ON | 선택 | (Type 2 전용) 특정 컬럼이 변경될 때만 새 이력 행을 생성합니다 |
SCD Type 1 구현: 최신값 덮어쓰기
SCD Type 1은 변경이 발생하면 기존 값을 최신 값으로 교체 합니다. 이력을 보존하지 않으며, 항상 현재 상태 만 유지합니다.SQL 예제
Python 예제
동작 예시
소스 CDC 레코드:| customer_id | name | city | updated_at | operation |
|---|---|---|---|---|
| 101 | 김철수 | 서울 | 2024-01-01 | INSERT |
| 101 | 김철수 | 부산 | 2024-03-15 | UPDATE |
| 101 | 김철수 | 대구 | 2024-06-20 | UPDATE |
| customer_id | name | city | updated_at |
|---|---|---|---|
| 101 | 김철수 | 대구 | 2024-06-20 |
항상 최신 상태만 유지됩니다. “김철수가 서울에 살았다”는 이력은 사라집니다.
SCD Type 2 구현: 이력 추적
SCD Type 2는 변경이 발생하면 기존 레코드를 만료 처리하고 새 레코드를 추가 합니다. 전체 변경 이력이 보존됩니다.SQL 예제
자동 생성되는 메타 컬럼
SCD Type 2로 저장하면 다음 시스템 컬럼이 자동으로 추가됩니다.| 컬럼 | 데이터 타입 | 설명 |
|---|---|---|
__START_AT | TIMESTAMP | 해당 버전의 유효 시작 시간 (SEQUENCE BY 컬럼의 값) |
__END_AT | TIMESTAMP | 해당 버전의 유효 종료 시간. NULL이면 현재 유효한 레코드 입니다 |
동작 예시
소스 CDC 레코드 (동일한 데이터):| customer_id | name | city | updated_at | operation |
|---|---|---|---|---|
| 101 | 김철수 | 서울 | 2024-01-01 | INSERT |
| 101 | 김철수 | 부산 | 2024-03-15 | UPDATE |
| 101 | 김철수 | 대구 | 2024-06-20 | UPDATE |
| customer_id | name | city | __START_AT | __END_AT |
|---|---|---|---|---|
| 101 | 김철수 | 서울 | 2024-01-01 | 2024-03-15 |
| 101 | 김철수 | 부산 | 2024-03-15 | 2024-06-20 |
| 101 | 김철수 | 대구 | 2024-06-20 | NULL |
__END_AT이 NULL인 행이 현재 유효한 최신 레코드 입니다.
현재 유효한 레코드만 조회
TRACK HISTORY ON: 선택적 이력 추적
모든 컬럼 변경이 아닌, 특정 컬럼이 변경될 때만 이력을 기록하고 싶다면TRACK HISTORY ON 절을 사용합니다.
💡TRACK HISTORY ON *(기본값)은 모든 컬럼 변경을 추적합니다.TRACK HISTORY ON * EXCEPT (last_login_at)형태로 특정 컬럼을 제외할 수도 있습니다.
DELETE 처리
Soft Delete (삭제 표시만)
CDC 소스에서 DELETE 이벤트를 받으면, 타겟에서 해당 레코드를 실제로 삭제하는 대신 삭제 표시 만 할 수 있습니다. SCD Type 2에서는__END_AT이 설정되어 만료 처리됩니다.
SCD Type 2에서 DELETE를 적용하면, 해당 레코드의 __END_AT이 설정되어 “만료됨”으로 표시됩니다. 물리적으로 삭제되지는 않습니다.
Hard Delete (물리적 삭제)
SCD Type 1에서 DELETE를 적용하면, 해당 레코드가 타겟 테이블에서 물리적으로 삭제 됩니다.DELETE 처리 비교
| 항목 | SCD Type 1 + DELETE | SCD Type 2 + DELETE |
|---|---|---|
| 물리적 삭제 | ✅ 행이 실제로 삭제됩니다 | ❌ 행은 유지됩니다 |
| 삭제 이력 | ❌ 추적 불가 | ✅ __END_AT 설정으로 추적 가능 |
| 복원 가능 | ❌ 원본 CDC 로그에서만 가능 | ✅ 이력에서 확인 가능 |
소스별 CDC 패턴
CDC 데이터를 생성하는 도구에 따라 이벤트 형식이 다릅니다.Debezium 형식 처리
Debezium은 오픈소스 CDC 도구로, Kafka를 통해 변경 이벤트를 전달합니다.Lakeflow Connect
Databricks의 관리형 커넥터인 Lakeflow Connect를 사용하면 CDC 데이터가 표준화된 형식으로 자동 수집됩니다.커스텀 CDC (Application 레벨)
애플리케이션에서 직접 변경 로그를 생성하는 경우:실습: CDC 파이프라인 전체 예제
고객 데이터의 CDC 처리를 Bronze → Silver → Gold 전체 과정으로 구현하는 예제입니다.성능 고려사항
대용량 CDC 처리 팁
| 항목 | 권장 사항 |
|---|---|
| 파티셔닝 | 타겟 테이블에 적절한 파티션을 설정하면 MERGE 성능이 향상됩니다 |
| 키 선택 | 키 컬럼의 카디널리티가 높을수록 효율적입니다. 복합 키는 최소화합니다 |
| 시퀀스 컬럼 | 타임스탬프보다 단조 증가하는 시퀀스 번호가 더 안정적입니다 |
| 배치 크기 | Auto Loader의 maxFilesPerTrigger 또는 maxBytesPerTrigger로 배치 크기를 조절합니다 |
| 불필요한 컬럼 제외 | COLUMNS * EXCEPT (...)로 메타데이터 컬럼을 제외하여 저장 공간을 절약합니다 |
순서 보장
⚠️ SEQUENCE BY 컬럼은 동일한 키에 대해 변경 순서를 올바르게 반영 해야 합니다. 동일한 키에 대해 같은 시퀀스 값을 가진 여러 레코드가 있으면, 하나만 임의로 선택됩니다. 타임스탬프의 정밀도가 충분하지 않은 경우 별도의 시퀀스 번호 컬럼 사용을 권장합니다.
정리
| 핵심 포인트 | 설명 |
|---|---|
| CDC의 목적 | 소스 DB의 변경사항만 캡처하여 효율적으로 레이크하우스에 반영합니다 |
| SCD Type 1 | 최신값으로 덮어쓰기. 이력 보존이 불필요한 경우에 사용합니다 |
| SCD Type 2 | 이력 추적. __START_AT/__END_AT 컬럼으로 유효 기간을 관리합니다 |
| APPLY CHANGES INTO | SDP에서 CDC를 처리하는 핵심 구문입니다 |
| SEQUENCE BY | 동일 키에 대한 변경 순서를 보장합니다. 필수입니다 |
| DELETE 처리 | Type 1은 물리적 삭제, Type 2는 만료 처리(soft delete)로 동작합니다 |