BrickDatasetCache CRD 설계
목적: 학습 노드에 데이터셋을 실체화하고 재사용·정리를 관리하는 커스텀 리소스 설계 API 그룹:
brick.brick.cloudhub.io/v1상태: 초안 (v0.1) 관련 문서:BrickML-스토리지-표준.md(§2 아키텍처 전제, §6 스테이징 계층 규약)
1. 배경과 역할
학습 Job은 데이터셋을 MinIO에서 직접 스트리밍하지 않고, 노드 로컬 볼륨에 실체화된 사본을 읽는다. 이 사본을 만들고 유지하고 정리하는 책임을 담당하는 리소스가 BrickDatasetCache다.
해결하는 문제
| 문제 | 해결 방식 |
|---|---|
| 다운로드 중 GPU 점유 | GPU 미할당 스테이징 Job으로 분리 |
| 반복 실행 시 재다운로드 | 노드별 캐시 재사용 |
| 동일 데이터셋·노드 동시 스테이징 → 데이터 손상 | CR 이름 유일성으로 single-flight |
| 캐시 누적으로 인한 디스크 고갈 | TTL + 용량 기반 강제 만료 |
| 스테이징 실패 시 상태 추적 불가 | CR이 intent record로 잔존 |
| 디스크 정리 주체 부재 | PVC ownerReference 연쇄 삭제 |
2. 설계 결정
왜 CRD인가
지속적 조정(reconcile) 작업이 실재한다: PVC·Job 생성 → 완료 관측 → 상태 갱신 → TTL·용량 판단 → 정리. 단순 조회 인덱스가 아니므로 DB 테이블보다 CRD가 적합하다.
또한 BrickApplication 도입 목적이 Java 백엔드의 직접 K8s 호출을 걷어내는 것이었으므로, 아키텍처 방향성과도 일치한다.
왜 BrickDatasetCache인가 (naming)
BrickStaging은 동작을, BrickDatasetCache는 상태를 가리킨다. 쿠버네티스 관례는 desired state를 이름으로 두므로 후자를 채택한다. 이 리소스의 의미는 "스테이징하는 행위"가 아니라 "이 노드에 이 데이터셋이 존재해야 함" 이라는 선언이다.
왜 학습 리소스의 하위가 아닌가
캐시는 개별 학습 실행보다 오래 산다. 학습 run이 스테이징을 하위 단계로 소유하면 "여러 run이 공유하는 볼륨을 누가 소유하는가"가 불명확해진다. 캐시를 독립 리소스로 두고 학습 측이 참조하는 구조가 생명주기상 정합하다.
캐시 범위: 원본만 담는다
스테이징 Job은 다운로드와 레이아웃 실체화까지만 수행하고, 전처리는 학습 Job이 담당한다.
경계 기준은 "데이터셋에 종속되는가, 실험 설정에 종속되는가" 다.
| 작업 | 종속 대상 | 위치 |
|---|---|---|
| 다운로드 | 데이터셋 | 스테이징 Job |
디렉터리 구조, data.yaml, 라벨 포맷 변환 | 데이터셋 | 스테이징 Job |
| 리사이즈, 정규화, 크롭·플립 등 증강 | 실험 설정 | 학습 Job (데이터로더) |
전처리 결과를 캐시에 담지 않는 이유
캐시 키에 전처리 설정이 포함되면, 사용자가 리사이즈 크기 하나만 변경해도 캐시가 무효화되어 전체 데이터를 다시 내려받아야 한다. 전처리 설정 변경은 사용자가 가장 빈번하게 수행하는 반복 작업이므로, 원본만 캐시해야 어떤 실험 설정에서도 재사용된다.
또한 학습 프레임워크(Ultralytics 계열 등)는 리사이즈를 데이터로더 내부에서 처리하므로, 이를 분리해 스테이징으로 옮기면 프레임워크 동작과 충돌한다.
학습 Job 내부에서도 전처리는 데이터로더에서 배치 단위로 수행한다. 학습 시작 전 전체 데이터를 일괄 전처리하는 별도 패스를 두면 그 시간 동안 GPU가 유휴 상태가 된다. 데이터로더 방식은 GPU 연산과 겹쳐 실행되므로 추가 비용이 없다.
스코프
(datasetId, nodeName) 당 CR 하나.
- 노드별 독립 캐시 (표준 §6). 동일 데이터셋이 여러 노드에 각각 존재할 수 있다
- CR 이름을 결정론적으로 부여하면 생성의 원자성이 single-flight 락으로 작동한다. 중복 생성 시 API 서버가
AlreadyExists로 거부한다
name: dscache-{datasetIdShort}-{nodeName}
namespace: brick-ml
이름 길이 제한(253자)과 DNS-1123 규칙을 고려해
datasetId·nodeName을 정규화한다. 원본 값은spec에 보존한다.
네임스페이스 스코프
Namespaced (brick-ml). PVC를 ownerReference로 소유해야 하므로 동일 네임스페이스여야 한다.
3. 소유 관계와 생명주기
BrickDatasetCache CR
├─ ownerRef ─▶ PVC (local PV, storageClass: brick-staging-local)
└─ ownerRef ─▶ Job (스테이징: 마커 검증 + 다운로드)
핵심 이점: 디스크 정리를 별도로 구현하지 않는다.
CR 삭제 → PVC 삭제 → 프로비저너가 노드 디렉터리 정리까지 연쇄된다. Controller가 노드 디스크에 접근할 수 없다는 제약이 PVC 생명주기에 위임되어 해소된다.
→ TTL 로직이 "CR을 삭제한다" 한 줄로 귀결된다. finalizer로 별도 정리 로직을 구현할 필요가 없다.
전제
- 스테이징 storage class의
reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer
⚠️
reclaimPolicy: Retain이면 PVC 삭제 후에도 디스크가 남아 정리가 되지 않는다. 배포 체크리스트 필수 항목.
4. 스키마
spec
apiVersion: brick.brick.cloudhub.io/v1
kind: BrickDatasetCache
metadata:
name: dscache-ds8f2a-worker-01
namespace: brick-ml
spec:
datasetId: ds-8f2a
nodeName: worker-01
source:
bucket: brick-datasets
prefix: datasets/ds-8f2a/
credentialsSecretRef:
name: minio-dataset-reader # s3:GetObject, prefix 스코프
manifest: # 검증 기준값
fileCount: 3000
totalBytes: 62914560000
hash: "sha256:3f8a..."
layout:
format: yolo # yolo | coco
version: v1 # 규약 변경 시 구버전 캐시 자동 무효화
# 전처리 설정은 포함하지 않는다 (§2 캐시 범위)
capacity: 100Gi # PVC 요청 용량 (데이터셋 × 1.3)
ttlSecondsAfterLastUse: 604800 # 7일
| 필드 | 설명 |
|---|---|
datasetId | 데이터셋 식별자. ready 상태 도달 후 내용이 변경되지 않으므로 캐시 키로 사용 가능 |
nodeName | 실체화 대상 노드. PVC·Job의 노드 고정에 사용 |
source | MinIO 위치 및 읽기 전용 크리덴셜 참조 |
manifest | 마커 검증 기준값. 업로드 검증 단계에서 산출 |
layout | 학습 프레임워크 디렉터리 규약 및 버전 |
capacity | PVC 요청량. 용량 회계의 단위 |
ttlSecondsAfterLastUse | 마지막 사용 후 보존 기간. 미지정 시 deployer 기본값 |
spec은 실질적으로 불변으로 취급한다. datasetId·nodeName·manifest·layout 변경은 다른 캐시를 의미하므로 새 CR로 만든다. 변경 가능한 것은 ttlSecondsAfterLastUse 정도다.
status
status:
phase: Ready # 아래 상태 전이 참조
pvcName: dscache-ds8f2a-worker-01
jobName: dscache-ds8f2a-worker-01-stage
stagedAt: "2026-07-30T04:15:00Z"
lastUsedAt: "2026-07-30T09:22:11Z"
stagedBytes: 62914560000
stagingDurationSeconds: 132
activeReferences: 2
attemptCount: 1
message: ""
conditions:
- type: Staged
status: "True"
lastTransitionTime: "2026-07-30T04:15:00Z"
reason: DownloadCompleted
status는 마지막 관측치다. 디스크 실제 상태와 어긋날 수 있으며, 권위 있는 검증은 항상 볼륨이 마운트된 파드 내부에서 수행된다 (§6).
5. 상태 전이
| phase | 의미 | 다음 |
|---|---|---|
Pending | CR 생성됨. PVC·Job 미생성 | → Staging (용량 확보 후) |
Staging | 스테이징 Job 실행 중 | → Ready / Failed |
Ready | 마커 유효. 학습 가능 | → Staging (revalidate) / Terminating |
Failed | 스테이징 실패. 재시도 한도 초과 | → Staging (수동/자동 재시도) / Terminating |
Terminating | 삭제 진행 중 (TTL 만료 또는 강제 만료) | CR 삭제 |
┌──────────────┐
생성 ───────▶ │ Pending │
└──────┬───────┘
│ 용량 확보 + PVC/Job 생성
▼
┌──────────────┐ Job 실패(한도 초과) ┌──────────┐
│ Staging │ ─────────────────────▶ │ Failed │
└──────┬───────┘ └────┬─────┘
│ Job 성공 │
▼ │
┌──────────────┐ │
│ Ready │ ◀───────── 재시도 ──────────┘
└──────┬───────┘
│ revalidate 요청 → Staging 으로 복귀
│ TTL 만료 / 강제 만료
▼
┌──────────────┐
│ Terminating │ ─▶ CR 삭제 ─▶ PVC·Job 연쇄 삭제 ─▶ 디스크 정리
└──────────────┘
Ready → Staging 복귀 (revalidate)
학습 Job의 init step이 마커 불일치를 발견한 경우(노드 재설치, 수동 정리, 축출 레이스 등), 요청자가 CR에 annotation을 부여하여 재스테이징을 트리거한다.
metadata:
annotations:
brick.cloudhub.io/revalidate: "2026-07-30T09:30:00Z"
Controller는 이 annotation의 타임스탬프가 status.stagedAt 이후이면 phase를 Staging으로 되돌리고 Job을 재생성한다. 명시적이고 관측 가능한 경로다.
대안: 학습 시작 전 항상 스테이징 Job을 실행하는 방식. Job이 멱등이므로 cache-hit 시 수 초 내 종료되지만, 파드 스케줄·이미지 준비 오버헤드로 학습 시작마다 20~30초가 추가된다. 구현이 더 단순하므로 초기 구현에서 선택할 수 있다.
6. 검증 책임 분리
Controller는
brick-system에서 실행되므로 대상 노드의 파일시스템에 접근할 수단이 없다.
| 주체 | 위치 | 역할 | 권위 |
|---|---|---|---|
| Controller | brick-system | PVC·Job 생성, 상태 기록, TTL·용량 판단 | 클러스터 상태에 대해서만 |
| 스테이징 Job | 대상 노드 (볼륨 마운트) | 마커 검증, 다운로드, 마커 기록 | 디스크 상태의 권위 |
| 학습 Job init step | 대상 노드 (볼륨 마운트) | 마커 재검증 | 최종 안전망 |
Controller와 Job은 대안이 아니라 계층이다. Controller가 다운로드를 수행하지 않고 Job을 생성해 관측한다.
PVC: Bound만으로 캐시 존재를 판단하지 않는다. local PV는 하위 디렉터리가 삭제되어도 PV 오브젝트가 Bound로 유지되며 쿠버네티스가 이를 감지할 수단이 없다.
7. 스테이징 Job 계약
멱등성 (필수)
Job의 첫 동작은 마커 검증이며, 유효한 마커가 존재하면 즉시 성공 종료한다.
1. /data/.complete 읽기 시도
2. 존재하고 datasetId·manifestHash·layoutVersion이 모두 일치
→ terminationMessage: {"result":"cache-hit"} exit 0
3. 불일치 또는 부재
→ /data/* 전체 삭제 (잔여물 제거)
→ MinIO에서 병렬 다운로드 (16~32 병렬)
→ layout 규약에 따라 디렉터리 실체화 (필요 시 포맷 변환)
→ 파일 수·바이트 검증
→ /data/.complete 기록
→ terminationMessage: {"result":"staged","bytes":N,"durationSeconds":N} exit 0
4. 실패
→ terminationMessage: {"result":"failed","reason":"..."} exit 1
이 설계의 효과: 호출자가 캐시 여부를 사전 확인할 필요가 없다. "무조건 스테이징"과 "확인 후 스테이징"이 하나의 경로로 통합된다.
결과 보고
terminationMessagePath(/dev/termination-log)에 JSON 한 줄을 기록한다. Controller가 파드 상태에서 읽어 status에 반영한다.
Job에 CR 패치 RBAC을 부여하지 않으므로 결합이 느슨하다. Job의 ServiceAccount는 K8s API 접근 권한이 필요 없다 — PVC와 Secret 마운트만 사용한다.
Job 스펙 요구사항
spec:
backoffLimit: 2
ttlSecondsAfterFinished: 600
activeDeadlineSeconds: 7200 # 무한 대기 방지
template:
spec:
restartPolicy: Never
nodeSelector:
kubernetes.io/hostname: worker-01 # spec.nodeName — 필수
containers:
- name: stager
resources:
requests: { cpu: "1", memory: 2Gi }
limits: { cpu: "2", memory: 4Gi }
# GPU 요청 없음 — 필수
volumeMounts:
- { name: data, mountPath: /data }
envFrom:
- secretRef: { name: minio-dataset-reader }
terminationMessagePath: /dev/termination-log
volumes:
- name: data
persistentVolumeClaim:
claimName: dscache-ds8f2a-worker-01
nodeSelector는 필수다. WaitForFirstConsumer는 PVC를 처음 사용하는 파드가 스케줄된 노드에 PV를 생성한다. 누락하면 스케줄러가 임의 노드를 선택하여 spec.nodeName과 어긋난다.
Job 재생성 시 주의: Job의 spec은 불변이므로 재시도 시 기존 Job을 삭제한 후 생성한다. ttlSecondsAfterFinished로 완료된 Job이 자동 정리되도록 하되, Controller는 재시도 전 명시적으로 삭제 여부를 확인한다.
마커 포맷
{
"datasetId": "ds-8f2a",
"manifestHash": "sha256:3f8a...",
"fileCount": 3000,
"totalBytes": 62914560000,
"layoutVersion": "yolo-v1",
"stagedAt": "2026-07-30T04:15:00Z"
}
파일별 체크섬은 요구하지 않는다. 파일 수 + 총 바이트 + 매니페스트 해시로 실질적 오류가 검출되며 검증이 즉시 완료된다.
8. TTL 및 용량 관리
TTL은 마지막 사용 시점 기준
spec.ttlSecondsAfterLastUse — 학습 Job이 참조할 때마다 status.lastUsedAt을 갱신한다.
생성 시점 기준으로 두면 하이퍼파라미터 튜닝 세션 중간에 만료되어 재다운로드가 발생한다.
status.activeReferences > 0인 동안 TTL은 적용되지 않는다.
참조 카운팅은 저장하지 않고 도출한다
학습 Job·Pod에 라벨을 부여하고, Controller가 non-terminal 상태의 것을 세어 status.activeReferences에 반영한다.
labels:
brick.cloudhub.io/dataset-cache: dscache-ds8f2a-worker-01
카운터를 저장하지 않는 이유: 증감 방식은 프로세스 크래시나 Job 강제 삭제 시 카운터가 누출되어 캐시가 영구히 정리되지 않는다. 라벨 셀렉터로 도출하면 이 실패 모드가 사라진다.
용량 관리
TTL만으로는 용량을 보장할 수 없다. TTL 만료 전에 디스크가 소진되면 대응 수단이 없으므로 두 경로를 함께 둔다.
| 경로 | 동작 |
|---|---|
| TTL 만료 | Controller가 CR 삭제 → 연쇄 정리 |
| 용량 부족 | 신규 캐시 Pending → Staging 전이 전에 할당 합계 + 요청량 ≤ 노드 용량 검증. 초과 시 activeReferences == 0인 캐시를 lastUsedAt 오래된 순으로 강제 만료 |
노드별 용량 인식
노드 status는 스테이징 마운트를 별도로 보고하지 않는다(ephemeral-storage는 루트 파일시스템 기준). 따라서:
- 노드별 스테이징 용량을
brickml-deployer설정에 선언한다 - Controller가 자신이 소유한 PVC의
spec.capacity합계로 할당량을 계산한다
# brickml-deployer (예시)
spec:
datasetCache:
defaultTtlSecondsAfterLastUse: 604800
nodes:
- name: worker-01
capacity: 300Gi
- name: worker-02
capacity: 300Gi
- name: worker-03
capacity: 300Gi
강제 만료로도 용량을 확보할 수 없으면 phase를 Pending으로 유지하고 message에 사유를 기록한다. 요청자는 이 상태를 근거로 사용자에게 "공간 확보 필요"를 표시하거나 ephemeral 볼륨 폴백을 선택할 수 있다.
축출 안전 규칙
activeReferences > 0인 캐시는 축출하지 않는다- TTL 만료 외의 축출은 용량 부족 시에만 수행한다. 축출 시점을 좁히면 상태 표시와 실행 사이의 레이스 창이 줄어든다
- PVC 삭제 방식이므로 마커·데이터 삭제 순서를 관리할 필요가 없다 (부분 삭제 상태가 발생하지 않음)
9. 실패 모드 대응
| # | 실패 모드 | 대응 | 결과 |
|---|---|---|---|
| ① | Stale positive — status: Ready이나 디스크가 비었거나 부분적 (노드 재설치, 수동 정리) | 학습 Job init step의 마커 재검증 → revalidate annotation → 재스테이징 | 소요 시간 증가 |
| ② | 축출 레이스 — 표시·판단 시점과 실행 시점 사이에 축출 | 축출 규칙 제한(§8) + ①의 경로 | 소요 시간 증가 |
| ③ | 동시 스테이징 — 같은 데이터셋·노드에 두 Job 동시 쓰기 → 데이터 손상 | CR 이름 유일성으로 API 서버가 차단. Job 이름도 결정론적 | 차단 |
| ④ | 부분 스테이징 — Job 중단으로 파일 일부 + 마커 없음 | 마커 부재로 cache-miss 판정. 재스테이징 시 잔여물 삭제 | 자동 복구 |
| ⑤ | 용량 부족 | Pending 유지 + message 기록. 강제 만료 시도 후에도 부족하면 요청자에게 노출 | 명시적 실패 |
| ⑥ | PVC 노드 배치 어긋남 — WaitForFirstConsumer가 임의 노드에 PV 생성 | Job·학습 Job 모두 nodeSelector 명시 | 차단 |
| ⑦ | 데이터셋 변경으로 캐시 무효화 | 해당 없음 — 데이터셋은 ready 전이 후 변경되지 않는다 (표준 §2 원칙 3, §4-⑦) | — |
| ⑧ | 레이아웃 규약 변경 | layout.version 불일치 → cache-miss 판정 → 재스테이징 | 자동 복구 |
| ⑨ | reclaimPolicy: Retain 오설정 | 배포 체크리스트에서 사전 차단. 미검출 시 디스크 누적 | 운영 사고 |
설계 원칙: ③을 제외한 모든 실패 모드는 "소요 시간 증가"로 격하되며 데이터 정확성 문제로 전이되지 않는다. ③만이 사전 차단이 필수인 항목이다.
10. 학습 측 통합 흐름
1. Flow 캔버스에서 Learning 실행 + 노드 선택
2. 요청자가 BrickDatasetCache CR 생성 또는 기존 CR 확인
- AlreadyExists → 기존 CR 관측 (single-flight)
3. Experiment > Run 상세 화면으로 이동
4. status.phase == Ready 대기
5. 학습 Job 생성
- PVC를 readOnly: true 로 마운트
- nodeSelector = spec.nodeName
- label: brick.cloudhub.io/dataset-cache=<CR 이름>
6. 학습 Job init step에서 마커 재검증
- 불일치 → revalidate annotation 부여 후 실패 종료, 재시도
7. 학습 진행. Controller가 activeReferences 및 lastUsedAt 갱신
readOnly: true 마운트는 필수다. 학습 코드가 캐시를 오염시키는 것을 막는다.
Experiment Run 상태 모델과의 연결
Run은 K8s Job 상태와 MLflow 데이터를 병합해 표시한다. 스테이징 Job이 앞단에 추가되면서 상태가 하나 늘어난다.
| Run 상태 | 출처 | 화면 |
|---|---|---|
Preparing | 스테이징 Job 실행 중 | 데이터 준비 중 배너 + 진행률 (신규) |
Running | 학습 Job 실행 중 | 학습 진행 중 배너 |
Finished | MLflow | 메트릭 표시 |
Failed — 데이터 준비 실패 | 스테이징 Job 실패 | 신규. 용량·노드 문제 → 관리자 문의 또는 다른 노드 선택 안내 |
Failed — 학습 코드 실행 전 오류 | K8s Job만, MLflow 기록 없음 | 기존 |
Failed — 학습 실패 | MLflow 기록 있음 | 기존 |
표시 규칙
- 진행률은 상태 배너 내부에 배치한다. 데이터 준비는 파일 수 기준으로 정확한 비율 산출이 가능하다
- 학습 진행률은 MLflow의 최근 기록 epoch 기준. 에폭 개념이 없는 방식(sklearn 등)은 경과 시간만 표시한다
- Pod·로그는 현재 활성 단계의 것을 표시한다 (준비 중이면 스테이징 파드)
- 캐시 히트로 준비 단계가 수 초 만에 끝나도 배너를 노출한다. 실행마다 시작 시간이 다른 이유를 사용자가 이해하게 된다
- Datasets 섹션에 캐시 출처를 함께 표기한다 (예:
캐시 사용 (worker-01)) - Run 목록의 Status 컬럼에
Preparing을 추가한다
11. RBAC
Controller
| 리소스 | 권한 |
|---|---|
brickdatasetcaches | get, list, watch, update, patch, delete |
brickdatasetcaches/status | get, update, patch |
persistentvolumeclaims | get, list, watch, create, delete |
jobs (batch) | get, list, watch, create, delete |
pods | get, list, watch — terminationMessage 판독용 |
events | create, patch |
nodes | get, list — 노드 존재·상태 확인 |
Secret 접근 권한은 부여하지 않는다. Controller는 credentialsSecretRef를 Job 스펙에 전달하기만 하며 내용을 읽지 않는다.
스테이징 Job ServiceAccount
K8s API 권한 없음. PVC와 Secret 마운트만 사용한다.
12. 관측성
Controller가 노출할 지표:
| 지표 | 용도 |
|---|---|
| 노드별 캐시 수 / 할당 용량 / 여유 용량 | 용량 계획, 알림 |
| cache-hit vs staged 비율 | 캐시 효용 측정. hit 비율이 낮으면 TTL 상향 검토 |
| 스테이징 소요 시간 분포 | 대역폭 병목 탐지, 사용자 안내 시간 산출 |
Failed 상태 CR 수 | 장애 탐지 |
| 강제 만료 발생 빈도 | 용량 부족 신호 → 디스크 증설 근거 |
스테이징 소요 시간은 고객사 환경 평가의 핵심 데이터다. 실측값이 축적되면 mc mirror 수동 측정을 대체할 수 있다.
13. 배포 체크리스트
- 스테이징 storage class
reclaimPolicy: Delete,volumeBindingMode: WaitForFirstConsumer - CR → PVC / Job
ownerReference설정 확인 - 스테이징 Job에 GPU 요청 없음
- 스테이징 Job에
nodeSelector명시 - 스테이징 Job 멱등 구현 (마커 유효 시 즉시 성공 종료)
- 재스테이징 시 기존 잔여물 삭제
-
terminationMessagePath결과 기록 및 Controller 판독 - 학습 Job init step 마커 재검증 + revalidate 경로
- 학습 Job PVC
readOnly: true - 학습 Job에
brick.cloudhub.io/dataset-cache라벨 - TTL 갱신을 참조 시점에 수행 (생성 시점 아님)
-
brickml-deployer에 노드별 스테이징 용량 선언 - ResourceQuota의
requests.storage및persistentvolumeclaims개수 반영 - Controller에 Secret 권한 미부여 확인
14. 미결 항목
-
manifest.hash산출 규칙 확정 — 업로드 단계에서 수집한 ETag 목록 재활용 검토 -
layout.format값 확정 — canonical 포맷이 학습 백엔드에 종속 (Ultralytics → YOLO, Detectron2/MMDetection → COCO) - revalidate 방식 vs 학습 시작마다 스테이징 Job 실행 방식 중 초기 구현 선택
- 이름 정규화 규칙 (
datasetId·nodeName→ DNS-1123) -
Failed상태 자동 재시도 정책 (백오프, 최대 횟수) - 데이터셋 삭제 시 관련 캐시 CR 연쇄 정리 트리거
- 스테이징 컨테이너 이미지 및 디스크 레이아웃 계약 문서화
- Experiment Run 상세 화면의
Preparing상태 목업 반영 (기존 4상태 목업에 추가) - 학습 스크립트 인자 변경 —
--split-s3-uri s3://...→ 로컬 마운트 경로 - validating webhook 필요 여부 —
spec불변 필드 보호 EOF