백엔드 개발에서 스키마와 ERD는
한 번 그리고 끝나는 문서가 아니라, 계속 변경되고 운영되는 구조다.
따라서 “정답 구조”보다 어떤 기준으로 선택했는지가 중요하다.
이 글에서는 실무에서 자주 마주치는 고민들을 기준으로
설계 → 확장 → 마이그레이션 → 인덱스 → 동시성 → 삭제 전략까지 정리한다.
1. 도메인에 종속되지 않는 공용 테이블을 설계하려면 어떻게 해야 할까?
문제
- 이미지, 첨부파일 같은 리소스는
유저, 게시글, 작가 등 여러 도메인에서 사용될 수 있다. - 특정 도메인에 종속되면 재사용과 확장이 어려워진다.
접근 방법
- 리소스 자체를 표현하는 테이블은 독립적으로 둔다
- 예: image, s3_image
- 도메인 테이블에서 해당 리소스를 참조(FK) 한다.
user
- id
- profile_image_id (FK → image.id)
image
- id
- s3_key
- url
나은 선택
- 도메인이 이미지를 1개만 가진다면 → FK 컬럼
- 여러 개 / 역할 / 정렬 / 이력이 필요하면 → 연결 테이블
user_image
- user_id
- image_id
- role
- sort_order
결론
리소스는 독립적으로,
관계는 도메인 쪽에서 참조하는 방향이 확장에 유리하다.
2. ORM에서 N:M 관계를 바로 쓰지 않으려면 어떻게 판단해야 할까?
문제
ORM의 ManyToMany는 편하지만, 실무에서는 잘 쓰이지 않는다.
이유
- 관계 자체에 속성이 생긴다
- 생성 시점, 상태, 역할, 정렬
- 쿼리 제어가 어렵다
- eager / cascade로 인한 성능·운영 리스크
해결 방식: 연결 엔티티
N:M을 두 개의 1:N으로 풀어낸다.
post_like
- user_id
- post_id
- created_at
- status
결론
관계가 단순 매핑을 넘어 “의미를 갖는 순간”,
연결 테이블은 엔티티가 된다.
3. 확장 요구사항이 생겼을 때 마이그레이션은 어떻게 해야 할까?
상황
- user.profile_image_id → 유저가 여러 이미지를 가질 수 있게 변경
단계적 접근 (Expand → Migrate → Contract)
- 기존 구조 유지 + 신규 테이블 추가
- 기존 데이터를 신규 구조로 이관(backfill)
- 일정 기간 함께 유지하며 검증
- 완전 전환 후 기존 컬럼 제거
중요한 포인트
- 한 번에 구조를 바꾸지 않는다
- 데이터 이관은 배치/스크립트로 수행
- 재시작 가능한 구조(멱등성)를 만든다
결론
마이그레이션은 “한 번에 끝내는 작업”이 아니라
단계적으로 안전하게 옮기는 과정이다.
4. dual-write는 어떻게 구현하고 왜 필요한가?
언제 필요한가
- 신규 구조로 전환 중
- 기존 데이터와 신규 데이터의 정합성을 유지해야 할 때
개념
- 전환 기간 동안 기존 컬럼 + 신규 테이블에 동시에 기록
- 같은 트랜잭션으로 묶어 원자성 보장
장점
- 데이터 누락 방지
- 점진적 전환 가능
결론
dual-write는 임시 전략이지만,
무중단 전환을 가능하게 하는 안전장치다.
5. “트랜잭션을 짧게 유지한다”는 건 무슨 의미일까?
오해
- timeout 설정
- 트랜잭션 옵션 조절
실제 의미
- 한 트랜잭션이 처리하는 데이터 양을 줄인다
- 오래 걸리는 작업은 트랜잭션 밖으로 분리
배치에서의 적용
- 대량 데이터 → chunk 단위로 처리
- N건 처리 → commit → 다음 N건
결론
트랜잭션을 짧게 유지한다는 건
작게 나누고 자주 커밋한다는 뜻이다.
6. 중복 데이터 문제를 락으로 해결해야 할까?
결론부터
락은 최선이 아니라 최후의 수단이다.
우선순위
- DB 무결성 제약(UNIQUE)
- 멱등성 설계(upsert)
- 배치 분할 + 재시도
- 그 다음에 락
락이 필요한 경우
- 재고 차감
- 포인트 사용
- 상태 전이처럼 경쟁이 치명적인 경우
결론
중복 insert 문제는
락의 문제가 아니라 설계의 문제다.
7. 인덱스는 언제, 어떤 기준으로 추가해야 할까?
기본 원칙
- 실제 쿼리 패턴 기준
- WHERE
- JOIN
- ORDER BY
- LIMIT
외래키 인덱스
- JOIN 조건에 자주 쓰이면 필수
- FK 제약과 인덱스는 별개
인덱스가 효과 없는 경우
- 조건 없는 전체 정렬
- 낮은 카디널리티 컬럼
- 쓰기 비중이 매우 높은 테이블
결론
인덱스는 “있으면 좋은 것”이 아니라
필요할 때만 추가하는 도구다.
8. soft delete와 UNIQUE 제약은 어떻게 함께 써야 할까?
문제
- soft delete 시 기존 row가 남아 UNIQUE 충돌 발생
해결
- 활성 데이터만 유니크하게 보장
- (user_id, post_id, deleted_at)
- 또는 deleted_at IS NULL partial index
철학
무결성은 애플리케이션이 아니라
DB가 최종적으로 보장해야 한다.
9. soft delete 대신 hard delete는 언제 쓸까?
hard delete가 적합한 경우
- 임시 데이터
- 보안/개인정보
- 재생성 가능한 데이터
soft delete가 적합한 경우
- 주문/결제
- 이력/감사
- 통계에 필요한 데이터
결론
삭제 전략은 기술 문제가 아니라
데이터 생명주기 문제다.
전체 결론
- 스키마와 ERD 설계에는 “정답 구조”가 없다
- 중요한 건:
- 요구사항
- 변경 가능성
- 운영 시나리오
- DB는 단순 저장소가 아니라 규칙을 강제하는 계층
- 락, 인덱스, soft delete는 최후의 선택지
좋은 설계란
“지금 요구사항에 맞으면서,
바뀔 때 무너지지 않는 구조”다.
반응형
'나의 개발 > 데이터베이스' 카테고리의 다른 글
| 트랜잭션 읽기와 격리 수준 (Isolation Level) 정리 (3) | 2025.12.29 |
|---|---|
| 관계형 데이터베이스(RDB)란 무엇인가? (0) | 2025.12.29 |
| 인덱스 클러스터링 팩터란? (0) | 2025.12.26 |
| 클러스터드 인덱스란? (3) | 2025.12.26 |
| DB 스키마와 ERD를 설계·확장·운영하려면 어떻게 해야 할까? (NestJS + TypeORM) (0) | 2025.12.20 |
댓글