본문 바로가기
데이터베이스

튜닝하다 조건을 떨어뜨렸다 — 쿼리를 고치면 결과부터 대조한다

by 꼼냥냥 2026. 9. 16.
728x90

쿼리 튜닝을 하다가 조건을 떨어뜨렸다. VIP 대상자가 7천 명으로 나왔고, 에러는 하나도 없었다. 석 달 동안 아무도 몰랐다. 성능 튜닝의 진짜 위험은 느려지는 게 아니라 답이 조용히 달라지는 것이다. 빨라진 건 눈에 보이고 답이 달라진 건 안 보인다. 그래서 규칙을 하나 만들었다. 쿼리를 고치면 성능이 아니라 결과부터 대조한다.

점포에서 온 문의

회원 등급 설정에서 신규 VIP 대상이 7천 명으로 나오는데 이게 맞나요?

그 점포 전체 회원이 그 정도 규모다. 거의 전원이 VIP 대상이라는 뜻이다. 누가 봐도 이상한데, 화면은 아무 에러 없이 그 숫자를 보여주고 있었다.

조건이 사라져 있었다

대상자를 세는 쿼리는 이런 모양이다. 회원별 누적 결제금액을 구하고, 설정한 금액 이상인 사람만 센다.

SELECT COUNT(*)
  FROM ( SELECT V.USER_ID
           FROM ( ... 상품 이력 ... ) V
          GROUP BY V.STORE_CODE, V.USER_ID
         HAVING SUM(V.ITEM_PRICE) >= #{sumTotalPay}   -- ← 이게 없었다
       ) T

HAVING 절이 통째로 빠져 있었다. 금액 조건 없이 회원을 전부 센 것이다. 그래서 7천 명이었다.

이력을 뒤졌다. 석 달 전 내가 이 쿼리를 튜닝하면서 지운 것이었다. 느린 집계를 손보다가 서브쿼리 구조를 바꿨고, 그 과정에서 조건이 딸려 나갔다.

왜 석 달 동안 아무도 몰랐나

세 가지가 겹쳤다.

1. 에러가 안 난다. 문법은 완벽하다. 결과도 나온다. 숫자만 틀렸다.

2. 틀린 방향이 "많이"였다. 대상자가 적게 나왔으면 바로 이상하다고 느꼈을 것이다. 많이 나오면 "이 점포가 크구나"로 넘어간다.

3. 아무도 정답을 모른다. 누적 500만 원 이상인 회원이 몇 명인지 감이 있는 사람이 없다. 비교할 기준값이 없으면 틀린 숫자를 검증할 방법이 없다.

성능 튜닝은 "빨라졌다"가 검증 기준이 되기 쉽다. 답이 같은지는 안 본다. 빨라진 건 눈에 보이고, 답이 달라진 건 안 보이기 때문이다.

세는 쿼리와 실행하는 쿼리가 따로 있었다

고치면서 알게 된 게 더 있다. 이 기능은 쿼리가 두 개다.

  • 대상자를 세는 쿼리 — 화면에 "N명"을 보여준다
  • 등급을 바꾸는 쿼리 — 실제로 UPDATE를 친다

둘이 같은 산식을 각자 들고 있다. 세는 쪽만 고치면 화면은 5명인데 실제로는 7천 명이 바뀐다.

실행하는 쪽도 열어 보니 조인 조건이 어긋나 있었다. 점포 코드를 안 보고 회원 아이디만으로 붙고 있어서, 점포가 다른 동명 아이디까지 걸릴 수 있는 상태였다.

-- 고치기 전
ON UI.USER_ID = PRD.USER_ID

-- 고친 뒤
ON UI.STORE_CODE = PRD.STORE_CODE
   AND UI.USER_ID = PRD.USER_ID

미리보기와 실행이 다른 쿼리면 반드시 둘 다 본다. 사용자가 보는 숫자와 실제로 벌어지는 일이 다를 수 있다.

고친 뒤에 한 일

두 쿼리를 맞추고, 점포 × 금액 조건 조합으로 옛 결과와 대조했다. 24개 조합을 돌려 전부 일치하는 걸 확인한 다음 배포했다.

그리고 숫자를 남겼다.

누적 500만원 이상 / 담당자 미배정 → 5명

7천여 명 → 5명. 이 정도로 벌어져 있었다.

그래서 규칙을 하나 만들었다

쿼리를 고치면 성능이 아니라 결과부터 대조한다.

방법은 단순하다. 고치기 전 쿼리를 파일로 떠 두고, 고친 뒤 같은 파라미터로 양쪽을 돌려 결과를 비교한다. 조합을 20~40개쯤 돌리면 웬만한 회귀는 잡힌다.

여기에 두 가지를 덧붙였다.

대조는 반드시 같은 브랜치 기준으로 한다. 한 번은 옛 쿼리를 뜬다면서 다른 브랜치에서 꺼내는 바람에 있지도 않은 불일치 24건을 만들어 놓고 한참 헤맸다. 다시 뽑으니 0건이었다. 비교 대상이 뭔지부터 확인해야 한다.

기준값을 코드 밖에 적어 둔다. 검증 스크립트에 이렇게 남겼다.

-- 아래 기준값과 맞아야 정상이다
--   점포 A / 누적 100만원 / 담당자 미배정 → 4,823명
--   점포 B / 누적 100만원 / 담당자 미배정 → 1,748명
--   점포 C / 누적 100만원 / 담당자 미배정 →   880명

다음에 이 쿼리를 만지는 사람이 무엇과 비교해야 하는지 알 수 있다. 이번 사고의 본질이 "비교할 기준이 없었다"였으니까. 같은 화면을 요약 테이블로 옮길 때도 기준값부터 적어 뒀다.

여담 — 자동 등급 설정을 꺼도 조건은 남는다

같이 확인한 것이다. 자동 등급 설정을 OFF로 두면 배치가 안 돈다. 하지만 저장돼 있던 조건은 지워지지 않는다.

다시 켜는 순간 옛 조건으로 돌기 시작한다. OFF는 "조건 삭제"가 아니라 "실행 중지"다. 이런 건 화면만 봐서는 구분이 안 되므로, 켤 때 조건을 다시 확인하는 게 맞다.

정리

  • 성능 튜닝의 위험은 느려지는 게 아니라 답이 조용히 달라지는 것이다. 에러가 안 난다.
  • 틀린 방향이 "많이"면 더 안 들킨다. 적게 나오면 바로 의심하는데 많이 나오면 넘어간다.
  • 미리보기와 실행이 다른 쿼리면 둘 다 고친다. 화면과 실제가 갈린다.
  • 쿼리를 고치면 성능이 아니라 결과부터 대조한다. 조합 20~40개면 충분하다.
  • 대조는 같은 브랜치 기준으로. 아니면 없는 불일치를 쫓게 된다.
  • 기준값을 적어 남긴다. 검증할 기준이 없는 게 사고의 뿌리였다.

댓글