Posts DB 테이블 설계할 때 생각해볼 것들
Post
Cancel

DB 테이블 설계할 때 생각해볼 것들

테이블 설계에 답은 없다고 생각하지만, 자주 직면하는 상황들이 있는 것 같다.

좋은 애플리케이션 코드 ?

  • 유지보수하기가 좋아야한다고 생각.
  • 애플리케이션 코드 유지보수 ?
    • 새로운 기능 추가
    • 기존 기능 수정
    • 버그 수정
    • 성능 최적화
    • 기타 등등
  • 이러한 유지보수의 실질적인 행위들을 생각해보면, 일반적으로 코드를 작성하는 시간보다 읽고 파악하는 시간이 훨씬 많은 것 같다.
  • 따라서, 애플리케이션 코드가 유지보수하기 좋으려면:
    • 읽고 이해하기가 쉽다.
    • 변경에 의한 영향 범위를 파악하기가 쉽다.
  • 이를 위해서는 코드의 가독성, 예측 가능성, 적절한 책임의 분리, 캡슐화 등이 중요하다고 생각한다.

좋은 테이블 구조 (DB 설계) ?

물론 테이블명, 컬럼명을 명확하게 해서 파악이 쉽게 하는것도 중요 하지만, 애플리케이션 코드만큼 자주 변경이 일어나지 않는다고 생각하고 애플리케이션 코드 유지보수 때처럼 많은 시간을 들여서 읽고 파악하는 경우는 드문것 같다. 따라서, 성능 / 데이터 무결성 / 운영 상황에 영향 최대한 덜 주는 게 중요한것 같다.

  • DB
    • 슬로우 쿼리 개선
    • 불필요 테이블, 컬럼 삭제
    • DML 처리

1. 성능

데이터 특징을 고려

  • 인덱스 전략
    • 자주 사용되는 검색 조건 / JOIN 조건 / 정렬 기준에 맞춰 인덱스 설계.
    • 단일 인덱스뿐 아니라 복합 인덱스 고려.
    • 불필요한 인덱스는 쓰기 성능 저하를 초래 → 주기적으로 모니터링 필요.
  • 쿼리 패턴 고려
    • DB 설계는 결국 어떤 쿼리를 자주 날릴지에 따라 좌우됨.
    • 예: 로그성 데이터 → 조회보다 insert가 많음 → append-only 구조 + 파티셔닝 적합.
    • 예: 주문/결제 → 조회/갱신이 많음 → 정규화 + FK 무결성 우선.
  • 비정규화
    • 조회 성능 개선을 위해 일부러 중복 컬럼이나 집계 테이블을 둠.
    • 단, 반드시 “원천 데이터의 진실은 어디에 있는가?”를 명확히 해야 함.
    • 반정규화한 컬럼/테이블은 트리거나 애플리케이션에서 동기화 책임을 져야 함.
  • 락 전략
    • 비관적 락(pessimistic locking): 경쟁이 심할 때 유용하지만 DB 락을 오래 잡으면 성능 저하.
    • 낙관적 락(optimistic locking, 버전 컬럼)과의 조합 고려 필요.
    • 실무에서는 DB 수준보다는 애플리케이션 레벨에서 락을 잘 관리하는 게 더 일반적임.
  • 확장성 고려
    • 테이블이 수억 건 이상 갈 수 있다면 미리 파티셔닝/샤딩 전략을 잡아야 함.
    • 단일 서버에서 처리 가능한 범위를 넘어설 수 있다는 가정 하에 설계하는 게 안전.

2. 무결성

  • 제약조건
    • PRIMARY KEY, UNIQUE, NOT NULL, CHECK, FOREIGN KEY 등 적극 활용.
    • 특히 FK는 “성능 때문에 빼자”는 경우가 많음 → FK를 빼면 무결성을 애플리케이션 코드로 보장해야 함 → 장기적으로 더 위험.
  • 정규화
    • 데이터 중복으로 인한 불일치를 줄이고, 참조 무결성 보장.
    • 단, 성능/운영 상 필요한 부분은 반정규화.
  • 업데이트/삭제 이상 방지
    • 정규화를 통해 불필요한 갱신/삭제 이상(anomaly)을 막음.
    • 예: 고객 이름을 Users 한 곳에서만 바꾸면 전체 주문에 반영되는 구조.

