← posts/b.log()

blog92@web:~$ cat posts/db-design-24-design-review.md

DATABASE5 min read

DB 설계 A to Z 24강 — 설계 리뷰 방법론

설계 리뷰의 입력물과 체크리스트, 안티패턴 역추적, 발견 사항의 심각도 분류, ADR로 남기는 결정, 그리고 카탈로그 쿼리와 린터로 자동화할 수 있는 항목을 정리한다.

설계와 변경, 분리까지 다뤘으니 이제 그 결과를 검증하는 방법입니다.

1. 리뷰의 목적

설계 리뷰는 "문법 검사"가 아니라 1강의 좋은 설계 기준(정확성, 무결성, 비중복성, 성능, 변경 용이성)을 증거로 확인하는 과정입니다. 리뷰어는 산출물뿐 아니라 그 결정의 근거를 봅니다.

2. 리뷰 입력물

입력물확인 내용
요구사항·업무 규칙 목록 (2강)모든 규칙이 어딘가에 구현되었는가
개념 ERD (3~4강)업무 담당자가 읽고 동의했는가
DDL실제 구현
쿼리 프로파일·볼륨 추정 (2강)인덱스·파티션 근거
반정규화·예외 결정 기록 (10강)근거와 정합성 수단
마이그레이션 계획 (22강)운영 중 적용 가능성

3. 체크리스트

A. 모델링

  • 모든 엔티티에 명확한 정의가 있는가 (용어 사전)
  • 관계마다 카디널리티와 참여도가 정의되었는가
  • "같은 사건이 두 번 일어날 수 있는가"를 확인했는가 (재처리, 검수 반복)
  • 이력이 필요한 속성을 식별했는가 (18강)
  • 다중값 속성이 한 컬럼에 저장되지 않았는가

B. 정규화와 중복

  • 자연 후보키 기준으로 3NF를 만족하는가
  • 반정규화된 모든 컬럼에 결정 기록과 동기화 수단이 있는가
  • 같은 의미의 컬럼이 다른 이름·타입으로 여러 곳에 있지 않은가

C. 키와 제약

  • 모든 테이블에 PK가 있는가
  • 대리키를 쓴 테이블에 자연키 UNIQUE가 있는가
  • FK가 정의되어 있고, FK 컬럼에 인덱스가 있는가
  • CASCADE 사용처의 삭제 범위를 검토했는가
  • NULL 허용 컬럼마다 NULL의 의미가 정의되었는가
  • 2강 업무 규칙 중 DB로 표현 가능한 것이 CHECK/UNIQUE/EXCLUDE로 구현되었는가
  • 멀티테넌시라면 tenant_id가 모든 키에 포함되었는가

D. 타입

  • 금액·수량이 부동소수점이 아닌가
  • 절대 시점과 벽시계 시각이 구분되었는가
  • 조인 컬럼끼리 타입·길이·콜레이션이 일치하는가
  • Oracle 문자열 길이 단위, MySQL 문자셋이 확인되었는가
  • 상태가 불리언 여러 개로 표현되지 않았는가

E. 성능

  • 상위 쿼리마다 사용할 인덱스가 설명 가능한가
  • 중복·미사용 인덱스가 없는가
  • 대용량 테이블의 증가량과 보관 정책이 정의되었는가
  • 대리키 생성 방식이 삽입 패턴에 적합한가 (무작위 UUID 주의)

F. 운영과 진화

  • 스키마 변경이 버전 관리되는가
  • 대용량 테이블 변경 시 온라인 적용 방법이 있는가
  • 감사 컬럼(created_at, updated_at, created_by)의 정책이 일관적인가
  • 소프트 삭제 사용 시 UNIQUE와 조회 조건이 처리되었는가
  • 개인정보 컬럼이 식별되고, 암호화·마스킹·보관 기한이 정해졌는가

4. 안티패턴 역추적

체크리스트와 별개로, 알려진 안티패턴 목록을 설계에 하나씩 대입해 봅니다.

