2025년 7월 중순부터 운영 솔루션에서 MySQL 쿼리 지연이 간헐적으로 보고되기 시작했습니다. 동일한 쿼리를 개발자가 직접 실행했을 때는 응답이 정상이었지만, 실제 운영 환경에서는 특정 시간대에 응답이 지연되는 현상이 반복되었습니다.
결론부터 말씀드리면 가장 큰 원인은 무분별한 API 조회 요청과 그로 인해 누적된 DB 부하였습니다. 스케일업으로 표면적인 증상은 즉시 해소되었지만, 진짜 해결은 애플리케이션 단의 불필요한 조회를 정리한 시점에 이루어졌습니다. 비용 2배가 증가한 스케일업은 사실상 근본 원인을 가린 상태에서 인프라로 덮은 임시 조치였던 셈입니다.
당시 팀은 매우 한정된 인원으로 신규 프로젝트에 동시 투입된 상황이었고, 운영 이슈와 신규 개발을 병행해야 하는 부담이 큰 시기였습니다. 결과적으로 9월 말에 이르러서야 모든 장애가 최종적으로 해소되었습니다.
이 글은 10년차 Java Spring Boot 개발자 관점에서 약 두 달 반에 걸친 트러블슈팅 과정을 시간순으로 정리한 회고록입니다. 비슷한 클라우드 DB 환경에서 간헐적 성능 저하를 겪고 있다면 의사결정 순서와 진짜 원인을 찾는 관점에서 참고하시기 바랍니다.
장애 발생 초기 상황과 진단의 어려움
이번 장애는 전형적인 “재현이 어려운 운영 이슈”였습니다. 개발자가 직접 동일 쿼리를 실행하면 빠르게 응답이 돌아오지만, 운영 환경에서는 같은 시점에 사용자 화면에서 지연이 보고되는 패턴이 반복되었습니다.
이런 경우 일반적으로 떠올리는 가설은 세 가지입니다.
- 쿼리 자체의 비효율: 인덱스 누락, 풀스캔, 비효율적인 조인 순서
- 인프라 자원 한계: CPU, 메모리, 네트워크, 디스크 I/O 병목
- 동시성 이슈: 락 경합, 트랜잭션 대기, 커넥션 풀 부족
문제는 세 가지 가설이 모두 부분적으로 맞았다는 점이었고, 한 번에 한 가지만 검증하다 보니 진단에 오랜 시간이 걸렸습니다. 신규 프로젝트와 병행하는 상황에서 깊이 있는 분석에 시간을 충분히 할애하지 못한 점도 원인이었습니다.
1차 대응: 지연 쿼리 개선 시도
가장 합리적인 첫 번째 가설은 “느린 쿼리가 있다”였습니다. APM(애플리케이션 성능 모니터링) 도구와 슬로우 쿼리 로그를 확인한 결과, 특정 시간대에 동일 쿼리가 지연되는 케이스가 잡혔습니다.
해당 쿼리를 분석한 후 인덱스 추가, 조인 순서 변경, 불필요한 컬럼 제거 등의 튜닝을 적용했습니다. 적용 직후에는 효과가 있는 듯 보였지만, 며칠이 지나자 다른 쿼리에서 동일한 지연 현상이 다시 발생했습니다.
쿼리를 한 건씩 개선해도 본질적인 원인이 다른 곳에 있다면 문제는 계속 다른 쿼리로 옮겨 다닙니다. 이 시점에서 단순 쿼리 튜닝만으로는 해결되지 않는 구조적 문제를 의심하기 시작했습니다.
결정적 발견: 클라우드 DB 맹신의 함정
쿼리 개선이 임시방편에 그치자 인프라 측면을 본격적으로 점검했습니다. 그런데 DB와 애플리케이션 서버 모두 클라우드를 사용 중이라는 점이 오히려 진단을 늦췄습니다.
당시 팀의 무의식적인 전제는 “관리형 클라우드 DB는 알아서 안정적으로 동작한다”였습니다. 그래서 모니터링은 주로 애플리케이션 서버의 CPU와 메모리에 집중되었고, 클라우드 DB 인스턴스의 자원 사용률 모니터링은 후순위로 밀려 있었습니다.
서버 측 지표에서 특이점이 발견되지 않자 뒤늦게 DB 인스턴스 모니터링 콘솔을 들여다봤고, 그제서야 DB CPU 사용률이 특정 시간대에 한계치를 치고 있다는 사실을 확인할 수 있었습니다.
교훈: 관리형 클라우드 DB라도 인스턴스 사양의 한계는 그대로 존재합니다. 애플리케이션 서버 모니터링만큼 DB 인스턴스 모니터링을 일상적으로 확인해야 합니다.
모니터링 지표로 확인한 정상 상태와 장애 상태 비교
실제 장애 시점의 DB 인스턴스 지표를 평소 정상 상태와 비교하면 차이가 명확하게 드러납니다. 두 시점의 CPU 사용률과 메모리 사용률을 나란히 두고 보면 원인 진단이 훨씬 빨라집니다.
정상 운영 상태의 DB 지표
평상시 DB 인스턴스의 CPU 사용률은 대체로 6~12% 구간에서 변동하며 일시적으로 18~21%까지 튀는 정도였습니다. 메모리 사용률은 45.1~45.2%로 매우 안정적으로 유지되었습니다. 이 정도 지표라면 인스턴스 자원에 여유가 충분한 상태로 판단할 수 있습니다.
| 항목 | 정상 시 수치 | 비고 |
|---|---|---|
| CPU 사용률 | 6~21% (평균 9~12%) | 일시적 스파이크만 존재 |
| 메모리 사용률 | 45.1~45.2% | 사실상 정적 |
| wait i/o | 1% 미만 | 디스크 병목 없음 |
장애 발생 시점의 DB 지표
문제가 발생한 시점의 그래프는 정상 상태와 완전히 다른 양상을 보였습니다. CPU 사용률이 약 09:15부터 지속적으로 100% 부근에 고정되었고, 동일 시간대에 메모리 사용률도 83.4%에서 84.6%까지 점진적으로 상승하는 패턴이 관찰되었습니다.
| 항목 | 장애 시 수치 | 정상 대비 |
|---|---|---|
| CPU 사용률 | 약 100% 지속 | 약 5~10배 증가 |
| 메모리 사용률 | 83.4% → 84.6% | 38%p 이상 상승 |
| 지속 시간 | 약 1시간 이상 | 일시적 스파이크 아님 |
특히 주목할 부분은 CPU가 한계치까지 올라간 동시에 메모리 사용률이 빠르게 늘어났다는 점입니다. 이 패턴은 InnoDB Buffer Pool이 데이터를 충분히 적재하지 못해 디스크에서 자주 데이터를 읽어오면서 CPU 부하가 함께 증가하는 전형적인 시나리오에 부합합니다. 결국 표면적으로 보이는 CPU 100% 현상의 이면에는 메모리 자원 부족이라는 근본 원인이 있었을 가능성이 높다고 판단할 수 있었습니다.
분석 포인트: 단일 지표만 보지 말고 CPU·메모리·I/O를 함께 시계열로 비교해야 합니다. 정상 기준선(baseline)이 있어야 이상치를 빠르게 식별할 수 있습니다.
DB 스케일업과 비용 2배 증가
원인이 DB 인스턴스의 자원 한계임을 확인한 후, 즉시 스케일업 일정을 잡았습니다. 클라우드 사업자가 제공한 선택지는 다음과 같았습니다.
| 항목 | 기존 사양 | 변경 후 사양 | 비고 |
|---|---|---|---|
| CPU | 4코어 | 8코어 | 2배 증설 |
| 메모리 | 16GB | 32GB | 2배 증설 |
| 월 이용 요금 | 기준 | 약 2배 | CPU/메모리 동시 상향 |
스케일업 적용 직후 모든 간헐적 지연 현상이 해소되었습니다. 운영 부담은 크게 줄었지만, 클라우드 DB 이용 요금이 즉시 2배로 증가했다는 점이 부담이었습니다.
MySQL은 단일 쿼리 처리 시 멀티 코어 활용도가 제한적이라는 특성이 있다고 알려져 있습니다. 결국 기존 장애의 본질은 CPU 코어 수의 부족이 아니라 메모리 부족으로 인한 캐시 미스와 그에 따른 CPU 부하 가능성이 높다고 판단했습니다. 즉 InnoDB Buffer Pool이 충분한 데이터를 적재하지 못해 디스크 I/O가 증가했고, 그 결과 단일 코어의 CPU 사용률이 한계치까지 올라간 시나리오로 해석됩니다.
진짜 원인 제거: 무분별한 API 조회 정리와 캐싱 적용
스케일업으로 급한 불을 끈 뒤 애플리케이션 단을 점검하면서 이 장애의 진짜 원인이 인프라가 아니라 무분별한 API 조회 요청에 있었다는 사실이 드러났습니다. 화면이 호출될 때마다 사용하지도 않는 데이터까지 DB에서 끌어오고, 코드성 데이터를 매 요청마다 조인으로 가져오는 패턴이 누적되면서 DB 부하가 임계치를 넘긴 것이었습니다.
정리한 핵심 작업은 다음과 같습니다.
- 단발성 의미 없는 조회 제거: 화면 진입 시 사용하지 않는 데이터를 미리 조회하는 코드를 정리
- 코드성 데이터 브라우저 캐싱: 공통 코드, 카테고리, 코드명처럼 변경 빈도가 매우 낮은 데이터는 클라이언트 측 캐시로 이동
- 조인 제거 후 매핑 방식 전환: 코드성 데이터와의 DB 조인을 제거하고, 결과 데이터에 캐싱된 코드명을 매핑하여 화면에 표출
특히 마지막 항목의 효과가 가장 컸습니다. 코드성 데이터는 변경이 거의 없기 때문에 매 요청마다 DB 조인으로 가져올 이유가 없습니다. 화면 진입 시 한 번만 로딩한 캐시를 활용해 매핑하면 DB 부하가 의미 있게 감소합니다.
이 작업은 Spring Boot 환경에서 비교적 적은 변경 범위로 적용 가능했으며, 컨트롤러 레이어의 평균 응답 시간이 단축되는 효과를 확인할 수 있었습니다. 캐시 무효화 전략은 코드성 데이터의 갱신 주기에 맞춰 일정한 TTL을 두는 방식으로 설계했습니다.
결정적 교훈: 스케일업은 증상을 가렸을 뿐 원인을 해결하지 못했습니다. 이 단계의 API 조회 정리 작업이 사실상 장애의 근본 원인 제거(root cause fix) 였습니다. 만약 API 조회 정리를 먼저 진행했다면, 비용 2배 증가 없이 장애를 해소할 수도 있었던 사례입니다.
보류된 과제: 메모리 중심 사양으로의 마이그레이션
스케일업으로 장애는 해결했지만 비용 효율 측면에서는 아직 개선 여지가 남아 있습니다. 앞서 학습한 내용대로 MySQL이 메모리 자원에 크게 의존한다면, 이상적인 사양은 CPU 4코어 + 메모리 32GB 조합입니다.
문제는 클라우드 사업자가 제공하는 표준 인스턴스 옵션 중 CPU를 줄이고 메모리만 유지하는 조합이 없다는 점이었습니다. 결국 선택지는 두 가지였습니다.
| 선택지 | 장점 | 단점 |
|---|---|---|
| 현재 사양 유지 (CPU 8 / 메모리 32GB) | 변경 리스크 없음 | 비용 비효율 가능성 |
| 신규 인스턴스 생성 후 마이그레이션 | 비용 최적화 가능 | 데이터 이관 리스크, 다운타임 발생 |
운영 안정성을 우선시한 결과, 마이그레이션은 일단 보류하고 현재 상태를 유지하기로 결정했습니다. 이는 비용보다 가용성을 우선시한 의사결정이었으며, 향후 트래픽 패턴과 사용량 추이를 추가 분석한 후 재검토할 예정입니다.
회고와 핵심 결론
이번 장애를 끝까지 추적해 본 결과 도달한 결론은 명확합니다.
가장 큰 원인은 무분별한 API 조회 요청과 그로 인한 DB 부하였습니다.
쿼리 지연도, CPU 100%도, 메모리 사용률 상승도 모두 결과적 증상이었습니다. 진짜 원인은 화면 단위로 불필요한 API 호출이 누적되면서 DB가 처리해야 할 요청량 자체가 비정상적으로 많았다는 점이었습니다. 이런 상황에서는 어떤 쿼리를 개선해도 다른 쿼리에서 지연이 발생할 수밖에 없었고, 결국 자원 한계까지 도달해 인스턴스 스케일업이 강제되었습니다.
이를 바탕으로 정리한 핵심 학습은 다음과 같습니다.
- 증상이 아니라 호출 빈도를 의심하라: 느린 쿼리 한두 건보다 “왜 이렇게 많이 호출되는가”가 더 중요한 질문입니다
- 간헐적 지연도 결국 인프라 자원 한계가 의심된다: 쿼리만 보지 말고 인스턴스 지표를 함께 봐야 합니다
- 관리형 DB도 인스턴스의 한계는 존재한다: 클라우드 DB의 자동화가 모니터링 의무를 면제해 주지는 않습니다
- 불필요한 API 호출 제거가 가장 비용 효율적인 해결책이다: 인프라 증설은 마지막 카드여야 합니다
- MySQL은 메모리 의존도가 높은 워크로드에 가깝다: 동일 비용이라면 CPU 코어 증설보다 메모리 확보가 우선순위가 높을 수 있습니다
자주 묻는 질문 (FAQ)
Q1. 동일 쿼리가 개발 환경에서는 빠른데 운영에서만 느린 이유는 무엇인가요?
운영 환경의 DB 인스턴스 자원 부하, 동시 접속자에 따른 락 경합, 캐시 미스 비율 등이 영향을 미칩니다. 단일 쿼리 성능보다 운영 시점의 인스턴스 지표를 함께 확인해야 정확한 원인을 파악할 수 있습니다.
Q2. MySQL은 CPU와 메모리 중 어느 쪽이 더 중요한가요?
일반적인 OLTP 워크로드에서는 메모리(특히 InnoDB Buffer Pool)의 영향이 큽니다. 데이터셋이 메모리에 적재될수록 디스크 I/O가 줄어들고 CPU 부하도 함께 감소합니다. 다만 특정 워크로드에서는 CPU가 더 중요한 경우도 있으므로 모니터링 지표 기반의 판단이 필요합니다.
Q3. 클라우드 DB 스케일업 외에 비용 효율적인 대안은 없나요?
애플리케이션 단의 불필요한 DB 호출 제거, 변경이 적은 데이터의 클라이언트 캐싱, 쿼리 인덱스 최적화가 가장 비용 효율적인 대안입니다. 인프라 증설은 마지막 수단으로 검토하시기 바랍니다.
Q4. 마이그레이션 없이 인스턴스 사양을 다운사이징할 수 있나요?
대부분의 클라우드 사업자는 동일 인스턴스에서 CPU와 메모리 비율을 임의로 조정하는 옵션을 제공하지 않습니다. 사양 변경이 필요하다면 신규 인스턴스 생성 후 마이그레이션이 일반적인 절차이며, 다운타임과 데이터 이관 리스크를 함께 고려해야 합니다.
Q5. 간헐적 지연을 빠르게 진단하려면 어떤 도구가 필요한가요?
APM(애플리케이션 성능 모니터링), DB 슬로우 쿼리 로그, DB 인스턴스 메트릭 대시보드 세 가지를 기본으로 갖춰야 합니다. 가능하면 임계치 기반 알람을 설정해 자동으로 이상 패턴을 감지하시기 바랍니다.
마무리
이번 사례의 가장 큰 교훈은 단순합니다. 무분별한 API 조회가 누적되면 어떤 쿼리 튜닝도, 어떤 인프라 증설도 임시방편에 불과합니다. 스케일업으로 즉시 해소된 증상은 진짜 원인을 가렸을 뿐, 호출 빈도를 정리한 시점에서야 비로소 장애가 종결되었습니다.
만약 현재 비슷한 패턴의 간헐적 DB 지연을 겪고 있다면 순서를 다음과 같이 잡아 보시기 바랍니다. 첫째, API 호출 빈도와 의미 없는 조회 패턴을 먼저 점검합니다. 둘째, DB 인스턴스의 CPU와 메모리 사용률을 시간대별로 함께 확인합니다. 셋째, 그 후에 쿼리 개선과 캐싱을 적용하고, 인프라 스케일업은 가장 마지막 카드로 남겨 두는 것이 비용 효율 측면에서 가장 합리적입니다.
장기적으로는 메모리 중심 사양으로의 마이그레이션, 읽기 전용 복제본 활용, 캐시 계층(Redis 등) 도입 등도 검토할 수 있는 카드입니다. 다만 모든 의사결정은 트래픽 패턴과 데이터 특성에 기반한 측정값을 우선해야 합니다.
핵심 결론
- 가장 큰 원인은 무분별한 API 조회 요청과 그로 인한 DB 부하였다
- 쿼리 지연, CPU 100%, 메모리 상승은 모두 결과적 증상에 불과했다
- 스케일업은 증상을 가렸을 뿐, 근본 원인은 API 호출 정리로 해결되었다
- 비용 2배 증가 없이도 해결 가능했던 사례이며, 진단 순서가 중요했다
- 관리형 클라우드 DB도 모니터링 의무는 동일하게 존재한다
- MySQL은 메모리 의존도가 높은 워크로드 특성을 보인다
“MySQL 쿼리 지연 해결: 3개월 클라우드 DB 분투기”에 대한 5개의 생각