Skip to main content

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의 노드 고정에 사용
sourceMinIO 위치 및 읽기 전용 크리덴셜 참조
manifest마커 검증 기준값. 업로드 검증 단계에서 산출
layout학습 프레임워크 디렉터리 규약 및 버전
capacityPVC 요청량. 용량 회계의 단위
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의미다음
PendingCR 생성됨. 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에서 실행되므로 대상 노드의 파일시스템에 접근할 수단이 없다.

주체위치역할권위
Controllerbrick-systemPVC·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 실행 중학습 진행 중 배너
FinishedMLflow메트릭 표시
Failed — 데이터 준비 실패스테이징 Job 실패신규. 용량·노드 문제 → 관리자 문의 또는 다른 노드 선택 안내
Failed — 학습 코드 실행 전 오류K8s Job만, MLflow 기록 없음기존
Failed — 학습 실패MLflow 기록 있음기존

표시 규칙

  • 진행률은 상태 배너 내부에 배치한다. 데이터 준비는 파일 수 기준으로 정확한 비율 산출이 가능하다
  • 학습 진행률은 MLflow의 최근 기록 epoch 기준. 에폭 개념이 없는 방식(sklearn 등)은 경과 시간만 표시한다
  • Pod·로그는 현재 활성 단계의 것을 표시한다 (준비 중이면 스테이징 파드)
  • 캐시 히트로 준비 단계가 수 초 만에 끝나도 배너를 노출한다. 실행마다 시작 시간이 다른 이유를 사용자가 이해하게 된다
  • Datasets 섹션에 캐시 출처를 함께 표기한다 (예: 캐시 사용 (worker-01))
  • Run 목록의 Status 컬럼에 Preparing을 추가한다

11. RBAC​

Controller​

리소스권한
brickdatasetcachesget, list, watch, update, patch, delete
brickdatasetcaches/statusget, update, patch
persistentvolumeclaimsget, list, watch, create, delete
jobs (batch)get, list, watch, create, delete
podsget, list, watch — terminationMessage 판독용
eventscreate, patch
nodesget, 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