ADR-0027: 학습 데이터셋 버전닝 — Iceberg Snapshot 고정
- 상태: 승인
- 날짜: 2026-07-23
- 관련: Epic #2527 (학습 데이터셋 레이어), PR #2540/#2545/#2549/#2553
배경
LLM/VLM 파인튜닝 데이터셋은 "이 모델이 정확히 어떤 데이터로 학습됐는가"를 재현할 수 있어야 한다 (컴플라이언스·디버깅·감사). HuggingFace 의 revision 에 해당하는 버전 개념이 필요했다.
결정
데이터셋 버전 = Iceberg snapshot ID 고정. 신규 저장 포맷·git 스타일 repo 를 도입하지 않고, 행 데이터가 이미 저장되는 Iceberg 테이블의 snapshot 메커니즘을 버전 경계로 사용한다.
- 원자적 캡처: 모든 쓰기 API(이미지 업로드/JSONL 커밋/행 삭제)는 PG 데이터셋 행
FOR UPDATE락으로 직렬화되고, 응답에resulting_snapshot_id를 반환한다 (락 보유 중$snapshots최신 조회 = 자신의 커밋). "나중에 latest 재조회" 는 금지 — 동시 쓰기가 섞인 snapshot 을 버전으로 고정하는 레이스가 생긴다. - 명시적 버전 생성:
POST /datasets/{id}/versions는 클라이언트가 쓰기 응답에서 받은snapshot_id를 명시적으로 전달해야 한다. 서버는 해당 snapshot 시점 행 수/split 분포를 실측해 저장한다 (무효 snapshot 은 422). - 버전 조회/export:
FOR VERSION AS OF {snapshot_id}— 이후 데이터가 추가/삭제되어도 버전 시점 그대로 조회된다. - API 경계 직렬화: snapshot ID 는 int64 로 JavaScript Number 정밀도(2^53)를 초과하므로 API 요청/응답 전부 10진 문자열로 다룬다 (DB 는 BigInteger).
대안 검토
| 대안 | 기각 사유 |
|---|---|
| Lance/LanceDB 도입 | 별도 스토리지 엔진 추가 — Trino/Iceberg 단일 데이터 평면 원칙 훼손, 운영 부담 |
| LakeFS (git-for-data) | 별도 시스템 + S3 게이트웨이 경유 — 과잉. snapshot 이 동일 보장을 무비용 제공 |
| 데이터 복사 (버전별 별도 테이블/prefix) | 저장 비용 O(버전 수), 대형 데이터셋 비실용 |
결과 및 제약
- snapshot 만료 금지: 버전이 고정한 snapshot 이
expire_snapshots로 만료되면 재현성이 붕괴한다. 현재 Iceberg 유지보수(pipelines/gend_pipelines/assets/iceberg_maintenance.py)는 스트리밍 테이블만 대상으로 하며iceberg.datasets스키마는 만료 대상에 넣지 않는다 (회귀 가드 테스트:pipelines/tests/test_iceberg_maintenance_datasets_guard.py). 향후 datasets 스키마에 유지보수가 필요해지면, PGtraining_dataset_versions에 참조된 snapshot 을 보존 목록으로 전달하는 메커니즘이 선행돼야 한다. - 행 삭제와 GDPR 상충:
DELETE /datasets/{id}/rows는 현재 테이블에서 행을 지우지만 버전이 고정한 과거 snapshot 에서는 여전히 조회된다. 완전 삭제(잊혀질 권리)가 필요한 경우 해당 행이 포함된 버전을 삭제한 뒤 snapshot 만료를 별도 수행해야 한다 — 이 절차는 운영 런북으로 관리한다 (자동화는 후속). - 데이터셋 삭제는 Iceberg 테이블 DROP 을 동반하므로 버전 이력도 함께 소멸한다(CASCADE) — 테이블이 없으면 snapshot 도 없어 버전 행 보존은 무의미하다는 의도적 결정.