> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sifi.life/llms.txt
> Use this file to discover all available pages before exploring further.

# AUTO CDC를 한 단계 더: 가장 까다로운 실무 사용 사례 풀어내기

> 이중 시간축 추적, 부분 업데이트 GA, 그리고 Apache Spark 기여로 확장되는 AUTO CDC

> **원문**: [Taking AUTO CDC to the Next Level: Solving the Hardest Real-World Use Cases](https://www.databricks.com/blog/taking-auto-cdc-next-level-solving-hardest-real-world-use-cases)
> **저자**: Josh Seidel, Shanelle Roman, Sudhanva Huruli
> **게시일**: 2026년 8월 11일

## 요약

* **Bitemporal AUTO CDC**는 비즈니스 시간(사실이 실제로 참이었던 때)과 시스템 시간(시스템이 그 사실을 알게 된 때)이라는 두 개의 독립적인 시간축을 추적해, SEC Rule 17a-4 같은 규제 요건을 충족합니다.
* \*\*부분 업데이트(Partial Updates)가 정식 출시(GA)\*\*되어, 변경된 필드만 보내는 CDC 소스가 NULL로 기존 데이터를 덮어쓰지 않도록 자동 처리됩니다.
* Databricks는 **AUTO CDC Type 1의 Python API를 Apache Spark 4.2에 기여**하며 오픈소스 약속을 이어 갑니다.

***

변경 데이터 캡처(Change data capture, CDC)는 데이터 엔지니어가 Spark 위에서 만드는 가장 흔한 작업 중 하나이자, 손으로 제대로 해내기 가장 성가신 작업 중 하나입니다. 이전 글 [Stop hand-coding change data capture pipelines](https://www.databricks.com/blog/stop-hand-coding-change-data-capture-pipelines)에서 우리는 Apache™ Spark Declarative Pipelines(SDP)의 AUTO CDC가 어떻게 수백 줄의 취약한 MERGE 로직을 몇 개의 간단한 선언으로 대체해 SCD Type 1, SCD Type 2, Snapshot CDC를 자동화하는지 소개했습니다.

파이프라인 요구 사항이 진화하면서, 엔지니어들은 표준 CDC 패턴으로는 풀기 어려운 상황에 부딪힙니다.

* 순서가 뒤섞인(out-of-order) 이중 시간(bitemporal) 타임라인 처리
* 기존 데이터를 손상시키지 않으면서 부분 레코드 업데이트 처리
* 스토리지 보존 기간(retention window)보다 오래 살아남는 감사 가능성(auditability) 유지

오늘 우리는 이런 실무적 난제를 정확히 풀어내기 위해 AUTO CDC를 한 단계 더 끌어올리고, 이 기능들을 오픈소스 Apache Spark 4.2로 확장합니다.

## Bitemporal AUTO CDC로 이중 시간축 이력 추적하기

표준 SCD Type 2 테이블은 어떤 사실이 실제 세계에서 언제 바뀌었는지는 알려 줄 수 있지만, 어느 시점에 여러분의 시스템이 무엇을 믿고 있었는지는 알려 주지 못합니다.

SEC Rule 17a-4와 FINRA 기록 보존 규정에 따라, 기업은 특정 시점에 존재했던 그대로 레코드를 재구성할 수 있어야 합니다. SEC의 기록 보존 단속(sweep)만으로도 2021년 이후 100곳이 넘는 기업에 20억 달러 이상의 벌금이 부과되었습니다. 어려운 부분은 오늘의 값을 저장하는 것이 아닙니다. 몇 달 뒤에, 보고일(reporting date)에 참조 데이터가 무엇이라 말했는지, 그리고 그 당시 우리 시스템이 무엇을 믿고 있었는지에 답하는 것입니다.

표준 SCD Type 2는 하나의 타임라인 — 어떤 사실이 언제 바뀌었는지 — 을 추적합니다. Bitemporal AUTO CDC는 두 개를 독립적으로 추적합니다.

* **비즈니스 시간(business time)**(이벤트 시간 또는 유효 시간이라고도 함): 그 사실이 실제 세계에서 실제로 참이었던 때. 어떤 종목 심볼이 월요일에 보고 대상이 되었다거나, 어떤 국가 코드가 분기 말에 폐기되었다는 식입니다.
* **시스템 시간(system time)**(트랜잭션 시간 또는 처리 시간이라고도 함): 기록 시스템(system of record)이 그 데이터를 알게 된 때. 월요일의 변경이 수요일이 되어서야 파이프라인에 도착할 수도 있습니다.

각 대상 테이블은 네 개의 시스템 관리 컬럼을 갖습니다. 비즈니스 시간을 위한 `__START_AT`, `__END_AT`, 그리고 시스템 시간을 위한 `__SYSTEM_START_AT`, `__SYSTEM_END_AT`입니다. 하나의 논리적 사실은 여러 개의 물리적 행을 가질 수 있는데, 비즈니스 버전/시스템 버전 조합마다 하나씩입니다. 이것이 두 축 어느 쪽으로든 시점 재구성(point-in-time reconstruction)을 가능하게 합니다. 핵심적인 동작 보장은 다음과 같습니다: 이벤트는 어느 타임라인에서든 어떤 순서로도 도착할 수 있습니다.

이미 처리된 것보다 이른 비즈니스 시간이나 시스템 시간을 가진 정정(correction)이 나타나면, 엔진은 그저 끝에 덧붙이는 대신 영향받는 이력을 다시 씁니다. 손으로 짠 로직 없이, 두 개의 시퀀싱(sequencing) 컬럼만 선언하면 엔진이 두 구간(interval)을 모두 관리합니다. 이는 심볼 마스터 같은 디멘션 테이블에도, 엄격한 감사 가능성이 필요한 거래 이력이나 센서 판독값 같은 팩트 테이블에도 똑같이 잘 동작합니다. [FINRA CAT 참조 데이터](https://www.catnmsplan.com/reference-data)에 대해 이것이 어떤 모습인지 다음과 같습니다.

정확한 SQL 절은 `STORED AS SCD TYPE BITEMPORAL`이 아니라 `STORED AS BITEMPORAL`이며, `SEQUENCE BY`와 `SYSTEM SEQUENCE BY`가 모두 필요합니다. Acme의 보고 대상 플래그(reportable flag)가 1월 1일(비즈니스 시간)에 바뀌었지만, 피드는 1월 5일(시스템 시간)이 되어서야 이를 수신했다고 합시다. 그리고 1월 8일에 백데이트(back-dated)된 정정이 도착해, 실제 변경은 1월 1일이었으나 값이 다르다고 말합니다. Bitemporal AUTO CDC는 두 질문에 모두 답할 수 있습니다.

1월 3일에 첫 번째 쿼리는 아무것도 반환하지 않습니다. 그 당시 시스템이 보여 준 바에 대한, 정확하고 감사 가능한 답입니다. 오늘 실행한 두 번째 쿼리는 정정된 진실을 반영합니다. 두 개의 시계, 두 개의 답, 둘 다 옳습니다. 시퀀싱 컬럼은 정렬 가능한(sortable) 타입이어야 하며 NULL 시퀀싱 값이 없어야 합니다. 이 기능은 서버리스 SDP 또는 Pro/Advanced 제품 에디션에서 실행되며, 현재 Beta 단계이므로 파이프라인을 `channel: PREVIEW`로 고정하세요.

## 타임 트래블을 넘어: VACUUM에도 살아남는 재현 가능한 ML

모델이 참조 데이터나 피처(feature) 데이터로 학습되었을 때, 재현 가능성(reproducibility)이란 몇 달 뒤 검토나 감사 과정에서 모델이 사용한 정확한 데이터셋을 재구성할 수 있음을 뜻합니다. 본능적으로는 Delta Lake 타임 트래블에 손이 가지만, 그것은 테이블 파일 이력의 속성이지 영구적인 기록이 아닙니다. `VACUUM`은 최근 버전이 더 이상 참조하지 않는 데이터 파일을 영구적으로 삭제하며, 기본 7일 보존 기간을 지나면 학습 시점에 기록해 둔 `TIMESTAMP AS OF`가 조용히 해석되지 않게 될 수 있습니다. Bitemporal 테이블은 그 이력을 파일 버전이 아니라 데이터로 저장합니다. `VACUUM`과 `OPTIMIZE`는 파일을 압축(compact)할 뿐 논리적 이력은 결코 건드리지 않으므로, 과거의 모든 비즈니스 버전이나 시스템 버전이 여전히 쿼리 가능한 행으로 남습니다. 이로부터 재현 가능성을 얻는 방법은 두 가지입니다: 두 개의 as-of 시점(비즈니스 시간과 시스템 시간)을 MLflow 파라미터로 로깅하고, 학습 쿼리를 그 믿음 상태(belief-state)에 고정하는 것입니다.

또는, 테이블이 현재 뷰(current view)를 노출한다면, 학습 시점에 하나의 시스템 시점만 로깅하고 나중에 그 타임스탬프의 시스템 시간 쿼리로 재구성하는 것입니다.

어느 쪽이든 재현 가능성 계약은 MLflow 실행(run) 안의 타임스탬프 몇 개이며, bitemporal 이력이 행으로 저장되기에 그 계약은 `VACUUM`이 기저 파일을 정리한 뒤에도 유지됩니다.

## AutoCDC 부분 업데이트가 이제 정식 출시(GA)되었습니다

모든 변경 데이터 캡처(CDC) 소스가 업데이트에 대해 완전한 행을 내보내는 것은 아닙니다. 대신 많은 소스는 변경된 필드만 보내고 나머지 모든 컬럼은 NULL로 표현합니다. 특별한 처리 없이는 이 NULL 값이 대상 테이블의 기존 데이터를 의도치 않게 덮어쓸 수 있습니다. 지금까지 고객들은 이 동작을 우회하기 위해 커스텀 로직을 만들어야 했습니다. AutoCDC 부분 업데이트로 이제 이것이 자동으로 처리됩니다.

부분 업데이트는 업데이트 이벤트가 컬럼의 일부만 수정하도록 허용함으로써 AutoCDC를 확장합니다. 선택된 컬럼에 대해, 들어오는 업데이트의 NULL 값은 기존 값을 덮어쓰는 것이 아니라 "업데이트하지 않음"으로 해석됩니다.

이는 변경되지 않은 값을 NULL로 내보내며 생략하는 CDC 소스에 특히 유용합니다. 부분 업데이트가 없으면 이 NULL들이 대상 테이블의 기존 데이터를 덮어쓰게 됩니다.

예를 들어, 대상 테이블에 다음이 들어 있다고 합시다: `(1, 'A', 20)`

들어오는 업데이트 이벤트가 다음을 담고 있다고 합시다: `(1, NULL, 30)`

기본적으로 AutoCDC는 행을 다음으로 업데이트합니다: `(1, NULL, 30)`.

부분 업데이트를 활성화하면, `name`의 NULL은 "기존 값을 그대로 둠"으로 취급되어 결과는 다음이 됩니다: `(1, 'A', 30)`.

부분 업데이트를 활성화하려면 AutoCDC 정의에 파라미터 하나만 추가하면 됩니다. 어떤 컬럼을 부분 업데이트로 취급할지는 세 가지 방식 중에서 지정할 수 있습니다.

* NULL 값을 무시해야 하는 컬럼 목록:
  `IGNORE NULL UPDATES ON columnList`
* NULL 값을 무시하지 *않아야* 하는 컬럼 목록:
  `IGNORE NULL UPDATES ON * EXCEPT (columnList)`
* 행마다 다를 수 있는 소스 컬럼 이름:
  `COLUMNS TO UPDATE`

전체 문법, 예시, 사용 지침은 [Apply Partial Updates 문서](https://docs.databricks.com/aws/en/ldp/cdc-advanced#apply-partial-updates)를 참고하세요.

## 우리는 오픈소스에 대한 약속을 이어 갑니다

Spark Declarative Pipelines는 오픈소스이므로, 가장 널리 쓰이는 그 흐름(flow) 유형 또한 오픈소스여야 합니다. 우리는 그 시작으로 AUTO CDC Type 1의 Python API를 Apache Spark 4.2에 기여합니다.

우리는 이를 Spark의 나머지가 진화하는 방식 그대로 — 일회성 코드 투척이 아니라 리뷰된 제안과 풀 리퀘스트의 연속으로 — 기여했습니다([SPIP](https://lists.apache.org/thread/j6sj9wo9odgdpgzlxtvhoy7szs0jplf7)와 [SPARK-56249](https://issues.apache.org/jira/browse/SPARK-56249) 참고). 순서가 뒤섞인 데이터에 대한 정확성이 기본으로 내장되어 있습니다. 작은 보조 테이블이 삭제 툼스톤(delete tombstone) 같은 조기 도착(early-arriving) 이벤트의 상태를 추적하고, 재시도된 마이크로배치는 대상을 손상시키는 대신 수렴하며, 스토리지 포맷이 아니라 Spark의 스트리밍·테이블 추상 위에 세워졌기 때문에 Delta Lake와 Apache Iceberg 양쪽에서 모두 동작합니다.

다음으로, 공개적으로 이어질 것들입니다.

* **다음 릴리스 기능:** 우리는 이미 SQL 인터페이스(`CREATE FLOW ... AS AUTO CDC INTO`)를 master에 머지했으며, 이는 다음 Apache Spark 릴리스에 실릴 것입니다.
* **고급 파이프라인 의미론:** SCD Type 2 전체 이력 관리, 네이티브 changelog 입력, 그리고 NULL 값이 대상 데이터를 덮어쓰지 못하게 하는 부분 업데이트 지원에 대한 개발이 진행 중입니다.
* **신뢰성 및 테스트:** 순서가 뒤섞인 데이터와 멱등(idempotent) 재시도에 대한 자동화 테스트 스위트를 확장하면서, apply-as-truncate 기능을 추가하고 있습니다.

## 시작하기

이중 시간(bitemporal) 규제 준수를 구현하려 하든, 부분 업데이트를 설정하려 하든, 아니면 Apache Spark의 오픈소스 AutoCDC를 살펴보려 하든, 시작하려면 아래 리소스를 확인하세요.

* [Bitemporal AUTO CDC](https://docs.databricks.com/aws/en/ldp/cdc#how-bitemporal-auto-cdc-works): SDP에서 이중 시간축 이력 추적을 구성하는 방법을 배웁니다.
* [Partial Updates 가이드](https://docs.databricks.com/aws/en/ldp/cdc-advanced#apply-partial-updates): 커스텀 코드 없이 누락된 업데이트 필드를 처리하는 전체 문법과 예시를 봅니다.
* [Open Source AutoCDC 프로그래밍 가이드](https://spark.apache.org/docs/latest/declarative-pipelines-programming-guide.html): Apache Spark 4.2에서 사용 가능한 선언적 CDC 기능을 살펴봅니다.
