“쿼리가 느린 게 아니라 모델이 거짓말을 하고 있다.”


1. 느린 쿼리는 결과, 원인은 모델 불일치

쿼리가 느릴 때 현장에서 제일 먼저 나오는 대응은 이겁니다.

  • 인덱스 추가
  • 힌트
  • 서브쿼리 제거
  • 조인 줄이기

근데 이걸로 안 끝나는 케이스는 거의 다 이겁니다.

모델이 현실을 제대로 표현하지 못하고 있다

그래서 DB가 “말을 돌려서” 답을 내느라 느려지는 겁니다.


2. 모델이 거짓말할 때 나타나는 전형적인 징후

아래 중 두 개 이상이면 99% 모델 문제입니다.

1️⃣ 의미가 모호한 컬럼

  • status, type, flag, etc1
  • 값이 1,2,3인데 의미는 문서에만 있음

→ 쿼리가 조건 분기 지옥으로 감


2️⃣ 시간 개념이 섞여 있음

  • 생성일 / 수정일 / 적용일 / 종료일이 한 테이블에 뒤섞임
  • “현재 유효한 것”을 구하기 위해 조건이 5개 이상

→ DB는 현재가 뭔지 계산해야 하는 상태


3️⃣ N:M을 숨긴 1:N

  • 사실 이력 테이블인데 최신 row만 쓰는 구조
  • group by + max + join

→ “최신”이라는 개념을 쿼리로 발명 중


4️⃣ 집계 책임이 DB에 떠넘겨짐

  • 실시간 통계
  • 조건별 합계
  • 상태별 카운트

→ DB가 비즈니스 판단까지 대신함


3. 그래서 쿼리가 ‘느려 보이는’ 이유

DB는 거짓말을 그대로 믿고 실행합니다.

  • “이 row는 하나야” → 실제로는 수백 개
  • “상태 하나면 끝나” → 사실상 상태 머신
  • “이 값은 불변” → 이력 덕지덕지

결과적으로 옵티마이저는

  • 잘못된 카디널리티
  • 틀린 실행 계획
  • 불필요한 풀스캔

을 선택하게 됩니다.

쿼리가 느린 게 아니라, DB가 혼란스러운 상태인 거죠.


4. 해결은 튜닝이 아니라 “진실한 모델링”

이럴 때 정공법은 명확합니다.

✔ 상태를 쪼갠다

  • 현재 상태 / 이력 상태 분리
  • 의미 단위로 컬럼 분해

✔ 시간을 명시한다

  • effective_from, effective_to
  • “현재”를 컬럼으로 만들지 말고 테이블로 만든다

✔ 책임을 옮긴다

  • 집계는 배치/캐시/전용 테이블
  • 조회용 모델 따로 둠 (CQRS 개념)

✔ 쿼리가 아니라 질문을 바꾼다

  • “이걸 한 번에 구할 수 있나?”
  • “이걸 실시간일 필요가 있나?”

5. 한 줄 요약 (리뷰용)

DB가 느리게 답하는 건,
모델이 현실을 정직하게 설명하지 못하고 있기 때문이다.

LIST

+ Recent posts