본문으로 건너뛰기

ADR-0039: 학습 데이터셋 스토리지 쿼터 — ingest 원장 계상 + 라우터 게이트 시행

  • 상태: 승인
  • 날짜: 2026-08-06
  • 관련: ADR-0027(데이터셋 snapshot 버전), ADR-0033(관측 카디널리티), 이슈 #2989 M0, 설계 docs/DESIGN_DATASET_STORAGE_QUOTA.md

맥락

학습 데이터셋 업로드에 총량 통제가 없었다 — 상한은 요청 형태 제한(파일당 100MB)뿐. prod SeaweedFS 는 용량 상한이 volume.max × volumeSizeLimitMB 구조라 고갈을 사후 알람으로 잡을 수 없고(#2784), M3(파일당 5GB 청크 업로드)를 열기 전에 선제 쿼터가 유일한 방어선이다. 바이트 사용량은 PG 어디에도 영속되지 않았다 (유일 컬럼 TrainingDatasetVersion.size_bytes 는 쓰는 코드 0곳).

결정

  1. 사용량 진실 소스 = training_dataset_ingests.bytes_added 증분 원장. 모든 쓰기 경로가 이미 이 원장에 1행씩 기록하므로 실제 저장 바이트를 함께 싣는다 — files 는 S3 실저장(blob+썸네일, 중복/거부 제외), jsonl/parquet 는 Bronze raw 원본. 사용량 = SUM(bytes_added) JOIN training_datasets. S3/Iceberg 실측 집계($files)는 채택하지 않는다 — 업로드 hot path 에서 Trino 왕복이 필요하고 GC 유예·staging 이 섞여 판정이 흔들린다.
  2. 데이터셋 삭제 = 원장 CASCADE 삭제 → 사용량 즉시 감소. 실제 S3 는 GC 유예(기본 7일) 후 정리되므로 그동안 과소 계상 — 수용하고 문서화한다. 행 삭제(rows_delete)는 S3 를 지우지 않으므로 사용량을 줄이지 않는다.
  3. Bronze raw 키 = content-hash 결정적 (raw/{sha256(data)}). uuid 키는 중복-전용 재업로드(신규 행 0 → snapshot 없음 → 원장 미기록)가 매번 새 객체를 쌓아 hard cap 을 무한 우회하는 경로였다. 결정적 키는 같은 파일 재업로드가 같은 객체를 덮어써 과소 계상을 파일당 1회 사본으로 유계화한다.
  4. 시행 = 라우터 게이트, 판정 = 사전 보수적 상한. hard 초과 413(detail "쿼터" 문구로 물리 상한 413 과 구분) / soft 초과는 허용 + quota_warning 응답. 쓰기 전에 dedup 결과를 알 수 없으므로 incoming 은 원본 전체 (files 는 zip 전개 후 합) 기준. 기본값은 무제한(GEND_DATASET_DEFAULT_ QUOTA_BYTES=0) — 기존 워크스페이스 무중단, prod 는 ws 별 쿼터 행으로 점진 시행.
  5. 동시성 = hard 유한 시 workspaces 행 FOR UPDATE. 데이터셋 행 락만으론 같은 ws 의 다른 데이터셋 동시 업로드 TOCTOU 로 hard 를 넘는다. 락 순서는 dataset → workspace 로 전 경로 고정 (데드락 없음, sqlite 는 no-op).
  6. 메트릭 = ADR-0033 준수. gend_dataset_quota_block_total{level=soft|hard} (preinit) + 라벨 없는 전사 gend_dataset_storage_usage_bytes gauge — workspace 라벨 금지 (LLM_QUOTA_BLOCK_TOTAL 의 workspace_id 라벨은 '따르지 말 것' 선례). 멀티 레플리카는 스크랩 시점 프로세스 값이므로 Grafana 집계는 max() 전제.
  7. per_user breakdown 은 같은 ws viewer 에게 공개 — 팀 내 용량 분담 투명성 (HF org usage 동형). created_by 는 이미 데이터셋 목록/버전 메타로 동일 viewer 에게 노출되는 정보 등급이다. 시행 단위는 워크스페이스뿐 (개인별 한도는 에픽 비목표).

비채택 대안

  • presigned/S3 직접 집계 — SeaweedFS 는 ClusterIP 전용, hot path Trino 왕복 비용, GC 유예 혼입.
  • 누적 카운터 컬럼 + 일일 재조정 배치 — 드리프트 소스 5곳(업로드·행삭제· 데이터셋삭제·export·GC) 전부 배선하는 비용 대비, append-only 원장이 더 단순하고 감사 가능.
  • export 산출물 계상TrainingDatasetExport 에 바이트 컬럼이 없고 잡이 비동기라 원장 시점이 없다. M0 후속.

결과

  • 업로드 시행·usage API·admin CRUD 는 #2989 M0 PR-1, 증설 승인 워크플로우는 PR-2, UI/CLI 가시화는 PR-3.
  • 운영 절차: 스토리지 쿼터 관리.
  • 알려진 유계 과소 계상: 중복-전용 업로드 raw(파일당 1회 사본)·원장 도입 전 기존 데이터(0 계상)·export 산출물 — M0 후속에서 재검토.