Incident Library

IL-0317 Clerk · 2026-02-19database

Postgres 쿼리 플랜이 바뀌어 데이터베이스가 느려지고 Clerk 트래픽의 95% 넘게 429 를 받았다

Postgres 에서는 쿼리를 실행하기 전에 쿼리 플래너가 데이터를 찾을 방법을 정하고, 데이터가 늘면 주기적으로 다시 분석해 플랜을 맞춘다. 플래너는 모든 행을 다시 읽지 않으려고 표본으로 데이터 분포를 어림한다. 문제의 쿼리에서 플래너는 한 컬럼의 NULL 비율에 기대고 있었는데, 실제 비율은 99.9996% 였다. 자동 ANALYZE 가 돌 때 표본이 너무 작아 NULL 만 뽑혔다. 플래너는 값이 100% NULL 이라고 판단했고, 쿼리의 그 부분이 NULL 이 아닌 행을 하나도 내지 않는다고 보고 플랜을 세웠다. 실제로는 17,000개가 넘는 행이 나왔고, 자주 실행되는 이 쿼리가 데이터베이스 자원 거의 전부를 차지했다. 요청 핸들러에 요청이 쌓여 트래픽의 95% 넘게 429 로 버려졌다. 장애는 2026-02-19 8:11 AM PST 에 시작됐다.

16:15 UTC 에 사고가 내부에 보고되고 에스컬레이션됐다. 팀은 16:32 UTC 에 트래픽이 치솟은 고객을 원인으로 보고 16:35 UTC 에 그 트래픽을 막았지만 소용없었다. 16:50 UTC 에는 그 급증이 Clerk 인프라 안에서 난 실패를 재시도한 것이었음을 알았다. 16:55 UTC 에는 데이터베이스가 느려졌을 뿐 살아 있었기 때문에 Session API 의 자동 장애 조치가 작동하지 않았다는 것을 알았다. 17:08 UTC 에 팀은 새 장애 조치를 켜서 핵심 Session API 밖에서 세션 토큰을 만들게 했다. 이 장애 조치는 몇 달 동안 만들고 시험했지만 운영에서는 아직 작동 조건을 걸지 않은 상태였다. 17:10 UTC 에 많은 사용자가 고객 앱에 다시 들어갔지만 로그인과 계정 관리 API 는 여전히 쓸 수 없었다.

17:25 UTC 에 자동 ANALYZE 로 바뀐 비효율적인 쿼리 플랜을 근본 원인으로 찾았다. 17:27 UTC 에 해당 테이블에 ANALYZE 를 수동으로 다시 돌리자 쿼리가 이전 플랜으로 돌아가 데이터베이스가 회복하기 시작했다. 17:43 UTC 에 시스템이 안정되고 데이터베이스가 정상 수준으로 돌아와 새 장애 조치를 껐다. 18:06 UTC 에는 전체 시스템이 안정된 것을 확인하고 모니터링 상태로 넘겼다. 서비스는 약 90분 만에 돌아왔다. 사고 직후 해당 테이블의 statistics target 을 올렸고, 그날 저녁 플래너가 결정론적인 플랜을 세울 수 있게 쿼리를 다시 짰다.

심각도
–
영향 범위
Over 95% of Clerk traffic returned 429 during an outage of about 90 minutes; sign in and account management APIs were unavailable; session tokens were served by a failover from 17:10 UTC
영향 사용자
Clerk customers and the users of their applications
지역
–
감지까지
4m
복구까지
1h 30m

증상

2026-02-19 8:11 AM PST 에 데이터베이스 성능이 갑자기 떨어졌다. 요청 핸들러에 요청이 쌓였고, Clerk 트래픽의 95% 넘게 처리되지 못한 채 429 를 받았다. 데이터베이스까지 간 일부 요청은 200 을 받았지만 아주 느렸다.

문제의 쿼리는 자주 실행되는 쿼리였다. 이 쿼리가 예상치 못하게 17,000개가 넘는 행을 훑으면서 데이터베이스 자원 거의 전부를 차지했다.

17:10 UTC 에 세션 토큰 장애 조치가 성공한 뒤로 많은 사용자가 고객 앱에 다시 들어갈 수 있었지만, 로그인과 계정 관리 API 는 여전히 쓸 수 없었다.

16:32 UTC 에 팀은 장애와 같은 때에 트래픽이 비정상으로 치솟은 고객 하나를 찾았고, 그 부하가 데이터베이스 문제를 일으켰다고 보았다. 16:35 UTC 에 이 고객의 트래픽을 수동으로 막았지만 시스템은 돌아오지 않았다. 16:50 UTC 에 팀은 그 고객의 급증이 지나치게 공격적인 재시도 때문이었고, 재시도를 부른 첫 실패는 Clerk 인프라 안에 있었다는 것을 알았다.

Session API 의 자동 장애 조치는 작동하지 않았다. 데이터베이스가 느려졌을 뿐 엄밀히는 살아 있었기 때문이다.

증상 서비스 중단데이터베이스 과부하지연 시간 증가

원인

촉발
Postgres 의 일상적인 자동 ANALYZE 가 표본을 너무 적게 뽑아 한 컬럼의 값이 100% NULL 이라고 잘못 판단했다. 그 결과 자주 실행되는 쿼리의 플랜이 비효율적인 것으로 바뀌었다.

감지

16:15 UTC 에 사고가 내부에 보고되고 에스컬레이션됐고, 팀이 그룹 통화를 열어 근본 원인을 찾기 시작했다.

조치

후속 조치

교훈

Clerk 는 쿼리 플랜이 바뀌는 문제가 갑자기 심하게 성능을 떨어뜨리므로, 뒤따르는 증상에 기대지 말고 바로 잡아내야 한다고 보았다. 이런 알림이 있었다면 고객의 트래픽 패턴이 원인이라고 잘못 보는 일을 피할 수 있었다. 데이터베이스가 내려가지 않고 느려지기만 하자 Postgres 장애를 기준으로 만든 Session API 의 자동 장애 조치는 작동하지 않았다. 고객들은 상태 페이지 공지가 너무 드물고, 첫 심각도 표시가 실제 영향과 맞지 않았으며, 첫 공지까지 시간이 너무 걸렸다고 지적했다.

출처

관련 사례

–

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

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

AI 도구와 스크립트용

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