12 KiB
12 KiB
Cloud# — 데이터베이스 설계서
1. 엔티티 목록
| 엔티티 | 설명 |
|---|---|
User |
사용자 인증 정보 및 스토리지 한도 관리 |
Folder |
사용자별 폴더 트리 구조 표현 |
FileItem |
파일 메타데이터 및 저장소 객체 연결 |
UploadSession |
tus 기반 업로드 진행 상태 추적 |
2. ER 다이어그램
3. 엔티티 상세 설계
3.1 User
사용자의 인증 정보와 스토리지 한도를 관리하는 엔티티.
주요 역할
- 로그인 식별자 관리
- 권한(role) 관리
- 전체 사용 가능 용량 / 사용 중 용량 관리
컬럼 정의
| 컬럼명 | 타입 | Null | 설명 |
|---|---|---|---|
id |
UUID / BIGINT | NOT NULL | PK — 내부 식별자 |
email |
VARCHAR | NOT NULL | 로그인 식별자. UNIQUE |
password_hash |
VARCHAR | NOT NULL | 해시된 비밀번호 |
display_name |
VARCHAR | NULL | 표시 이름 |
role |
ENUM | NOT NULL | ADMIN | USER 등 |
storage_allowed_bytes |
BIGINT | NULL | 허용 용량. NULL 이면 무제한 |
storage_used_bytes |
BIGINT | NOT NULL | 현재 사용 중 용량. 기본값 0 |
created_at |
TIMESTAMP | NOT NULL | 계정 생성 일시 |
updated_at |
TIMESTAMP | NOT NULL | 마지막 수정 일시 |
deleted_at |
TIMESTAMP | NULL | 소프트 삭제 일시 |
설계 포인트
email은 UNIQUE 제약으로 중복 가입 방지storage_allowed_bytes가NULL이면 무제한 처리- 애플리케이션 레이어에서
NULL여부를 먼저 확인
- 애플리케이션 레이어에서
storage_used_bytes는 집계 캐시 컬럼으로 운영- 즉시 조회 성능은 우수하나, 파일 업로드·삭제 시 반드시 동기화 필요
- 정합성 보장을 위해 트랜잭션 내에서 함께 갱신
3.2 Folder
사용자별 폴더 트리를 표현하는 엔티티.
주요 역할
- 폴더 계층 구조 표현
- 탐색기 경로 구성
- 파일의 논리적 위치 제공
컬럼 정의
| 컬럼명 | 타입 | Null | 설명 |
|---|---|---|---|
id |
UUID / BIGINT | NOT NULL | PK |
owner_user_id |
FK → User | NOT NULL | 소유 사용자 |
parent_folder_id |
FK → Folder | NULL | 부모 폴더. NULL 이면 루트 폴더 |
name |
VARCHAR | NOT NULL | 폴더 이름 (부모 기준 상대명) |
full_path |
VARCHAR | NULL | 전체 경로 캐시 (예: /문서/프로젝트/2025) |
created_at |
TIMESTAMP | NOT NULL | 생성 일시 |
updated_at |
TIMESTAMP | NOT NULL | 수정 일시 |
deleted_at |
TIMESTAMP | NULL | 소프트 삭제 일시 |
설계 포인트
자기참조 트리 구조
parent_folder_id IS NULL → 루트 폴더
parent_folder_id = X → X 폴더의 하위 폴더
full_path 컬럼의 위치
full_path 는 원본 진실(source of truth)이 아닌 조회 최적화용 캐시 컬럼이다.
| 구분 | 컬럼 | 역할 |
|---|---|---|
| 원본 진실 | id, parent_folder_id, name |
실제 구조의 기준 |
| 캐시 | full_path |
목록 조회·검색·WebDAV 경로 변환·breadcrumb 표시용 |
- 폴더 이름 변경·이동 시
full_path와 하위 폴더의full_path를 함께 갱신해야 함 - 불일치 발생 시
id/parent_folder_id기준으로 재계산 가능
유니크 제약
같은 부모 폴더 아래에서 동일한 이름의 폴더는 허용하지 않는다.
UNIQUE (owner_user_id, parent_folder_id, name) -- 활성 데이터 기준
- 소프트 삭제 구현 시
deleted_at IS NULL조건부 유니크 인덱스 권장
루트 폴더 정책
- 사용자당 루트 폴더(
parent_folder_id IS NULL) 는 1개 권장 - 애플리케이션 레이어 또는 DB 제약으로 강제
3.3 FileItem
사용자 관점에서 보이는 파일 자체의 메타데이터를 관리하는 엔티티.
주요 역할
- 탐색기 파일 목록 조회
- 파일명, MIME, 크기, 미리보기 상태 관리
- 실제 저장소(LocalFS / S3)의 객체와 연결
컬럼 정의
| 컬럼명 | 타입 | Null | 설명 |
|---|---|---|---|
id |
UUID / BIGINT | NOT NULL | PK |
owner_user_id |
FK → User | NOT NULL | 소유 사용자 |
folder_id |
FK → Folder | NOT NULL | 소속 폴더 |
display_name |
VARCHAR | NOT NULL | 사용자에게 보이는 파일 이름 |
storage_key |
VARCHAR | NOT NULL | 실제 저장소 객체 키. UNIQUE |
size_bytes |
BIGINT | NOT NULL | 파일 크기 (>= 0) |
mime_type |
VARCHAR | NULL | MIME 타입 (예: image/jpeg) |
checksum_sha256 |
VARCHAR | NULL | 무결성 검증용 해시 |
preview_status |
ENUM | NOT NULL | 미리보기 파이프라인 상태 |
metadata_json |
JSON / TEXT | NULL | 파일 종류별 가변 메타데이터 |
created_at |
TIMESTAMP | NOT NULL | 업로드 완료 일시 |
updated_at |
TIMESTAMP | NOT NULL | 수정 일시 |
deleted_at |
TIMESTAMP | NULL | 소프트 삭제 일시 |
설계 포인트
storage_key 와 display_name 분리
display_name : 사용자에게 보이는 이름 → "보고서 최종.pdf"
storage_key : 저장소의 실제 키 → "user/12/ab/cd/f83e...uuid.bin"
- 파일 이름 변경 시
display_name만 변경하고 저장소 객체는 그대로 유지 storage_key가 외부에 노출되지 않도록 주의
metadata_json 활용 예시
// 이미지
{ "width": 1920, "height": 1080 }
// 비디오
{ "duration": 3600, "codec": "h264", "bitrate": 4000 }
// 문서
{ "page_count": 42 }
preview_status 상태 흐름
PENDING → PROCESSING → DONE
→ FAILED
(미지원 포맷) → UNSUPPORTED
파일명 중복 정책
같은 폴더 내에서 동일한 파일 이름은 허용하지 않는다.
UNIQUE (owner_user_id, folder_id, display_name) -- 활성 데이터 기준
- 소프트 삭제 구현 시
deleted_at IS NULL조건부 유니크 인덱스 권장
3.4 UploadSession
tus 기반 대용량 업로드의 진행 상태를 관리하는 엔티티.
주요 역할
- 업로드 시작 ~ 완료까지의 진행 상태 추적
- 실패·중단·재개 처리
- 임시 저장 위치와 최종
FileItem생성 연결
컬럼 정의
| 컬럼명 | 타입 | Null | 설명 |
|---|---|---|---|
id |
UUID / BIGINT | NOT NULL | PK |
owner_user_id |
FK → User | NOT NULL | 업로드 요청 사용자 |
target_folder_id |
FK → Folder | NOT NULL | 완료 후 파일이 위치할 폴더 |
token |
VARCHAR | NOT NULL | 외부 공개용 식별자. UNIQUE |
tus_upload_id |
VARCHAR | NULL | tus 서버와의 매핑 ID. UNIQUE 가능 |
status |
ENUM | NOT NULL | 업로드 상태 (아래 상태 흐름 참고) |
expected_size |
BIGINT | NOT NULL | 전체 파일 크기 (bytes) |
received_size |
BIGINT | NOT NULL | 현재까지 수신된 크기 (bytes) |
original_name |
VARCHAR | NOT NULL | 업로드 원본 파일명 |
mime_type |
VARCHAR | NULL | MIME 타입 |
storage_key_temp |
VARCHAR | NULL | 임시 저장 경로 |
file_item_id |
FK → FileItem | NULL | 완료 후 생성된 FileItem 참조 |
created_at |
TIMESTAMP | NOT NULL | 업로드 세션 생성 일시 |
expires_at |
TIMESTAMP | NULL | 세션 만료 일시 |
completed_at |
TIMESTAMP | NULL | 업로드 완료 일시 |
설계 포인트
token 과 내부 id 분리
id : 내부 DB 식별자 → 외부 노출 금지
token : 클라이언트가 업로드 재개 시 사용하는 공개 식별자
status 상태 흐름
CREATED → UPLOADING → COMPLETED
→ FAILED
→ CANCELLED
→ EXPIRED
업로드 완료 처리 흐름
업로드 완료 시 아래 세 작업을 하나의 트랜잭션으로 처리한다.
1. UploadSession.status → COMPLETED
2. FileItem 신규 생성
3. User.storage_used_bytes += size_bytes
received_size 제약
received_size <= expected_size -- 항상 유지
4. 관계 정의
| 관계 | 카디널리티 | 설명 |
|---|---|---|
User → Folder |
1 : N | 한 사용자는 여러 폴더를 소유 |
Folder → Folder |
1 : N | 폴더는 하위 폴더를 가짐 (자기참조) |
User → FileItem |
1 : N | 한 사용자는 여러 파일을 소유 |
Folder → FileItem |
1 : N | 한 폴더는 여러 파일을 포함 |
User → UploadSession |
1 : N | 한 사용자는 여러 업로드 세션을 가짐 |
Folder → UploadSession |
1 : N | 한 폴더는 여러 업로드 세션의 목적지가 됨 |
UploadSession → FileItem |
1 : 0..1 | 완료된 세션은 하나의 FileItem을 생성 |
5. 제약 조건 요약
User
| 제약 | 내용 |
|---|---|
| PK | id |
| UNIQUE | email |
| CHECK | storage_used_bytes >= 0 |
Folder
| 제약 | 내용 |
|---|---|
| PK | id |
| FK | owner_user_id → User.id NOT NULL |
| FK | parent_folder_id → Folder.id NULL 허용 |
| UNIQUE | (owner_user_id, parent_folder_id, name) — 활성 데이터 기준 |
| 정책 | 사용자당 루트 폴더(parent_folder_id IS NULL) 1개 권장 |
FileItem
| 제약 | 내용 |
|---|---|
| PK | id |
| FK | owner_user_id → User.id NOT NULL |
| FK | folder_id → Folder.id NOT NULL |
| UNIQUE | storage_key |
| UNIQUE | (owner_user_id, folder_id, display_name) — 활성 데이터 기준 |
| CHECK | size_bytes >= 0 |
UploadSession
| 제약 | 내용 |
|---|---|
| PK | id |
| FK | owner_user_id → User.id NOT NULL |
| FK | target_folder_id → Folder.id NOT NULL |
| FK | file_item_id → FileItem.id NULL 허용 |
| UNIQUE | token |
| UNIQUE | tus_upload_id (NULL 제외) |
| CHECK | received_size <= expected_size |
| CHECK | expected_size >= 0 |
6. 설계 결정 사항 (Decision Log)
| # | 결정 사항 | 이유 |
|---|---|---|
| 1 | storage_used_bytes 를 집계 캐시 컬럼으로 관리 |
매 조회 시 SUM 집계 대신 즉시 응답. 대신 업로드·삭제 트랜잭션에서 동기화 필수 |
| 2 | full_path 를 캐시 컬럼으로 정의 |
폴더 이동·이름 변경 시 하위 경로 전체 갱신이 필요하지만, 조회·WebDAV·breadcrumb 성능 확보 |
| 3 | storage_key 와 display_name 분리 |
파일 이름 변경이 저장소 객체 이동 없이 메타데이터 변경만으로 처리 가능 |
| 4 | UploadSession 완료 처리를 단일 트랜잭션으로 |
FileItem 생성과 storage_used_bytes 증가의 정합성 보장 |
| 5 | 소프트 삭제 (deleted_at) 적용 |
실수 복구, 휴지통 기능 지원. 유니크 제약은 deleted_at IS NULL 조건부 인덱스로 처리 |
| 6 | token (외부) / id (내부) 식별자 분리 |
내부 PK 노출 방지. 클라이언트는 token 으로만 업로드 세션 접근 |