데이터 계약(Data Contract): schema, SLA, owner, breaking change
암묵적 계약이 파이프라인을 깨뜨린다
소스 팀이 테이블에서 컬럼 하나를 이름 바꿨다. 그들 입장에서는 사소한 내부 리팩터링이었다. 하지만 그 컬럼을 직접 참조하던 세 개의 dbt 모델이 조용히 NULL을 채우기 시작했고, 두 개의 BI 대시보드가 어제부터 잘못된 수치를 보여주고 있다. 아무도 통보받지 못했다.
이것이 암묵적 계약의 문제다. 소스가 "이 데이터를 이 형태로 이 시간까지 제공하겠다"는 약속을 한 적이 없으니, 어기는 것도 인지하지 못한다. 데이터 계약(Data Contract)은 이 관계를 명시적으로 바꾼다. 데이터 생산자와 소비자 사이의 기대치를 코드로 문서화하고, 위반 시 파이프라인을 멈추는 메커니즘을 만든다.
데이터 계약이란 무엇인가
데이터 계약은 데이터 생산자와 소비자 사이의 명시적 합의다. 다음 네 가지를 정의한다.
- Schema: 어떤 필드가, 어떤 타입으로, 어떤 제약과 함께 제공되는가
- SLA: 언제까지, 얼마나 자주, 얼마나 완전하게 데이터가 도착하는가
- Owner: 누가 이 데이터를 소유하고, 문제가 생기면 누구에게 연락하는가
- Change policy: 스키마나 SLA가 바뀔 때 어떤 절차를 따르는가
계약은 YAML 파일로 소스 저장소 또는 데이터 플랫폼 저장소에 커밋된다. "살아 있는 문서"처럼 사용되는 Confluence 페이지나 스프레드시트와 달리, 버전 관리되고, 자동 검증되고, CI/CD와 연동된다.
계약의 구성 요소
Not Null / Unique
허용 값 범위
의미 정의
가용성(Availability)
완전성(Completeness)
레이턴시
기술 담당자
비즈니스 담당자
에스컬레이션 경로
Deprecation 기간
Breaking 알림 절차
호환성 정책
1. Schema
스키마는 계약의 핵심이다. 필드명, 데이터 타입, nullable 여부, 허용 값 범위, 필드의 의미(semantic definition)를 포함한다.
# 예시: ODCS v3 형식의 스키마 정의 (일부)
schema:
- name: user_id
dataType: string
required: true
description: "전역 유니크 사용자 식별자"
examples: ["usr_abc123"]
- name: event_type
dataType: string
required: true
enum: ["click", "view", "purchase", "refund"]
description: "이벤트 종류. purchase와 refund는 amount 필드가 반드시 존재해야 함."
- name: amount
dataType: decimal(18,2)
required: false
description: "거래 금액. event_type이 purchase 또는 refund일 때만 not null."
- name: created_at
dataType: timestamp
required: true
description: "이벤트 발생 시각 (UTC)"스키마 정의에서 중요한 것은 의미(semantic definition)다. user_id가 UUID인지, 내부 정수 ID인지, 외부 시스템의 식별자인지를 타입만으로는 알 수 없다. 필드가 무엇을 의미하는지 명시하지 않으면 소비자마다 다르게 해석한다.
2. SLA
SLA는 데이터가 언제까지, 얼마나 완전하게 도착해야 하는지 정의한다.
| SLA 항목 | 예시 | 측정 방법 |
|---|---|---|
| 신선도(Freshness) | 매시간 10분 이내 갱신 | 마지막 파티션의 created_at vs 현재 시각 |
| 가용성(Availability) | 99.5% (월 기준) | 파이프라인 성공률 |
| 완전성(Completeness) | 기대 행 수의 95% 이상 | row count 모니터링 |
| 레이턴시 | CDC 이벤트 소스 커밋 후 5분 이내 도착 | 이벤트 타임스탬프 기준 |
SLA가 빠진 계약은 반쪽짜리다. "데이터를 제공한다"는 약속만 있고 "언제까지"가 없으면 소비자는 자신만의 기대를 갖게 되고, 그 기대가 어긋날 때마다 분쟁이 생긴다.
3. Owner
소유권은 두 역할로 나눈다.
- 기술 소유자(Technical owner): 파이프라인을 운영하고, 장애를 수리하고, 스키마 변경을 검토하는 엔지니어
- 비즈니스 소유자(Business owner): 이 데이터가 무엇을 의미하는지 알고, 비즈니스 정의 변경 시 최종 결정권을 갖는 담당자
두 역할이 분리되어야 하는 이유는 엔지니어링 변경과 비즈니스 의미 변경이 서로 다른 승인 경로를 필요로 하기 때문이다. 컬럼 이름 변경은 기술 소유자가 검토할 수 있지만, revenue의 정의를 "세전"에서 "세후"로 바꾸는 것은 비즈니스 소유자의 승인이 필요하다.
4. Change Policy
변경 정책은 계약에서 가장 자주 빠지는 부분이지만 운영에서 가장 중요하다.
Breaking Change: 무엇이 계약 위반인가
모든 스키마 변경이 위험한 것은 아니다. 변경을 호환 가능(backward-compatible)과 파괴적(breaking)으로 나눠야 한다.
호환 가능 변경 (소비자 코드 수정 불필요)
- nullable 컬럼 추가
- 기존 string 컬럼의 최대 길이 증가
- 새로운 enum 값 추가 (소비자가 unknown 처리하는 경우)
- 컬럼에 기본값 추가
Breaking Change (소비자 코드 수정 필요)
- 컬럼 삭제
- 컬럼 이름 변경
- 데이터 타입 변경 (string → integer 등)
- nullable 컬럼을 not null로 변경
- enum 값 제거 또는 변경
- SLA 기준 완화 (신선도 기준을 1시간에서 6시간으로)
- 테이블 분할 또는 병합
- 소비자 목록 식별
- 마이그레이션 가이드 제공
- 새 버전 계약 초안
- dual-write 또는 alias
- 소비자 이전 현황 추적
- 주간 진행 알림
- 접근 로그 공유
- 이전 지원 마감 공지
- 미이전 소비자 영향 처리
- 계약 버전 archive
Breaking change는 반드시 이전 버전과 병렬로 일정 기간 운영한 후 제거해야 한다. 즉각 제거는 소비자에게 예측 불가능한 장애를 만든다.
계약의 버전 관리
데이터 계약은 소프트웨어 API와 같이 Semantic Versioning을 사용한다.
v{major}.{minor}.{patch}
major: breaking change (소비자 코드 수정 필요)
minor: 하위 호환 추가 (새 컬럼, 새 enum 값)
patch: 비기능 변경 (설명 수정, 담당자 변경)버전 번호가 올라가는 것은 기술적 사실일 뿐이다. 중요한 것은 major 버전이 올라갈 때 소비자가 충분한 이전 시간을 갖도록 하는 프로세스가 실제로 동작하는지다.
YAML 계약 구조 (ODCS v3 기반)
Open Data Contract Standard (ODCS) v3은 데이터 계약의 표준 YAML 형식을 정의한다.
apiVersion: v3.1.0
kind: DataContract
id: urn:datacontract:payments:order-events:v2
name: order-events
version: 2.1.0
status: active
info:
owner: payments-team
contact:
name: 결제 플랫폼 팀
email: [email protected]
url: https://wiki.company.com/data/order-events
servers:
production:
type: kafka
topic: order-events
format: avro
schema:
- name: order_id
dataType: string
required: true
- name: user_id
dataType: string
required: true
- name: status
dataType: string
enum: ["created", "paid", "cancelled", "refunded"]
- name: amount
dataType: decimal(18,2)
required: false
quality:
- type: completeness
description: "order_id and user_id must not be null"
query: "SELECT count(*) FROM order_events WHERE order_id IS NULL OR user_id IS NULL"
mustBe: 0
- type: freshness
description: "최신 이벤트가 1시간 이내여야 함"
mustBeLessThan: 60
unit: minutes
servicelevels:
availability:
description: "월 99.5% 이상"
percentage: 99.5%
freshness:
description: "소스 커밋 후 5분 이내 Kafka 도착"
threshold: 5
unit: minutes이 파일이 Git에 커밋되면 다음이 가능해진다.
- PR 리뷰로 계약 변경을 제어
- CI에서 실제 데이터와 계약의 일치 여부 자동 검증
- Schema Registry와 연동해 Kafka/Avro 호환성 강제
- 카탈로그 도구(DataHub, OpenMetadata 등)에서 계약 연결
계약 위반 시 파이프라인을 어떻게 멈출 것인가
계약이 있어도 위반을 감지하지 못하면 의미가 없다. 두 가지 접근을 결합한다.
1. Ingestion 시점 검증: 데이터가 플랫폼에 들어오는 순간 계약과 비교한다. 스키마가 맞지 않으면 dead-letter queue(DLQ)로 보내고 파이프라인을 중단하거나 알림을 보낸다.
2. 변환 후 검증(Post-transform assertion): dbt test, Great Expectations 같은 도구로 변환 완료 후 데이터 품질을 검증한다. 이 단계에서 실패하면 serving 레이어로의 배포를 막는다.
| 위반 유형 | Ingestion 감지 | Transform 후 감지 |
|---|---|---|
| 컬럼 삭제 | ✓ (즉시 감지) | ✓ (NULL 이상 감지) |
| 타입 변경 | ✓ (파싱 오류) | △ (캐스팅 성공 시 통과) |
| 신선도 SLA 위반 | ✓ (도착 시각 확인) | △ (이미 늦게 처리) |
| 의미 변경 | ✗ (감지 불가) | ✗ (감지 불가, 사람이 판단) |
의미 변경(semantic drift)은 자동으로 감지하기 가장 어렵다. amount의 정의가 세전에서 세후로 바뀌어도 스키마는 그대로다. 이것은 계약의 business owner 변경 승인 절차로만 통제할 수 있다.
계약 없는 세계와의 비교
| 상황 | 계약 없음 | 계약 있음 |
|---|---|---|
| 소스 컬럼 삭제 | 파이프라인이 조용히 NULL 채움, 며칠 후 BI 이상 발견 | Ingestion 즉시 실패, 알림 발송 |
| 신선도 SLA 위반 | 대시보드가 오래된 데이터 표시, 사용자 티켓 발생 | 모니터링 알림, 사전 공지 |
| 소비자가 컬럼 의존 여부 불확실 | 생산자가 변경 전 모든 코드 직접 탐색 | lineage 기반 영향도 분석 즉시 가능 |
| 담당자 불명확 | 장애 발생 시 핑퐁 | 계약에 owner 명시, 에스컬레이션 경로 존재 |
실전 체크리스트
데이터 계약을 처음 만들 때
- [ ] 생산자 팀과 소비자 팀 모두 계약 내용에 동의했는가?
- [ ] 모든 필드의 의미(semantic)가 명시되어 있는가?
- [ ] SLA가 측정 가능한 숫자로 정의되어 있는가?
- [ ] 기술 소유자와 비즈니스 소유자가 분리되어 있는가?
- [ ] Breaking change 정의와 deprecation 기간이 합의되어 있는가?
- [ ] 계약 검증이 CI/CD에 통합되어 있는가?
- [ ] 위반 시 알림 경로와 대응 프로세스가 정해져 있는가?
Breaking change를 처리할 때
- [ ] 변경이 breaking인지 backward-compatible인지 분류했는가?
- [ ] 모든 소비자를 lineage로 식별했는가?
- [ ] 충분한 deprecation 기간(최소 30일)을 설정했는가?
- [ ] 구 버전과 신 버전 병렬 운영 계획이 있는가?
- [ ] 마이그레이션 가이드를 소비자에게 전달했는가?
- [ ] 이전 완료 여부를 추적하는 수단이 있는가?
기억할 문장
데이터 계약은 소스와 플랫폼 사이의 신뢰 메커니즘이다. 계약이 없으면 소스 팀은 변경의 downstream 영향을 알 수 없고, 플랫폼 팀은 "언제 무엇이 바뀔지 모르는 상태"로 운영된다. 좋은 계약은 양쪽이 더 빠르게 움직일 수 있게 한다. 생산자는 계약 범위 안에서 자유롭게 내부를 바꿀 수 있고, 소비자는 계약이 지켜지는 한 안심하고 의존할 수 있다.
다음 장에서는 계약과 연결된 데이터 품질(Data Quality): completeness, freshness, uniqueness, validity, reconciliation을 다룬다.
References
- bitol-io, Open Data Contract Standard (ODCS) — https://github.com/bitol-io/open-data-contract-standard
- Acceldata, How Data Contracts Enforce Pipeline Stability & Quality — https://www.acceldata.io/blog/how-data-contracts-guarantee-pipeline-reliability-data-quality-slas
- Monte Carlo, Data Contracts Explained — https://montecarlo.ai/blog-data-contracts-explained
- Branch Boston, Data Contracts vs Data SLAs — https://branchboston.com/data-contracts-vs-data-slas-ensuring-data-quality-at-scale/
- Medium, Data Contracts: 9 Versioning Rules That End Schema Wars — https://medium.com/@bhagyarana80/data-contracts-9-versioning-rules-that-end-schema-wars-ac58badddc2c
- DEV Community, Data Contract Framework Implementation Guide — https://dev.to/thesius_code_7a136ae718b7/data-contract-framework-data-contract-framework-implementation-guide-hhj