Incident Library

IL-0441 ZOZO · 2021-03-04 (게시일)database

세일 이벤트 때 SQL Server 의 초당 배치 실행 수가 임계값을 넘으면 쿼리가 밀려 애플리케이션 타임아웃 오류가 쏟아졌다

ZOZO 는 서비스 일부에 SQL Server 를 쓴다. 2020년 중반부터 한 기능을 맡은 DB 에서 초당 배치 실행 수가 일정 임계값을 넘으면 쿼리가 밀리고 워커 스레드가 바닥나 애플리케이션에 타임아웃 오류가 쏟아졌다. 팀은 성능 모니터의 SQL Server 지표, 확장 이벤트 blocked_process_report, 1분마다 테이블에 저장해 둔 DMV 데이터를 가지고 사내 조사 흐름도를 따라 조사했다. 오류가 나기 시작할 때 Page Latch, Non-Page Latch, 블로킹 순으로 변화가 나타났지만 근본 원인은 끝내 찾지 못했다.

팀은 장애 중에 관찰된 현상 가운데 없애거나 줄일 수 있는 것을 고르는 방식으로 대책을 정했다. 다른 날 같은 장애가 났을 때 blocked_process_report 의 waitresource 별 이벤트 수를 뽑아 보니, 특정 스토어드 프로시저의 컴파일 대기가 블로킹을 일으키고 있었다. 팀은 이 스토어드 프로시저 네 개의 실행 계획을 플랜 가이드로 고정했다. sql_statement_recompile 이벤트의 recompile_cause 를 보니 재컴파일 대부분이 Statistics changed 였다. 그래서 장애 시간대에 통계가 업데이트된 테이블들의 통계 자동 업데이트를 트래픽이 늘 시간대에만 끄고, 트래픽이 가라앉으면 다시 켰다. 플랜 캐시 축출은 SQL Server 가 메모리를 관리하므로 직접 제어할 수 없었다. 그래서 팀은 메모리에 올라간 큰 인덱스를 압축했다.

대책을 적용한 뒤로는 같은 수준의 배치 실행 수에서도 쿼리가 밀리지 않았다. 커넥션 증가가 눈에 띄게 줄었고 오류가 사라졌다. CPU 사용률 피크는 100% 가까이에서 60% 정도로 내려갔다. 컴파일 수와 재컴파일 수가 크게 줄었고, 배치 실행 수가 가장 많을 때도 락 경합이 보이지 않았다.

심각도
–
영향 범위
Applications using one SQL Server database got many timeout errors during traffic peaks
영향 사용자
–
지역
–
감지까지
–
복구까지
–

증상

2020년 중반부터 세일 이벤트처럼 트래픽이 몰릴 때, 한 기능을 맡은 SQL Server DB 의 초당 배치 실행 수가 일정 임계값을 넘으면 애플리케이션에서 타임아웃 오류가 많이 났다. 여기서 배치는 SQL Server 에서 하나 이상의 쿼리를 묶은 단위다.

타임아웃 오류가 쏟아지기 시작한 순간 초당 배치 실행 수가 급격히 떨어지고 배치 총 실행 시간이 급격히 늘었다. 쿼리 하나의 실행 시간이 크게 길어졌다는 뜻이었다.

성능 모니터에서 CPU 사용률은 피크 때 100% 가까이 올랐다. Batch Resp Statistics 를 보면 CPU 사용 시간이 20msec 에서 100msec 사이인 배치가 CPU 사용률을 올린 주된 원인이었다. 블로킹과 SQL Server 쪽 오류도 보였다. 장애 초기에는 비페이지 래치 대기가 튀었다. 배치 실행 수와 함께 컴파일 수가 늘었고, 작업 영역 메모리를 할당받은 쿼리 수도 늘었다. 워커 스레드를 기다리는 일이 많아 워커 스레드가 바닥났을 가능성이 컸다.

팀은 쿼리가 밀린 이유로 세 가지를 의심했다. CPU 사용률이 치솟아 생긴 SOS_SCHEDULER_YIELD 대기, 비페이지 래치 경합, 접근이 몰리는 곳에 락이 오래 걸려 생긴 블로킹이었다. 오류가 나기 시작한 지점을 짧은 시간 단위로 다시 보니 Attention 이벤트가 난 때 CPU 사용률은 오히려 내려가 있었다. 그래서 CPU 는 원인으로 보기 어려웠다. 오류 앞뒤로 Page Latch 가 먼저 나타났고, 이어서 Non-Page Latch, 그다음 블로킹이 나타났다.

확장 이벤트 blocked_process_report 에는 장애 시작 15초쯤 뒤에 첫 블로킹 이벤트가 찍혔다. 첫 블로커는 background 프로세스였고, 이 프로세스도 ACCESS_METHODS_HOBT_VIRTUAL_ROOT 래치를 기다리고 있었다. 확장 이벤트는 정해진 시간 넘게 이어진 블로킹만 기록하므로 성능 모니터보다 늦게 찍혔다. 첫 블로커가 무슨 일을 하고 있었는지는 알 수 없었다.

DMV 로 파고들어도 CPU 부하 면에서 튜닝할 쿼리는 나오지 않았다. 래치 총 대기 시간은 ACCESS_METHODS_ACCESSOR_CACHE, BUFFER, ACCESS_METHODS_HOBT_VIRTUAL_ROOT 순으로 컸다. 대기 유형은 이차적인 THREADPOOL 을 빼면 비페이지 래치, 페이지 래치, 락이 대부분이었다. 팀은 끝내 근본 원인을 찾지 못했다.

증상 간헐적 타임아웃데이터베이스 과부하잠금 경합과 교착

원인

원인 unknown

구성 요소 데이터베이스
기여 요인 –
촉발
세일 이벤트나 럭키백 판매 이벤트로 트래픽이 늘어 초당 배치 실행 수가 일정 임계값을 넘었다.

감지

–

조치

후속 조치

–

교훈

근본 원인을 찾지 못한 장애에서도 장애 중에 관찰된 현상 가운데 없애거나 줄일 수 있는 것을 골라 대책을 세우면 장애가 나지 않는 상태를 만들 수 있었다. 조사는 특이하게 변한 지표를 먼저 보고 가설을 세운 뒤 DMV 와 확장 이벤트로 파고들고, 장애 앞뒤 몇 초의 변화를 자세히 보는 순서로 했다.

출처

관련 사례

내 클러스터에도 있는지 확인하기

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 데이터베이스. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.