CASE STUDY
한눈에 보는 변화
이 개선은 느린 SQL에 인덱스를 하나 추가한 사례가 아닙니다. 사용자가 조회 범위를 좁혀도 시간이 거의 줄지 않는 현상에서 고정 비용을 의심하고, 주문 조건이 적용되기 전에 하위 결과 전체를 집계하는 실행 순서를 찾아 실제 조회 대상만 계산하도록 바꾼 사례입니다.
주문조회 화면이 검색 기간과 조건에 관계없이 수십 초씩 지연됐습니다. 하위 결과 테이블 전체를 GROUP BY한 뒤 주문과 결합하는 구조라 데이터가 늘수록 비용이 커졌습니다.
직접 담당한 범위
- 병목 Query와 실행 구조 분석
- SQL 재설계, 인덱스 필요성 검토와 결과 동일성 테스트
- 사용 빈도가 높은 검색 조건을 기준으로 운영 반영 후 검증
| 구분 | 변경 전 | 변경 후 | 확인된 변화 |
|---|---|---|---|
| 집계 순서 | 하위 결과 전체를 주문별 GROUP BY한 뒤 화면 조회 주문과 결합 | 먼저 주문을 제한하고 각 주문에 필요한 결과만 계산 | 불필요한 전체 선집계 제거 |
| 튜닝 기준 | 조건을 줄여도 약 40~50초가 유지되는 고정 비용 | 기간·업체·주문 상태 등 실제 사용 조건별 반복 측정 | 핵심 Query 약 0.5초 |
| 결과 계약 | 기존 MyBatis 반환 컬럼과 화면 집계 결과 | SQL 실행 구조만 변경하고 반환 구조와 값은 유지 | 화면·Mapper 변경 없이 운영 반영 |
| 운영 효과 | 검색 조건과 무관하게 수십 초 지연 | 빈도가 높은 조회 패턴에서 1초 이내 처리 | 전체 조회의 95% 이상 1초 이내 |
| 상세 페이지 접근 경로 | 재·추가 반응 조건에서 기존 PK Index를 충분히 활용하지 못함 | 주문 관련 테이블의 (법인 키, 업무 키) 복합 인덱스 후보 검토 | 실행계획 개선 확인·인덱스 미배포 |
EVIDENCE CHAIN
문제를 좁힌 과정
- 01 · 현상 재현
조회 조건을 좁혀도 실행시간이 거의 변하지 않았습니다
기간을 하루 단위로 줄이고 업체·상태 조건을 바꿔도 핵심 Query는 약 40~50초가 걸렸습니다. 반환 건수와 실행시간의 상관이 낮다는 점을 먼저 확인했습니다.
판단화면 렌더링이나 네트워크보다 SQL 내부에서 조건과 무관하게 발생하는 고정 비용을 우선 의심했습니다.
- 02 · 실행 구조 확인
주문 조건보다 하위 결과의 전체 집계가 먼저 수행됐습니다
Query와 실행계획을 대조한 결과 하위 결과 테이블 전체를 주문 단위로 GROUP BY한 뒤 바깥의 주문 검색 조건과 결합하고 있었습니다.
판단사용자가 하루치만 요청해도 하위 집계 입력 범위는 줄지 않아 조건 변화가 응답시간에 반영되지 않았습니다.
- 03 · 대안 비교
인덱스만 추가해서는 집계 입력량이 그대로 남았습니다
조인·필터 조건의 인덱스 필요성은 함께 검토했지만, 전체 선집계 구조를 유지하면 읽고 묶어야 할 데이터 범위 자체는 줄지 않습니다.
판단접근 경로의 미세 조정보다 실제 조회 주문만 계산하도록 실행 순서를 바꾸는 것을 우선했습니다.
- 04 · SQL 재설계
외부 주문을 먼저 제한하고 필요한 결과만 집계했습니다
대용량 선집계를 제거하고 SELECT 절의 상관 서브쿼리에서 현재 주문에 연결된 결과 건수만 계산하도록 재작성했습니다. MyBatis의 반환 컬럼과 화면 계약은 유지했습니다.
판단현업의 일반적인 좁은 조회에서는 전체 결과 집계보다 주문별 선택 집계가 유리하다고 판단했습니다.
- 05 · 결과·성능 검증
빠른 한 번보다 결과 동일성과 운영 패턴을 함께 확인했습니다
기존·개선 Query의 건수와 컬럼 값을 조건별로 비교하고, 기간·업체·주문 상태 등 실제 사용 조건에서 반복 측정한 뒤 사용자 승인으로 운영에 반영했습니다.
판단핵심 Query는 약 0.5초로 단축됐고 운영상 전체 조회의 95% 이상이 1초 이내에 처리됐습니다.
- 06 · 상세 Query 보완
목록과 별개로 상세 페이지의 재·추가 반응 조회 경로를 확인했습니다
상세 페이지 Query에서는 재·추가 반응을 조회하는 조건과 기존 PK Index의 선두 컬럼이 맞지 않아 접근 경로가 비효율적인 구간을 분리했습니다. 조회 조건에 맞춘 주문 관련 테이블의 (법인 키, 업무 키) 복합 인덱스 후보에서 실행계획 개선을 확인했지만, 해당 인덱스는 운영에 배포하지 않았습니다.
판단목록의 전체 선집계 제거·운영 반영과 상세의 복합 인덱스 후보 검토·미배포는 서로 다른 범위로 분리해 관리했습니다.
DECISION LOG
검토한 대안과 선택 근거
기존 선집계 유지 + 인덱스 추가
인덱스가 접근 비용을 줄일 수 있어도 조회 조건과 무관한 전체 GROUP BY 입력량은 그대로 남습니다.
전체 선집계 결과를 계속 재사용
넓은 범위의 일괄 조회에는 유리할 수 있지만, 실제 화면의 빈번한 좁은 검색 패턴과 맞지 않았습니다.
주문 제한 후 상관 서브쿼리 집계
외부 주문 건수가 먼저 줄어드는 운영 패턴에서 필요한 하위 데이터만 계산할 수 있었습니다.
화면·Mapper 반환 구조까지 변경
병목은 SQL 실행 순서였고 기존 반환 컬럼과 화면 계약을 바꿀 이유가 없었습니다.
IMPLEMENTATION
설계와 구현
SQL 실행 순서
- 하위 결과 테이블 전체를 먼저 GROUP BY하던 Inline View 제거
- 바깥 주문 조건을 먼저 적용하고 현재 주문에 필요한 결과만 집계
- 상관 조건과 조인·필터 조건을 기준으로 접근 경로와 인덱스 필요성 검토
애플리케이션 계약
- MyBatis Mapper의 기존 반환 컬럼명과 타입 유지
- 화면에서 사용하는 주문별 결과 건수와 조회 항목 유지
- SQL 변경 범위를 병목 구간으로 제한해 회귀 영향 축소
상세 페이지 Query
- 재·추가 반응 조건에서 기존 PK Index를 활용하지 못하는 접근 경로 분리
- 주문 관련 테이블의 (법인 키, 업무 키) 복합 인덱스 후보 도출
- 목록 Query만 운영 반영하고 상세 인덱스는 실행계획 검토 후 미배포
측정 기준
- 기간·업체·주문 상태 등 현업이 반복 사용하는 조건 선정
- 조건별 기존·개선 Query를 반복 실행해 단발성 편차 분리
- 최악 조건만이 아니라 전체 운영 조회 중 1초 이내 비율 확인
적용 조건
- 상관 서브쿼리는 외부 주문 건수가 먼저 충분히 줄어드는 경우에 사용
- 넓은 범위 조회나 상관 조건 접근 비용이 큰 경우 선집계 대안과 다시 비교
- 튜닝 패턴을 정답으로 고정하지 않고 실제 카디널리티와 조회 패턴으로 결정
VERIFICATION
테스트 매트릭스
| 검증 영역 | 검증 방법 | 완료 기준 |
|---|---|---|
| 결과 건수 | 기존·개선 Query의 주문별 결과 건수 비교 | 모든 검증 조건에서 집계값 동일 |
| 반환 컬럼 | MyBatis Mapping과 화면 표시값을 전후 대조 | 기존 화면 계약과 데이터 타입 유지 |
| 조회 조건 | 기간·업체·주문 상태 조합별 반복 실행 | 자주 쓰는 조건에서 응답시간이 안정적으로 단축 |
| 핵심 성능 | 개선 전 약 40~50초와 개선 Query 반복 측정 | 핵심 Query 약 0.5초 |
| 운영 분포 | 운영 반영 후 실제 조회 응답시간 확인 | 전체 조회의 95% 이상 1초 이내 |
| 화면 회귀 | 목록 테스트표 2행(Y 2행)과 상세 11행(Y 7행·N 4행)을 구분 | 행 수를 고유 시나리오·전건 성공으로 집계하지 않고 결함 보완 후 완료 확인 |
OPERATION
운영 반영과 후속 안정화
- 핵심 Query 응답시간을 약 40~50초에서 약 0.5초로 단축
- 운영상 전체 조회의 95% 이상을 1초 이내로 개선
- 상세 페이지 복합 인덱스 후보에서 실행계획 개선을 확인했으나 인덱스는 미배포
TAKEAWAYS
이 사례에서 드러난 역량
- 조회 조건을 줄여도 시간이 같다면 조건 적용 전에 발생하는 고정 비용을 먼저 찾아야 합니다.
- 인덱스 튜닝 전에 어떤 데이터를 몇 건 읽고 집계하는지 실행 순서를 확인해야 합니다.
- 상관 서브쿼리와 선집계 중 어느 쪽이 빠른지는 카디널리티와 실제 조회 패턴으로 결정해야 합니다.
- 성능 개선은 응답시간뿐 아니라 결과 건수·컬럼·화면 계약이 같다는 검증까지 포함해야 합니다.