9/04(D-12) 18:00 ERD FREEZE
이 시점 이후 스키마 변경은 PM 승인 + 마이그레이션 + 클라이언트 대응이 세트로 따라옵니다. 프리즈 전에 최대한 의심하고, 프리즈 후에는 최대한 참으세요.
설계 규칙
정규화
- 모든 테이블은 1NF → 2NF → 3NF 순서로 정규화한다.
- 역정규화는 명시적 근거가 있을 때만 허용한다 (근거를 이 문서 하단에 기록).
- 근거 없는 역정규화는 금지.
연결
- 모든 테이블은 노드-엣지로 연결되어야 한다.
- 고립된 테이블은 설계 오류로 간주한다. 어디에도 안 붙는 테이블이 생겼다면 모델링이 틀린 것이다.
- 테이블 간 관계는 Repository 레이어에서 JOIN 또는 ID 참조로 구현한다.
Fractal 대응
- 테이블 1개 = 11-File Set 1벌 = AI/사람 위임 단위 1개
- 하나의 Router 는 하나의 테이블만 담당한다. 이게 SRP 의 실체다.
공통 컬럼 규약
모든 테이블이 갖는 컬럼입니다. 예외를 만들지 않습니다.
| 컬럼 | 타입 | 설명 |
|---|---|---|
id |
BIGSERIAL PK | 내부 식별자 |
public_id |
UUID UNIQUE | 외부 노출용. API 응답에는 id 대신 이걸 쓴다 |
created_at |
TIMESTAMPTZ | 생성 시각 (UTC 저장, 표시 시 KST 변환) |
updated_at |
TIMESTAMPTZ | 수정 시각 |
deleted_at |
TIMESTAMPTZ NULL | 소프트 삭제. NULL 이면 살아 있음 |
왜
public_id 를 따로 두나연속된 정수 id 를 URL 에 노출하면 남의 데이터 개수와 순서가 새어 나가고,
주소창의 숫자를 바꿔보는 것만으로 접근 시도가 가능해집니다. 나중에 바꾸려면 전 클라이언트를 고쳐야 하니 처음부터 나눕니다.
테이블 목록 (초안)
도메인 테이블은 기획서 대조가 필요합니다
아래에서 기반 테이블(인증·프로필·파일·AI)은 기획과 무관하게 확정입니다. 도메인 테이블은 자리만 잡아둔 것이며, 이름과 컬럼은 기획서 v6 대조 후 확정합니다.
기반 — 확정
| 테이블 | 역할 | 주요 컬럼 | 담당 |
|---|---|---|---|
user |
계정 | email UNIQUE, password_hash, status |
장민석 |
user_profile |
프로필 | user_id FK, nickname, avatar_file_id FK |
장민석 |
refresh_token |
토큰 갱신 | user_id FK, token_hash, expires_at, revoked_at |
장민석 |
file |
업로드 파일 | owner_user_id FK, path, mime, size |
장민석 |
conversation |
AI 대화 세션 | user_id FK, title, last_message_at |
신채연 |
message |
대화 메시지 | conversation_id FK, role, content, token_in, token_out |
신채연 |
tool_call |
툴 호출 기록 | message_id FK, tool_name, arguments, result, latency_ms |
신채연 |
llm_usage |
비용 집계 | user_id FK, model, tokens, cost, occurred_at |
신채연 |
도메인 — 기획서 대조 필요
| 테이블 | 역할 | 상태 |
|---|---|---|
{domain_main} |
핵심 리소스 #1 (MUST M3) | 이름 미정 |
{domain_sub} |
핵심 리소스 #2, #1과 관계 (MUST M4) | 이름 미정 |
{domain_tag} |
분류/태그 (필요 시) | 미정 |
관계도 (기반 부분)
user ──1:1── user_profile ──N:1── file
│
├──1:N── refresh_token
├──1:N── file (owner)
├──1:N── conversation ──1:N── message ──1:N── tool_call
└──1:N── llm_usage
user ──1:N── {domain_main} ──1:N── {domain_sub}
└──N:1── file
인덱스 계획
목록 조회가 느려지는 건 대부분 인덱스가 없어서입니다. 처음부터 걸어둡니다.
| 테이블 | 인덱스 | 이유 |
|---|---|---|
user |
email UNIQUE |
로그인 조회 |
refresh_token |
(user_id, expires_at) |
갱신 시 유효 토큰 탐색 |
conversation |
(user_id, last_message_at DESC) |
내 대화 목록 정렬 |
message |
(conversation_id, created_at) |
대화 복원 |
llm_usage |
(user_id, occurred_at) |
일자별 비용 집계 |
| 모든 테이블 | deleted_at 부분 인덱스 |
소프트 삭제 필터 |
마이그레이션
- 도구: Alembic. 스키마 변경은 반드시 리비전 파일로 남긴다.
- 리비전 하나 = 변경 하나. 여러 변경을 한 리비전에 몰지 않는다.
downgrade를 반드시 작성한다. 롤백 못 하는 마이그레이션은 배포 전날 공포의 근원이 된다.- D-4(9/12) 에
downgrade→upgrade왕복을 실제로 검증한다 (BE-29).
기획서 v6 대조
- 도메인 테이블 3종의 실제 이름·컬럼 확정
- 기획서의 화면에 나오는 모든 데이터 항목이 어느 테이블에 있는지 매핑
- 고립 테이블 0개 확인
- 역정규화가 필요한 지점이 있는지 판단 (있다면 근거를 아래 로그에)
결정 로그
| 날짜 | 결정 | 이유 |
|---|---|---|
| 2026-09-03 | public_id(UUID) 를 외부 노출용으로 분리 |
순번 노출·순회 접근 차단. 나중에 바꾸면 전 클라이언트 수정 |
| 2026-09-03 | 소프트 삭제(deleted_at) 기본 채택 |
14일 안에 실수로 지운 데이터를 복구할 수단이 필요 |
| 2026-09-03 | 시각은 UTC 저장 · KST 표시 | 앱/웹/서버 타임존이 섞이면 오픈 당일 재현 안 되는 버그가 난다 |