안티패턴설계에서 찾는 신호관련 강의
Jaywalking (콤마 구분 목록)VARCHAR 컬럼 이름이 복수형 (category_ids)3, 8강
Naive Treesparent_id만 있고 깊은 조회 요구가 많음17강
ID Required모든 테이블에 무조건 id, 자연키 UNIQUE 누락5강
Keyless EntryFK 없음13강
EAVattr_name, attr_value 컬럼19강
Polymorphic Associationstarget_type, target_id 쌍19강
Multicolumn Attributestag1, tag2, tag38강
Metadata Tribblesorder_2025, order_2026 테이블 복제16강 (파티셔닝으로 대체)
Rounding ErrorsFLOAT 금액12강
31 Flavors자주 바뀌는 값 목록을 CHECK/ENUM으로 고정12강
Fear of the UnknownNULL 대신 '', 0, '1900-01-01' 사용13강
무작위 UUID PKInnoDB에 UUIDv4 PK14강
이중 쓰기 (시스템 간)앱이 DB와 메시지 브로커에 각각 쓰기23강

5. 리뷰 진행 방식

  1. 작성자가 요구사항부터 설명한다. DDL부터 보면 근거를 놓칩니다.
  2. 리뷰어는 구체적인 시나리오로 질문한다. "상품이 다른 카테고리로 이동하면 과거 매출은 어느 카테고리로 집계되나요?"
  3. 발견 사항은 심각도로 분류한다.
심각도기준예시
차단데이터 손실·오염 가능대체키 UNIQUE 누락, 금액 FLOAT
높음운영 중 수정 비용이 큼PK 타입 INT, 파티션 키 부적절
중간성능·유지보수 문제FK 인덱스 누락, 명명 불일치
낮음개선 제안주석 누락
  1. 결정 사항은 ADR(Architecture Decision Record) 형식으로 남긴다.
markdown
# ADR-012: 처리 실적 테이블 월 단위 파티셔닝
 
## 상태
승인 (2026-09-17)
 
## 맥락
일 5만 건, 3년 보관. 월별 보관 기한 초과 데이터 삭제가 필요하며,
주요 조회는 처리일 범위 조건을 포함한다.
 
## 결정
work_date 기준 월 단위 RANGE 파티셔닝. PK는 (log_id, work_date).
 
## 결과
- 오래된 데이터 삭제가 파티션 분리로 대체됨
- log_id 단독 유일성은 DB가 보장하지 않음 (IDENTITY로 실질 보장)
- 처리 실적을 참조하는 테이블은 work_date를 함께 보유해야 함

6. 자동화할 수 있는 것

항목방법
PK 없는 테이블카탈로그 쿼리
FK 인덱스 누락카탈로그 쿼리
명명 규칙 위반스키마 린터 (예: squawk, sqlfluff 규칙)
위험한 마이그레이션마이그레이션 린터 (락을 거는 DDL 탐지)
미사용·중복 인덱스통계 뷰
환경 간 스키마 차이스키마 diff 도구

PostgreSQL: 인덱스 없는 FK 찾기

sql
SELECT c.conrelid::regclass AS table_name,
       c.conname            AS fk_name
FROM pg_constraint c
WHERE c.contype = 'f'
  AND NOT EXISTS (
      SELECT 1 FROM pg_index i
      WHERE i.indrelid = c.conrelid
        AND (i.indkey::int2[])[0:cardinality(c.conkey) - 1] = c.conkey
  );

FK 컬럼이 어떤 인덱스의 선두 컬럼들과 같은 순서로 일치하는지 검사합니다.

24강 정리

  1. 리뷰는 좋은 설계 기준을 근거와 함께 확인하는 과정이다.
  2. 체크리스트와 안티패턴 역추적을 함께 사용한다.
  3. 발견 사항은 심각도로 분류하고, 결정은 ADR로 남긴다.
  4. 기계적으로 검사 가능한 항목은 자동화한다.
COMMENTS (…)

댓글을 불러오는 중이에요.

NEW COMMENT0 / 1000
⌘↵ 전송

blog92@web:~$ cd ..