3. 운영 안정성

  • PK 전략 (인조키 vs 자연키)
    • 인조키(surrogate key): 의미 없는 숫자 ID (AUTO_INCREMENT, UUID). 운영에 유리 (변경 불필요, 단순).
    • 자연키(natural key): 주민번호, 이메일 같은 실제 의미 있는 값. 변경 가능성이 있어서 PK로는 위험.
    • ⇒ 원칙적으로는 인조키를 PK로 두고, 자연키는 UNIQUE 제약으로 관리.
  • DDL/DML 영향
    • 대규모 DDL (예: ALTER TABLE ADD COLUMN, MODIFY COLUMN)은 테이블 락 발생 가능 → 운영 중 장애 유발.
    • MySQL, PostgreSQL 등은 일부 DDL이 Online DDL 지원하지만, DB 버전에 따라 다름.
    • 운영에서는 DDL도 배포 계획 잡고 진행해야 함.
  • 배포/마이그레이션 전략
    • DB 스키마 변경은 backward compatibility 고려해야 함.
      • 컬럼 추가: 괜찮음
      • 컬럼 삭제/타입 변경: 영향도 큼 → 단계적 rollout 필요
  • 로그성 데이터 분리
    • 운영 DB와 로그/통계성 데이터를 분리하면 본 서비스에 영향 최소화 가능.
    • 아카이빙 테이블, 별도 DW(Data Warehouse) 활용.

✅ 대규모 DDL 최소화를 위한 전략

DB 스키마 변경(DDL)은 운영 중 장애를 유발할 수 있음. 따라서 사전(설계 단계)과 사후(운영 단계) 모두에서 전략이 필요하다.


설계 단계에서의 전략 (사전 예방)

  • PK/Key
    • 인조키(surrogate key) → 구조 안정성 확보
    • 자연키(natural key)는 UNIQUE 제약으로만 관리
  • 컬럼 설계
    • 충분히 넉넉한 데이터 타입 (VARCHAR(255), BIGINT 등)
    • ENUM 대신 코드 테이블(lookup table)
    • NOT NULL DEFAULT를 초기에 잘 정의
    • 공통 메타데이터 컬럼(created_at, updated_at, deleted_at) 포함
  • 확장성 고려
    • 다형성 구조(GenericEvents + type 컬럼) → 새 타입 추가 시 DDL 불필요
    • JSON / EAV 모델 활용 (단, 남용 주의)
  • 관계 설계
    • FK는 무결성을 보장하지만 운영 변경 부담 큼 → 변경 가능성이 높은 관계는 FK 생략 후 인덱스로 보장
  • 버전 관리/호환성
    • 컬럼 DROP 대신 deprecated 처리 후 점진적 제거
    • ENUM 대신 코드 값(TINYINT 등) 사용 → 새로운 상태 추가 시 DDL 불필요
    • 카테고리/코드값은 별도 테이블로 관리

📌 요약

  • 작은 변경 → Online DDL 기능 활용
  • 큰 변경 → 점진적 migration (dual write + backfill)
  • 더 큰 변경 → gh-ost / pt-online-schema-change / pg_repack 같은 도구 활용
  • 운영 → 트래픽 분산, backward compatibility, feature flag, 리허설 필수

좋은 테이블 구조 (DB 설계) 체크리스트

