
AI 에이전트에게 스키마 변경을 위임하기 위한 설계 원칙
요약
AI 에이전트가 스키마 변경을 수행할 때 발생할 수 있는 위험을 관리하기 위한 '계약 컴파일러(Contract Compiler)' 설계 원칙을 제안합니다. 에이전트의 빠른 변경 속도에 맞춰, 단순한 프롬프트가 아닌 데이터 리니지와 계약 그래프를 활용한 자동화된 검증 메커니즘의 필요성을 다룹니다.
핵심 포인트
- 에이전트의 빠른 변경 속도에 대응하는 검증 메커니즘의 부재가 주요 위험 요소임
- 빌드 성공 여부보다 데이터 계약(Contract)을 깨뜨리지 않는 것이 핵심 과제임
- 계약 컴파일러를 통해 ORM, SQL, SDK 등을 하나의 그래프로 연결하여 안전성을 판정해야 함
- 하위 호환성, 모델 정합성, PII 도달 범위 등을 주요 검증 대상으로 설정함
Claude Code나 Codex와 같은 AI 에이전트에게 "accounts 테이블에 plan_tier를 추가하고, 관련 부분까지 반영해줘"라고 요청하면, ORM 스키마, SQL 마이그레이션 (Migration), dbt의 source, 분석 모델, SDK 타입, 테스트까지 한꺼번에 업데이트해 줍니다.
구조가 잘 잡힌 리포지토리(Repository)라면, 이전에는 반나절이 걸렸던 변경 작업이 단 하나의 프롬프트(Prompt)로 끝납니다.
반면, 좋은 변경이 저렴해진 것과 같은 속도로 위험한 변경 또한 저렴해졌습니다. 게다가 까다로운 점은, 그러한 변경일수록 파일 간의 정합성이 맞고 문제없이 빌드(Build)되어 버린다는 것입니다.
즉, 어려운 것은 스키마 변경을 작성하는 것이 아니게 되고 있습니다.
어려운 것은, 그 변경이 하류(Downstream)를 포함하여 안전한지 판단하는 것입니다.
지금까지 저는 에이전트에게 "주의해 주세요"라고 부탁하는 대신, 짧고 검증 가능한 사실이나 경계(Boundary)를 전달해야 한다고 생각해 왔습니다. 본고에서는 그 사고방식을 데이터베이스 변경으로 확장합니다.
현시점에서의 가설은 다음과 같습니다.
- 에이전트에 의해 스키마 변경이 저렴해지는 것 자체는 문제가 아닙니다.
- 문제는 변경 속도만 올라갔을 뿐, 검증 메커니즘은 인간 중심의 시대에 머물러 있다는 점입니다.
- 위험한 것은 빌드에 실패하는 변경이 아니라, 빌드에 성공한 채로 계약(Contract)을 깨뜨리는 변경입니다.
- 필요한 것은 더 신중한 프롬프트가 아닙니다. 여러 선언을 하나의 계약 그래프(Contract Graph)로 연결하여 안전성을 판정하는 계약 컴파일러(Contract Compiler)입니다.
여기서 말하는 계약 컴파일러란 ORM 스키마, SQL DDL, dbt 산출물, 오브젝트 매니페스트(Object Manifest), SDK 타입 등을 공통의 그래프로 연결하여 변경에 따른 위험을 평가하는 메커니즘입니다.
본고에서는 편의상 가동 상태의 관측, 공통 IR(Intermediate Representation)로의 변환, 해저드(Hazard) 평가를 포함하는 판정 계통 전체를 "계약 컴파일러"라고 부릅니다. 엄밀히 말하면 Compiler가 IR로의 변환을 담당하고, Evaluator가 변경을 판정합니다.
이것이 반드시 새로운 단일 제품을 의미하는 것은 아닙니다. 기존의 스키마 관리, 데이터 리니지(Data Lineage), 정책 평가, CI 도구를 연결하여 하나의 판정 경계로서 기능하게 하는 설계도 포함합니다.
또한, 본고에서 주로 다루는 안전성은 다음과 같습니다.
- 스키마와 데이터의 하위 호환성 (Backward Compatibility)
- 하류 모델 및 SDK와의 정합성
- PII(개인정보) 등의 데이터 도달 범위
- 공개 산출물의 버전 정합성
테이블 락(Table Lock), 백필(Backfill) 시간, 복제 지연(Replication Delay), 성능 저하, 배포 순서, 롤백 가능성 등도 중요하지만, 여기서는 별도의 운영 해저드로 취급합니다.
에이전트가 스키마를 작성하는 시대
단 하나의 컬럼 변경이라도 실제 영향 범위는 넓어집니다.
Drizzle / Prisma / SQL DDL → 마이그레이션 계획 → dbt source
↓ ↓ ↓
앱 타입 / SDK 하위 호환성 dbt 모델
...
여기서는 의도적으로 "마이그레이션"이 아니라 "마이그레이션 계획"이라고 적었습니다.
본고에서는 바람직한 상태를 정답(Source of Truth)으로 보고, 마이그레이션을 거기서부터 도출하는 구성을 전제로 합니다. 마이그레이션 이력 자체를 정답으로 삼는 운용이라 하더라도, 적용 후의 상태를 공통 IR로 변환한다는 생각은 변하지 않습니다.
에이전트는 이것들을 하나의 커밋(Commit)으로 업데이트하며, 서로 정합성이 맞는 것처럼 보이게 할 수 있습니다. 하지만 그것은 편집된 파일들 사이의 정합성에 불과합니다.
가동 중인 데이터베이스, 이미 공개한 산출물, 클라이언트가 고정하고 있는 SDK, 리포지토리 외부의 개인정보 정책과의 정합성까지는 보장되지 않습니다.
물어야 할 것은 에이전트가 변경을 구현할 수 있느냐가 아닙니다.
그 변경이 속한 계약 그래프를 누가 어느 타이밍에 검증하느냐입니다.
그린 빌드(Green Build)라는 위험
두려워해야 할 변경은 컴파일러 에러가 발생하는 변경이 아닙니다. 그것은 보통 CI에서 검출할 수 있습니다.
정말로 위험한 것은 모든 것이 그린(Green, 성공) 상태인 채로 의미론적인 파괴가 일어나는 변경입니다.
nullable이 엄격해져서 기존 행이 다음 업데이트 시 제약 조건 위반이 되는 경우text에서varchar(32)로의 변경으로 인해 환경에 따라 에러나 잘림(Truncation)이 발생하는 경우enum의 값이 삭제되거나 재할당되어 과거 데이터의 의미가 변하는 경우- 이름 변경이 삭제와 추가로 취급되어 하류에서 데이터 소실로 보이는 경우
- PII 컬럼이 분석 모델을 경유하여 의도치 않게 Parquet 산출물에 포함되는 경우
- dbt의
source가 옛날 상태로 남아 존재하지 않는 스키마를 참조하는 경우 - Parquet 스키마가 버전 업데이트 없이 바뀌어
current...
가 덮어씌워집니다.
SQL이 해석 가능한지, 빌드가 통과하는지만을 확인하는 CI(지속적 통합)에서는 이러한 문제를 충분히 탐지할 수 없습니다.
dbt contracts, source tests, migration lint, Atlas, 결합 테스트를 조합하면 일부는 방지할 수 있습니다. 그럼에도 불구하고 많은 파이프라인은 변경이 파괴적(destructive)인지, 하위 호환성(backward compatibility)을 깨뜨리는지, 혹은 계층을 가로지르는 계약 위반인지까지는 묻지 않습니다.
인간 중심의 시대에는 변경 속도가 느렸고, 리뷰를 통해 보완할 수 있는 여지가 있었습니다.
에이전트에 의해 변경량이 늘어나면, "누군가가 알아채겠지"라는 생각은 통제 수단으로서 기능하지 못하게 됩니다.
파괴적 변경에 대한 그린 빌드(Green build)는 안전해 보이는 실패의 전형입니다.
기존 도구만으로는 닫히지 않는 루프
dbt는 분석 측면의 계약 관리(contract management)에 탁월합니다.
Model contracts를 사용하면 모델이 반환하는 컬럼명이나 타입을 선언할 수 있으며, 구현이 어긋날 경우 빌드를 실패하게 만들 수 있습니다. 제약 사항이 어디까지 강제되는지는 데이터 플랫폼이나 매터리얼라이제이션(materialization)에 따라 다르지만, 분석 출력에 대한 계약으로서는 유효합니다.
dbt-duckdb를 사용하면 CI 내의 DuckDB에서 DAG(유향 비순환 그래프) 전체를 실행할 수 있습니다. external materialization과 S3 또는 R2를 조합하면, 상설 데이터 웨어하우스(data warehouse)를 보유하지 않고도 변환, 테스트, 공개까지 구성할 수 있습니다.
하지만 공개된 파일은 단순한 출력이 아닙니다. 스키마 해시, 버전이 지정된 경로, 매니페스트(manifest), current 포인터를 가진 또 하나의 계약 측면입니다.
새로운 형태로 current를 덮어씌우는 것은 파일 업데이트가 아니라, 사용 중인 계약의 변경입니다. 본고에서는 이러한 종류의 위험을 UNTRACKED_LATEST_OVERWRITE라고 부릅니다. 이는 표준 용어는 아니며, 논의를 위한 명칭입니다.
반면, dbt contracts가 다루는 것은 주로 분석 출력입니다.
인덱스(index), 제약(constraint), RLS(행 수준 보안), 트리거(trigger), 테넌트 경계(tenant boundary), PII(개인정보) 분류, ORM 타입 등은 Drizzle, Prisma, Atlas, 수동 작성된 DDL 측에 있습니다. dbt는 운영 스키마를 source로 참조하지만, 그 라이프사이클 전체를 소유하고 있지는 않습니다.
분석 계약은 운영 계약에 의존하고 있음에도 불구하고, 양자를 지속적으로 대조하는 메커니즘이 없습니다.
그리고 현재, 에이전트는 그 양 끝단을 동시에 편집하고 있습니다.
스키마 변경을 판정하는 하네스(Harness)
실제 운영에서는 로우(raw) 모델을 그대로 쉘(shell)에 연결하는 것이 아니라, 통상적으로 도구, 권한, 프롬프트, 실행을 제어하는 하네스로 감쌉니다.
OpenAI도 Codex CLI에 대해 agent loop and harness라는 표현을 사용하고 있습니다.
스키마 변경에도 동일한 이유로 하네스가 필요하다고 생각합니다.
구성 요소는 네 가지 역할로 나뉩니다.
Author
인간 또는 에이전트가 바람직한 상태를 작성함
SQL DDL · Drizzle · Prisma · Atlas HCL · dbt SQL + YAML
...
Author가 항상 옳다는 것을 전제로 하지 않습니다.
변경을 작성한 것이 인간인지 모델인지가 아니라, Compiler와 Evaluator가 어떻게 판정하는지를 중시합니다. 따라서 Observer도 중요합니다. 가동 중인 상태와 생성된 선언은 독립적인 입력이어야 합니다.
컴파일러가 에이전트가 작성한 마이그레이션(migration)만을 읽는다면, 그것은 현실이 아니라 에이전트가 설명한 현실을 평가하는 것에 불과합니다.
작성자가 편집할 수 없는 상태와 대조해야만 검증이 됩니다.
Observer가 취득하는 정보에는 출처, 관측 시각, 환경, 버전, 스키마 해시 등의 이력(lineage)을 포함시킵니다. 관측 결과가 오래된 경우, 판정을 계속하지 않고 재취득 또는 인간의 확인을 요구합니다.
공통 IR로의 변환
SQL DDL, TypeScript, Prisma Schema, HCL, dbt의 SQL과 YAML, manifest.json, DuckDB 카탈로그, Parquet 스키마는 모두 형식이 다릅니다.
이것들을 문자열로서 비교하면 포맷 변경만으로도 차분이 발생합니다.
따라서 각 선언을 공통의 중간 표현(Intermediate Representation, IR)로 변환합니다.
// 설명용 예시
type Column = {
name: string
...
테이블에는 키(key), 제약(constraint), 인덱스(index), 액세스 정책(access policy)을 부여합니다. 분석 모델에는 의존 관계, 출력 컬럼, 매터리얼라이제이션 방법을 부여합니다.
중요한 것은 운영 오브젝트(operational object), dbt 모델, 공개 산출물을 하나의 어휘로 평가할 수 있어야 한다는 점입니다.
IR(Intermediate Representation)은 출력이 아니라, 판단의 단위입니다.
타입(Type), 검증기(Validator), SDK, sources.yml, Parquet 스키마(Schema), 정책 번들(Policy bundle)은 해당 IR로부터 도출할 수 있습니다. dbt의 manifest.json은 이미 분석 측의 IR에 가깝습니다. 부족한 것은 운영 측을 동일한 형태로 변환하고, 양자를 연결하는 메커니즘입니다.
단, 공통 IR은 모든 정보를 최소공배수로 축약하는 것이 아닙니다. PostgreSQL의 RLS, 데이터 웨어하우스(Data Warehouse) 고유의 제약, Parquet의 논리적 타입(Logical type) 등 공통화할 수 없는 정보는 확장 속성(Extended attribute)으로 유지합니다.
목적은 플랫폼 고유의 정보를 버리는 것이 아니라, 계층을 가로지르는 판단에 필요한 사실을 연결하는 것입니다.
차분(Diff)에서 해저드(Hazard)로의 변환
차분은 무엇이 변했는지를 나타냅니다. 하지만 그 변경이 안전한지는 판단할 수 없습니다.
따라서 Evaluator는 차분을 영향 범위가 포함된 해저드(Hazard)로 변환하여, CI나 에이전트가 읽을 수 있는 구조화된 데이터로 반환합니다.
{
"status": "blocked",
"hazards": [
...
CI가 물어야 할 것은 "차분이 있는가"가 아닙니다.
"최대 심각도는 무엇인가", "인간의 판단이 필요한가"입니다.
처음부터 완전한 해저드 분류를 목표로 할 필요는 없습니다. 예를 들어, PostgreSQL의 카탈로그(Catalog), dbt의 manifest.json, 최근의 Parquet 스키마, 공개 매니페스트(Manifest)를 읽어 들여 다음과 같은 판정부터 시작할 수 있다고 생각합니다.
COLUMN_DROP
NULLABILITY_TIGHTEN
TYPE_NARROWING
ENUM_REMAP
PII_EXPORT
LATEST_SCHEMA_CHANGE
에이전트에게 변경을 구현하게 해도 좋습니다.
단, Evaluator를 통과하지 않는 변경은 다음 단계로 진행시키지 않습니다.
기계 판독 가능한 경계
하네스(Harness)가 변경을 판정할 수 있다면, 변경 전에 규칙을 에이전트에게 전달할 수 있습니다.
"파괴적 변경(Breaking change)에 주의하십시오"라고 쓰는 대신, 다음과 같은 경계를 데이터로서 전달합니다.
- Nullable 컬럼, 인덱스(Index), 테스트 추가는 사전 승인
- 컬럼 삭제에는 권장되지 않음(Deprecation) 기간이 필요
- 파괴적 변경은 expand–contract 방식으로 실시
- PII(개인정보) 공개에는 정책이 필요
- 데이터셋 형태 변경에는 버전 업데이트가 필요
- 가동 중인 포인터(Pointer)의 덮어쓰기는 금지
그러면 지시도 구체적이 됩니다.
해저드가 warning을 넘지 않도록 변경하십시오.
critical을 피할 수 없는 경우에는 구현하지 말고 계획을 반환하십시오.
에이전트는 의미가 모호한 그린 체크(Green check)가 아니라, 구조화된 해저드 판정에 대해 수정을 반복할 수 있습니다.
파괴적 변경도 컴파일 가능한 DROP COLUMN이 아니라, expand–contract 계획으로서 반환할 수 있습니다.
"PII를 정책 없이 공개해서는 안 된다"라는 규칙이 금지 작업 및 차단 해저드(Blocking hazard)로 구현되어 있다면, 에이전트에게 지키라고 부탁할 필요가 없습니다.
희소해지는 것은 마이그레이션(Migration)을 작성하는 능력이 아닙니다.
그 마이그레이션이 안전한지를 판단하는 계약(Contract)을 정의하는 능력입니다.
CI에 두는 게이트(Gate)
이 메커니즘은 CI 게이트가 되어야 비로소 의미를 갖습니다.
AI 에이전트
│ 변경을 제안
▼
...
게이트는 제안된 상태와 가동 중인 상태를 각각 독립적으로 동일한 IR로 변환합니다.
거기에 dbt와 공개 산출물의 정보를 결합하여, 빌드 결과가 아닌 해저드 판정을 반환합니다.
critical은 차단 - 모호한 변경은 인간에게 이관- 관측 정보가 오래된 경우 재취득
- 안전한 변경은 그대로 통과
데이터베이스의 관측 방법이나 최근 매니페스트의 저장 위치는 교체 가능합니다.
중요한 것은 CI가 구문 트리(Syntax tree)가 아니라, 계약 그래프(Contract graph)를 평가하는 것입니다.
운영하기 쉬운 게이트로 만들기 위해 다음 원칙도 필요합니다.
- 차분이 아니라, 바람직한 상태와 계획부터 시작한다
- ops schema와 dbt source를 이중 관리하지 않는다
- 공개 산출물을 버전 관리한다
current포인터의 변경을 명시적인 작업으로 취급한다- 관측 정보의 출처와 신선도(Freshness)를 유지한다
- 예외는 근거와 함께 기록한다
CI는 마이그레이션이 빌드될 수 있는지가 아니라, 계약 그래프가 안전한지를 판정합니다.
예상되는 반론
lint나 dbt contracts, Atlas로 충분하지 않은가
이것들은 유용하지만, 단독으로는 도구 간의 계약 (contract)을 다룰 수 없습니다.
Atlas는 파괴적인 운영 변경을 감지하고, dbt contracts는 분석 출력을 고정합니다. 하지만 운영 컬럼의 pii 라벨을 Parquet 출력까지 추적하고, 그것을 근거로 공개를 중단하는 단계까지는 담당하지 않습니다.
계약 컴파일러 (contract compiler)는 기존 도구를 대체하는 것이 아니라, 각각이 가진 사실과 판정을 연결하여 계층을 가로지르는 변경을 평가하기 위한 역할입니다.
에이전트에게 많은 컨텍스트 (context)를 전달하면 되지 않는가
부족한 것은 컨텍스트의 양이 아니라 판단 경계입니다.
컬럼 삭제가 안전한지는 단일 파일에 적혀 있지 않습니다. 운영 스키마 (operational schema), 하류 모델 (downstream model), SDK, 공개 결과물을 가로지르는 그래프 (graph) 안에 있습니다.
큰 컨텍스트 윈도우 (context window)만으로는 그 판단을 보장할 수 없습니다.
모델을 신뢰하지 않는 것뿐 아닌가
오히려 반대입니다. 신뢰하지 않는다면 에이전트에 의한 스키마 변경을 금지할 것입니다.
계약 컴파일러가 있다면 에이전트에게 넓은 변경 권한을 부여하면서도, 경계를 기계적으로 제어할 수 있습니다. 동일한 하네스 (harness)는 인간의 변경에도 적용할 수 있습니다.
하나의 IR로 통합하는 것은 너무 복잡하지 않은가
모든 정보를 한 번에 통합하려고 하면 너무 복잡해집니다.
처음부터 완전한 IR (Intermediate Representation)을 목표로 할 필요는 없습니다. 사용 중인 컬럼의 삭제, PII의 출력, 결과물 스키마의 덮어쓰기 등 영향이 큰 경계부터 단계적으로 연결하는 것이 현실적입니다.
공통 IR은 시스템 전체를 중앙 집권적으로 관리하기 위한 것이 아닙니다. 여러 계약을 하나의 판정 그래프 위에서 참조할 수 있도록 하기 위한 것입니다.
소규모 앱에는 과도하지 않은가
맞습니다. 변경량이 적은 환경에서는 인간이 차이점 (diff)을 읽는 것이 합리적입니다.
이 메커니즘이 필요해지는 시점은 변경량이 인간의 주의력을 초과할 때입니다. 에이전트는 그 상황을 앞당깁니다.
해저드 (hazard) 판정에 거짓 양성 (false positive)이 발생하지 않을까
거짓 양성은 발생합니다. 다만, 오경보가 많은 게이트 (gate)는 무시되고, 무시되는 게이트는 존재하지 않는 게이트보다 더 나쁩니다.
따라서 거짓 양성은 근거와 함께 억제할 수 있어야 하며, 정책 예외 (policy exception)로서 추적할 수 있어야 합니다. 오버라이드 (override) 또한 이력으로 남겨야 합니다.
보수적인 것뿐만 아니라, 운영 가능해야 하는 것이 중요합니다.
계약 그래프라는 방향성
SQL DDL, ORM 스키마, dbt 모델, Parquet 스키마, S3 매니페스트 (manifest), SDK 타입은 각각 별개의 결과물입니다.
동시에, 동일한 데이터 경계를 서로 다른 계층에서 표현한, 상호 연결된 계약들의 집합이기도 합니다.
에이전트는 그 모든 것을 빠르게 변경할 수 있습니다. 각각을 별도의 파일로 관리하며 인간이 계속해서 차이점을 추적해야 한다면, 그 속도는 점차 리스크가 됩니다.
반면, 필요한 사실을 공통 IR로 변환하고, 변경을 해저드로 평가하며, 허용되는 경계를 기계 판독 가능한 형태로 전달할 수 있다면, 속도는 다시 이점이 됩니다.
물론 모든 시스템에 이 메커니즘이 필요하다고 생각하지는 않습니다.
소규모 앱이라면 인간이 차이점을 읽는 것이 합리적일 것입니다. 잠금 시간 (lock time), 백필 (backfill), 성능, 롤백 가능성 등 계약 그래프만으로는 완결되지 않는 운영상의 위험도 남습니다.
그럼에도 지향하는 방향은 비교적 명확합니다.
에이전트에게는 스키마, 마이그레이션, 모델, 타입을 작성하게 하면 된다고 생각합니다.
다만, 그 변경이 안전한지 여부까지 에이전트나 리뷰어의 주의력에 맡겨서는 안 됩니다.
에이전트가 변경을 작성하고, 계약 컴파일러가 통과 여부를 판단한다.
그 분업이 에이전트 시대의 스키마 변경을 뒷받침하는 설계 원칙 중 하나가 될 것이라고 생각합니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기