이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- 신규 기능을 만들 때 DB 설계는 어떤 순서로 판단해야 하는가?
- 개인 프로젝트와 기업 운영 환경에서 DB 설계 기준은 어떻게 달라지는가?
- 인프라/DBA와 초기에 어떤 내용을 공유해야 하는가?
개요
신규 기능의 DB 설계는 테이블 몇 개를 만드는 작업이 아니다. 데이터 불변식, 조회 패턴, 트랜잭션 경계, 인덱스, 마이그레이션, 운영 지표를 한 번에 설계하는 일이다.
초기 설계에서 놓친 결정은 나중에 장애와 마이그레이션 비용으로 돌아온다. 특히 백엔드 개발자는 API 계약과 DB 구조 사이의 간격을 책임져야 한다.
원리
신규 기능의 DB 설계 원리는 요구사항을 테이블로 바로 옮기기 전에 “깨지면 안 되는 규칙, 가장 많이 호출될 경로, 실패 후 복구 방식”을 먼저 고정하는 것이다. 테이블 구조는 그 결과물이지 출발점이 아니다.
좋은 백엔드 설계는 API 계약, DB 제약, 트랜잭션 경계, 인덱스, migration, 운영 지표를 한 문서에서 연결한다. 이 연결이 없으면 기능은 빨리 만들 수 있어도 운영에서 왜 느리고 왜 깨지는지 설명하기 어려워진다.
상황
예를 들어 쿠폰 발급 기능을 만든다고 하자.
- 사용자는 한 이벤트에서 쿠폰을 한 번만 받을 수 있다.
- 쿠폰 재고는 제한되어 있다.
- 발급 내역 목록을 최신순으로 본다.
- 이벤트 종료 후 통계를 낸다.
단순히 coupons, coupon_issues 테이블만 만들면 부족하다. 중복 발급, 재고 차감, 동시 요청, 목록 조회, 통계, 운영 복구를 함께 봐야 한다.
먼저 확인할 것
- 절대 깨지면 안 되는 비즈니스 불변식은 무엇인가?
- 중복을 DB 제약으로 막을 수 있는가?
- 쓰기 API와 읽기 API의 호출량은 어느 정도인가?
- 가장 많이 쓰는 목록 조회 조건과 정렬은 무엇인가?
- 트랜잭션 안에서 외부 API를 호출하는가?
- 데이터 보관 기간과 삭제 정책은 무엇인가?
- 나중에 컬럼이나 상태가 늘어날 가능성이 있는가?
이 질문은 설계 리뷰 문서에 그대로 적어도 좋다.
기능: 이벤트 쿠폰 발급
불변식: 회원은 이벤트당 1회만 발급 가능
쓰기 피크: 오픈 직후 3분간 1,000 RPS 예상
읽기 패턴: 내 쿠폰 목록 최신순, 관리자 발급 현황
정합성: 중복 발급 불가, 재고 초과 불가
완화: 이벤트별 rate limit, 대기열 가능
복구: 발급 원장 기준으로 재계산 가능해야 함이 정도를 적으면 테이블 설계가 단순 ERD가 아니라 운영 의사결정으로 바뀐다.
설계 예시
중복 발급을 막으려면 Unique 제약이 필요하다.
CREATE TABLE coupon_issues (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
coupon_id bigint NOT NULL,
member_id bigint NOT NULL,
issued_at timestamp NOT NULL DEFAULT now(),
status varchar(20) NOT NULL,
CONSTRAINT uq_coupon_issue_coupon_member UNIQUE (coupon_id, member_id)
);서비스 코드의 중복 검사는 사용자 메시지를 좋게 만들 수 있지만, 최종 안전장치는 Unique 제약이다.
@Transactional
public CouponIssueResult issue(Long couponId, Long memberId) {
Coupon coupon = couponRepository.findByIdForUpdate(couponId)
.orElseThrow();
coupon.decreaseStock();
try {
couponIssueRepository.save(new CouponIssue(couponId, memberId));
} catch (DataIntegrityViolationException ex) {
throw new DuplicateCouponIssueException();
}
return CouponIssueResult.success();
}이 코드는 이해하기 쉽지만 모든 요청이 같은 쿠폰 row를 잠그므로 hot row 병목이 생길 수 있다. 트래픽이 큰 이벤트라면 재고 차감 방식을 별도 설계해야 한다.
재시도와 중복 요청도 설계에 포함한다.
ALTER TABLE coupon_issues
ADD COLUMN idempotency_key varchar(100);
CREATE UNIQUE INDEX uq_coupon_issues_idempotency
ON coupon_issues (idempotency_key);클라이언트, API gateway, 서버 내부 retry가 같은 요청을 여러 번 보낼 수 있다. 중복을 “사용자가 버튼을 두 번 눌렀다” 수준으로만 보면 결제, 포인트, 쿠폰 같은 기능에서 사고가 난다.
백엔드 개발자가 의심해야 할 원인
신규 기능에서 나중에 장애가 되는 설계 냄새는 다음이다.
exists검사 후 insert로 중복을 막으려 한다.- 재고, 포인트, 카운터를 하나의 row에 집중시킨다.
- 목록 API 인덱스를 설계하지 않는다.
- 상태값을 문자열로만 두고 가능한 전이를 정의하지 않는다.
- 삭제 정책 없이 소프트 삭제 컬럼만 추가한다.
- 운영 배치와 사용자 API가 같은 테이블을 동시에 크게 갱신한다.
인프라/DBA와 같이 볼 것
기업 운영 환경에서는 초기에 다음을 공유하면 좋다.
- 예상 row 증가량
- 피크 쓰기 QPS
- 가장 무거운 조회 SQL
- 필요한 Unique/FK/Check 제약
- 대형 테이블 변경 가능성
- 백업과 보관 요구사항
- 배치 실행 시간대
- replica 읽기 가능 여부
인프라/DBA는 DB 서버 크기만 보는 사람이 아니다. 연결 수, I/O, 복제, 백업, DDL 락, 장애 복구까지 본다. 개발자가 데이터 증가와 접근 패턴을 알려줘야 정확히 준비할 수 있다.
하지 말아야 할 대응
- “일단 컬럼을 넉넉하게 문자열로 둔다”로 시작하지 않는다.
- 동시성 요구사항을 나중으로 미루지 않는다.
- Unique 제약 없이 애플리케이션 코드만 믿지 않는다.
- 대량 트래픽 기능에서 hot row를 무시하지 않는다.
- 운영 마이그레이션 계획 없이 엔티티만 먼저 바꾸지 않는다.
실전 팁
- 테이블 설계 전에 쓰기 API, 읽기 API, 운영 API를 나눠 적는다.
- 불변식은 코드 검증과 DB 제약 중 어디에서 최종 보장할지 정한다.
- 목록 API는 첫 설계부터 정렬 기준과 인덱스를 함께 잡는다.
- 동시성 요구가 있는 기능은 단위 테스트보다 동시 요청 테스트를 준비한다.
- 인프라/DBA에게 예상 row 증가량과 피크 쓰기량을 초기에 공유한다.
- 기능 설계 문서에 불변식, 예상 QPS, 주요 SQL, 인덱스, migration 순서, 복구 기준을 한 번에 적는다.
- 재시도 가능성이 있는 쓰기 API는 idempotency key나 Unique 제약으로 중복 실행을 막는다.
주니어 팁
테이블을 만들기 전에 API를 세 가지로 나눠 적는다.
- 쓰는 API
- 읽는 API
- 운영자가 보는 API
각 API에 대해 “조건”, “정렬”, “한 번에 가져오는 건수”, “동시에 들어올 수 있는 요청”을 적으면 인덱스와 트랜잭션 경계가 보이기 시작한다.
시니어 팁
시니어는 기능의 성공 케이스보다 실패와 변경을 먼저 본다.
- 중복 요청이 들어오면 어떻게 되는가?
- 외부 시스템이 실패하면 상태는 어디까지 저장되는가?
- 배포 중 구버전과 신버전이 동시에 동작해도 되는가?
- 대형 테이블이 된 뒤에도 컬럼을 추가할 수 있는가?
- 장애 후 데이터 재처리가 가능한가?
Guru급 팁
트래픽이 큰 기능은 “정확한 즉시 처리”를 당연하게 요구하지 않는다. 재고, 카운터, 랭킹, 통계는 이벤트 적재, 비동기 처리, eventual consistency, idempotency key, outbox 패턴으로 분리할 수 있다.
좋은 설계는 항상 강한 일관성을 쓰는 것이 아니라, 강한 일관성이 필요한 부분과 지연 허용이 가능한 부분을 구분한다.
위험 신호!
- 중복 요청과 재시도 시나리오를 설계하지 않는다.
- 목록 API의 인덱스 없이 기능 개발을 끝낸다.
- 재고나 카운터를 하나의 row에 집중시키고 트래픽 추정을 하지 않는다.
- 마이그레이션 순서 없이 엔티티 변경만 먼저 진행한다.
- 인프라/DBA에게 데이터 증가량과 작업 시간대를 공유하지 않는다.
- 성공 케이스 API만 있고 중복 요청, 부분 실패, 외부 시스템 실패 시나리오가 없다.
- 운영자가 조회할 화면과 배치가 어떤 조건으로 데이터를 읽을지 설계에서 빠져 있다.
확인 질문
- 신규 기능의 DB 설계에서 가장 먼저 찾아야 하는 것은 무엇인가?
- 깨지면 안 되는 비즈니스 불변식과 가장 자주 호출될 조회/쓰기 패턴이다.
- 중복 발급을 DB에서 막아야 하는 이유는 무엇인가?
- 동시 요청이 애플리케이션 중복 검사를 동시에 통과할 수 있으므로 최종 저장 단계에서 Unique 제약이 필요하기 때문이다.
- 인프라/DBA에게 초기에 공유해야 하는 정보는 무엇인가?
- 예상 데이터 증가량, 피크 QPS, 핵심 SQL, 제약 조건, 배치와 마이그레이션 계획, 보관 요구사항이다.
- 신규 기능 설계 리뷰 문서에 DB 관점으로 포함할 항목은 무엇인가?
- 불변식, 쓰기/읽기 패턴, 예상 QPS, 주요 SQL, 인덱스, 트랜잭션 경계, migration 순서, 복구와 재처리 기준이다.