1. 성능

  • 자주 사용하는 검색 조건 / JOIN / 정렬 기준을 고려해 인덱스 설계했는가?
  • 불필요한 인덱스를 제거하여 쓰기 성능 저하를 막고 있는가?
  • 주요 쿼리 패턴(조회 위주 / 쓰기 위주 / 혼합형)을 고려했는가?
  • 필요하다면 성능을 위해 반정규화를 적용하고, 원본 데이터의 진실 소스를 명확히 했는가?
  • 락 전략을 고려했는가?
    • 비관적 락(pessimistic) vs 낙관적 락(optimistic, 버전 컬럼)
  • 데이터량 증가 시 확장 전략을 고려했는가?
    • 파티셔닝 / 샤딩 / 아카이빙

2. 무결성

  • 기본 제약조건을 적절히 활용했는가?
    • PRIMARY KEY / UNIQUE / NOT NULL / CHECK / FOREIGN KEY
  • 정규화를 통해 데이터 중복을 줄이고 무결성을 보장했는가?
  • 필요한 경우 반정규화를 하되, 갱신/삭제 이상(anomaly)을 최소화했는가?
  • 데이터의 업데이트/삭제 이상을 예방할 수 있는 구조인가?

3. 운영 안정성

  • PK 전략이 적절한가?
    • 인조키(surrogate key: AUTO_INCREMENT, UUID) → 운영 안정성 ↑
    • 자연키(natural key: 주민번호, 이메일 등) → UNIQUE 제약으로만 관리
  • DDL 변경 시 운영 영향도를 최소화할 수 있는가?
    • Online DDL 지원 여부 확인
    • 대규모 변경 시 단계적 rollout
  • 스키마 변경 시 backward compatibility를 고려했는가?
    • 컬럼 추가: 안전
    • 컬럼 삭제/타입 변경: 위험 → 단계적 적용
  • 로그성/이력성 데이터는 별도 테이블/DB로 분리했는가?
  • 아카이빙/삭제 등 데이터 라이프사이클 관리를 설계 단계에서 반영했는가?
  • 기본 감사(Audit) 컬럼을 포함했는가?
    • created_at, updated_at, deleted_at
  • 보안과 운영을 위해 DB 계정의 최소 권한 원칙을 적용했는가?

자주 마주하는 상황

신규 테이블 vs 기존 테이블에 컬럼 추가

기존 테이블에 컬럼 추가

  • 추가되는 컬럼의 데이터가 주요 조회 조건으로 사용되는지
    • 즉, 데이터의 cardinality가 높은지 => 인덱스 효율
    • 효율이 좋지 않다면, 다른 컬럼과 조합해서 복합 인덱스로 풀 수 있는지

PK : 인조키 vs 자연키

[개발요청-비즈플러스팀] 상품권 유통사 판매에 따른 개발 · 설계 & 모델링

판매처에 제공할 페이코 주문 식별값 관리

  • 판매처에서 주문시, 판매처에서는 페이코에서 관리하는 주문 식별값이 필요
  • 하지만, 유통사 판매 주문번호는 하나의 값으로 관리될 것이므로 기존의 order 테이블의 order_no를 사용하게 되면, 주문마다 매번 같은 값이 리턴됨
  • 따라서, 판매처에 유일한 식별값 전달하기 위해 판매처 주문관리번호와 같은 새로운 값이 필요할 것으로 생각

[external_seller_order - 외부 판매처 주문]

seller_order_managing_noseller_idseller_order_nopayco_order_noproduct_codeorder_qty
판매처 주문 관리번호(?)판매처id판매처 주문번호페이코 주문번호상품코드주문수량

판매처 발행 상품권 관리

  • 판매처 주문번호로 어떤 상품권(핀번호)이 발급됐는지 관리
  1. gift_user 테이블에 판매처 주문번호 컬럼 추가
  2. 신규 테이블 생성

[external_seller_giftcard_publication - 외부 판매처 상품권 발행]

gift_publication_noseller_order_noseller_id
상품권 발행번호판매처 주문번호판매처id

판매처 상품코드, 유효기간 관리

  • 하나의 주문번호에 권종별로 유효기간이 다르게 관리될 수 있다.
  • 또한, 판매처에 제공하는 상품 식별값은 내부에서 관리하는 gift_id가 아닌 상품코드와 같은 값으로 전달하는 게 안전할 것으로 생각
  1. order_gift 테이블에 유효기간(valid_month), 상품코드(product_code) 컬럼 추가
  2. 판매처별 상품코드 및 유효기간 관리를 위한 테이블 생성
product_codeseller_idgift_idvalid_month
상품코드판매처id상품id유효기간

[external_seller - 외부 판매처]

seller_idseller_name제휴여부
판매처ID판매처명현재도 제휴되어 있는지

상품권(핀번호) 등록 내역 전달여부 관리

  • 판매처에서 정상 사용하려면 등록(사용한) 핀번호, 사용일시 등을 전달해야 한다.
  1. gift_user 테이블에 판매처 전달 여부 컬럼 추가
  2. 전달 여부 관리 전용 테이블 신규 생성

모델링 관련 의견

1. 판매처 상품코드, 유효기간 관리

  • product_code와 gift_id를 동일한 레벨로 볼 수 있나?
  • 그럴 수 없다, 같은 gift_id라도 어떤 seller_id인지에 따라 product_code가 다를 수 있다.
  • 따라서, order_gift에 product_code 등을 관리하기 위해서는 product_code와 gift_id와의 명확한 정의가 필요하다.

2. 상품권(핀번호) 등록 내역 전달여부 관리

  • 굳이 분리할 필요 없는 속성이므로 gift_user에서 관리하는 게 좋을 것으로 생각

3. 판매처에 제공할 페이코 주문 식별값 관리

  • 비즈니스 흐름상 해당 테이블이 필요할 것으로 생각

4. 판매처 발행 상품권 관리

  • gift_user 테이블을 활용하는 게 좋을 것으로 생각
  • 판매처 주문번호만으로는 유일한 주문을 식별할 수 없을 수도 있기 때문에(다른 판매처인데 판매처 주문번호 겹치는 경우)
  • 판매처 주문 관리번호 컬럼을 활용하는 게 좋을 것으로 생각

[개발요청-비즈플러스팀] 상품권 유통사 판매에 따른 개발 · 설계 & 모델링

모델링

테이블 설계

1. 반기 오픈마켓 신규 하위 사업자 차액 정산

데이터 특징

  • 일정산 데이터가 훨씬 많을 것 (반기정산은 1년에 2번)
  • (25.8.1 ~ 25.9.2 기준) 차액정산 건별 내역이 별도로 약 4~5천건 정도 생성됨

기존 테이블에 ‘정산 주기’ 컬럼 추가 vs 신규 테이블 생성

‘정산 주기’ 컬럼 추가

  • 장점
    • 테이블 및 배치 job 개수 늘어나지 않음
    • 다른 주기의 차액정산이 생겼을 때 별도의 테이블 필요 없음
  • 단점
    • ‘정산 주기’ 컬럼은 인덱스 효율 낮음
    • 하지만, 일자나 기간이 거의 조회 조건에 포함되어 있는 일일 데이터가 4~5천건 정도이므로 크게 상관은 없을 것 같음

신규 테이블

  • 장점
    • 조회 시, 정산 주기를 구분하지 않아도 됨
    • 각 정산주기 별로 다른 속성이 생긴다면 각각 컬럼 추가로 쉽게 확장 가능
  • 단점
    • 유지보수 포인트가 늘어남: 테이블 많아짐, 배치 job 추가됨
    • 정산 주기를 제외하면 기존 포인트카드 차액정산과 데이터가 모두 같기 때문에, 테이블 중복 생성 느낌
    • 또 다른 주기의 포인트카드 차액정산이 필요하면 또 다른 테이블 추가 필요
    • 포인트카드 차액정산 데이터 한 번에 보려면 UNION이나 여러 번 질의 필요
This post is licensed under CC BY 4.0 by the author.