{"id":"IL-0498","org":"Clerk","date":"2025-09-14","docs":[],"title":{"en":"Clerk's Postgres database had load spikes every 15 minutes and requests failed and slowed intermittently for four days","ko":"Postgres 데이터베이스 부하가 15분마다 치솟아 나흘 동안 요청 실패와 지연 급증이 간헐적으로 이어졌다"},"domain":["database","gcp"],"impact":{"scope":"Clerk customer traffic: intermittent significant request failures and latency spikes; for 3 to 6 minutes on 17 September new sign ins, sign ups and other account management failed, while user sessions stayed active","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Clerk's review found that connection metrics sampled every 60 seconds could not show a recycling pattern that lasted seconds and repeated every 15 minutes, that an attack and background jobs with similar periods pulled attention away from the database layer, that the Backend API looked healthy only because it manages far fewer connections, and that the release freeze removed the staggering of connection creation that normal deploys provide.","ko":"Clerk 는 돌아보면 원인이 단순해 보이지만 여러 요인이 겹쳐 찾기 어려웠다고 밝혔다. 첫째, 연결 지표를 60초 간격으로 수집해 15분마다 몇 초 안에 일어나는 재생성 주기가 대시보드에 드문드문한 급증으로만 보였다. 둘째, 같은 시기의 사기 가입 공격이 약 10분 주기로 부하를 만들어 원인을 그쪽으로 의심하게 했다. 셋째, 10분, 15분, 30분 간격의 정기 작업과 예약 쿼리가 급증 시각과 맞아떨어져 조사를 흩뜨렸다. 넷째, 같은 연결 풀링 로직을 쓰는 Backend API 에는 급증이 잘 보이지 않았는데, Clerk 는 Backend API 가 관리하는 연결 수가 훨씬 적어 재생성이 맞춰져도 데이터베이스가 감당했기 때문으로 봤다. 다섯째, 장애 중에 건 배포 동결은 평소 배포가 연결 생성 시각을 흩어 주던 효과를 없애 문제를 키웠다. 이 관계가 직관적이지 않아 연결 동기화를 가리키는 가장 분명한 신호가 가려졌다."},"related":[],"sources":[{"url":"https://clerk.com/blog/2025-09-18-database-incident-postmortem","kind":"blog","archived":"https://web.archive.org/web/20261009213448/https://clerk.com/blog/2025-09-18-database-incident-postmortem"}],"summary":{"en":"Clerk runs its services on Cloud Run with many containers. The connection pooler in each container had a static maximum connection lifetime of 15 minutes, so each connection was closed and replaced every 15 minutes. Before the upgrade, Postgres' lock manager granted new connections with O(n²) time complexity, which acted as a natural rate limiter and spread the replacement of expired connections over time. The minor version that the cloud provider applied on 14 September 2025 optimised connection granting, so large batches of new connections could be granted almost at once.\n\nWith fast connection granting, all expired connections in a container were recreated at the same moment. During a deploy the Cloud Run containers start one after another within about a minute, which keeps their recycling apart, but any back pressure in the database gradually pulled the recycling of all containers into step. Once synchronised, every container refreshed its connections at the same time every 15 minutes, the effect grew with each cycle, the pool of active connections ran out and customers saw latency spikes. The Backend API was not affected in the same way because it manages far fewer connections. A release freeze during the incident took away the staggering that normal deploys give connection creation and made the problem worse.\n\nFrom 15 to 18 September engineers optimised expensive queries, refined critical indexes and re-indexed, blocked abusive traffic more aggressively, which may have rejected a small number of legitimate requests with HTTP 429, performed a manual minor version upgrade and increased database capacity. The provider was unwilling to roll the database back to the earlier version. At 03:19 UTC on 18 September the team identified the cause and added jitter to the connection pooling configuration. Database load dropped below pre-incident levels at once and the latency spikes stopped.","ko":"2025년 9월 14일 05:30 UTC에 클라우드 사업자가 Clerk 의 Postgres 를 마이너 버전으로 자동 업그레이드했다. 이전 버전의 잠금 관리자는 새 연결을 연결 수의 제곱에 비례하는 시간 복잡도로 허가해, 처리하는 연결이 많을수록 연결 요청 하나하나가 느려졌다. 이 비효율이 뜻하지 않게 새 연결 생성 속도를 늦추는 속도 제한 구실을 했다. 업그레이드 뒤에는 이 병목이 사라져 많은 새 연결이 거의 한꺼번에 허가됐다.\n\nClerk 는 Cloud Run 컨테이너를 많이 쓰고, 컨테이너마다 연결 풀러의 최대 연결 수명이 15분으로 고정돼 있어 연결이 15분마다 닫히고 새로 만들어졌다. 예전에는 새 연결이 천천히 만들어져 만료가 시간에 걸쳐 흩어졌지만, 업그레이드 뒤에는 만료된 연결이 한꺼번에 다시 만들어졌다. 배포 직후에는 컨테이너 시작 시각이 흩어져 있었지만, 데이터베이스에 백프레셔가 생길 때마다 컨테이너들의 연결 재생성 시각이 점점 맞춰졌다. 결국 15분마다 모든 컨테이너가 연결을 동시에 새로 만들었고, 이 효과가 15분마다 쌓이면서 활성 연결 풀이 고갈돼 고객이 보는 지연 급증으로 이어졌다.\n\n9월 15일 13:08 UTC에 장애가 시작됐다. 팀은 18일까지 쿼리를 최적화하고, 인덱스를 다듬어 전체 재색인을 하고, 데이터베이스 파라미터를 조정했다. 남용 트래픽 차단도 잠시 강화했는데, 이 때문에 정상 요청 일부가 HTTP 429 로 거부됐을 수 있다. 자동 업그레이드를 되돌리는 방안도 알아봤지만 사업자가 데이터베이스 버전을 되돌리려 하지 않았다. 9월 17일에는 수동 마이너 버전 업그레이드와 용량 증설을 했다. 6월 26일 장애 뒤에 한 개선 덕분에 세션은 이번 장애 대부분 동안 동작했다. 장애 중 위험을 줄이려고 건 배포 동결은 오히려 문제를 키웠다. 평소 배포에서는 머신이 차례로 올라오면서 연결 생성 시각이 머신마다 흩어졌는데, 동결로 이 효과가 사라졌기 때문이다.\n\n9월 18일 03:19 UTC에 근본 원인을 찾았고, 연결 재생성 시각이 서로 맞춰지지 않도록 연결 풀링 설정에 지터를 넣었다. 배포 직후 데이터베이스 부하는 장애 전보다 낮아졌고 지연 급증이 사라졌다. 이후 데이터베이스는 장애 중 넣은 성능 개선 덕분에 업그레이드 전보다 나은 상태로 안정됐다."},"trigger":{"en":"At 05:30 UTC on 14 September 2025 the cloud provider automatically upgraded Clerk's Postgres database to a new minor version, which changed how Postgres handled connection locks.","ko":"2025년 9월 14일 클라우드 사업자가 Clerk 의 Postgres 데이터베이스를 마이너 버전으로 자동 업그레이드했다. 이 업그레이드에는 Postgres 가 연결 잠금을 다루는 방식의 큰 내부 변경이 들어 있었다."},"severity":"unknown","symptoms":["high-error-rate","high-latency","database-overloaded","connection-pool-exhausted"],"timeline":{"detected_after":"0m","resolved_after":"62h 11m"},"date_kind":"incident","detection":{"en":"Internal monitoring alerted on significant request failures and increased latency at 13:08 UTC on 15 September. Shortly after the upgrade on 14 September it had already shown an increased but still acceptable database load.","ko":"9월 14일 업그레이드 직후 내부 모니터링이 데이터베이스 부하 증가를 잡았지만 견딜 만한 수준이었고 지연에는 영향이 없었다. 9월 15일 13:08 UTC에 내부 모니터링이 요청 실패가 크게 늘고 지연이 늘었다고 알렸다."},"components":["database","database-proxy"],"root_cause":"upgrade-breaking-change","remediation":[{"text":{"en":"Optimised several expensive queries, refined critical indexes and re-indexed the database","ko":"비용이 큰 쿼리 여러 개를 최적화해 데이터베이스의 기본 부하를 영구히 줄였다. 주요 인덱스를 다듬고 전체 재색인을 해 데이터베이스 오버헤드도 줄였다."}},{"text":{"en":"Blocked abusive traffic more aggressively for a time","ko":"안정성을 지키려고 남용 트래픽 차단을 잠시 강화했다. 효과는 있었지만 정상 요청 일부가 HTTP 429 로 거부됐을 수 있다."}},{"text":{"en":"Performed a manual minor version upgrade and increased database capacity","ko":"9월 17일 07:15 UTC부터 08:10 UTC까지 수동 마이너 버전 업그레이드를 하고 데이터베이스 용량을 늘렸다. 데이터베이스 상태가 눈에 띄게 나아졌고 지연 급증이 다시 사라진 듯했다."}},{"text":{"en":"Added jitter to the connection pooling configuration so connections no longer recycled in step","ko":"연결 재생성 시각이 서로 맞춰지지 않도록 연결 풀링 전략에 지터를 넣었다. 배포 직후 데이터베이스 부하가 장애 전보다 낮아졌고 지연 급증이 사라졌다."}}],"action_items":[{"text":{"en":"Evaluate other database providers that give more control over upgrades and downgrades","ko":"업그레이드와 다운그레이드를 더 통제할 수 있고 지금 사업자보다 성능이 나은 데이터베이스 솔루션을 검토한다."}},{"text":{"en":"Isolate more services from each other, for example Sign Up and Sign In infrastructure from the rest of the system","ko":"가입과 로그인 인프라를 시스템의 다른 부분에서 떼어 내는 식으로 서비스 격리를 넓힌다."}},{"text":{"en":"Keep optimising the most costly database queries","ko":"비용이 큰 쿼리 목록을 계속 따라가며 데이터베이스 성능을 더 끌어올린다."}},{"text":{"en":"Evaluate sharding and multi-tenant infrastructure to isolate one application from others","ko":"애플리케이션 하나를 다른 애플리케이션과 격리하는 테넌트 격리 방법을 검토한다. 이 작업은 샤딩과 멀티테넌트 인프라의 최신 방식을 살펴보는 일이라 다른 개선보다 오래 걸릴 가능성이 크다."}}],"contributing":["unpinned-version","retry-storm","monitoring-blind-spot","overlapping-failures","manual-action-worsened"],"recurrence_of":null,"symptoms_text":{"en":"At 13:08 UTC on 15 September 2025, Clerk's internal monitoring alerted on significant request failures and increased latency. Over the following days the request failures and latency spikes kept coming back while the Postgres database stayed unhealthy, with repeated spikes in database load.\n\nThe database had already looked different the day before. Shortly after the cloud provider automatically upgraded the database to a new minor version at 05:30 UTC on 14 September, monitoring showed an increased but still acceptable database load with no effect on latency. On 15 September latency returned to normal at 14:09 UTC, and significant failures and latency spikes came back from 14:58 to 15:16 UTC that day and from 15:07 to 15:14 UTC on 17 September. The latency spikes showed in Clerk's Frontend API but not consistently in its Backend API, which has the same connection pooling logic.\n\nOn 17 September a deploy with a new query optimisation at 00:22 UTC seemed to remove the load spikes, but they were back at 01:00 UTC and now came roughly every 15 minutes. Session management kept working through these spikes, because the session endpoints tolerate more latency and Clerk's SDKs retry. A manual minor version upgrade and an increase in database capacity from 07:15 to 08:10 UTC caused 3 to 6 minutes of failures for new sign ins, sign ups and other account management, and the spikes again seemed to be gone until the afternoon.\n\nSeveral things hid the cause. Database connection metrics were sampled every 60 seconds while the connection recycling happened within seconds every 15 minutes, so dashboards showed only occasional spikes in active connections, and the team first treated them as a symptom of load. In the same days Clerk was mitigating a large volume of fraudulent sign ups whose load surges came about every 10 minutes, and recurring background jobs and scheduled queries ran on 10, 15 and 30-minute intervals, so the team first suspected the attack and those jobs. A release freeze imposed to reduce risk during the incident made the problem worse.","ko":"2025년 9월 15일 13:08 UTC에 내부 모니터링이 요청 실패가 크게 늘고 지연이 늘었다고 알렸다. 그 뒤 9월 18일까지 고객 트래픽에서 요청 실패와 지연 급증이 간헐적으로 이어졌다.\n\n그 전날인 9월 14일 05:30 UTC에 클라우드 사업자가 데이터베이스를 마이너 버전으로 자동 업그레이드했다. 직후 내부 모니터링은 데이터베이스 부하가 늘어난 것을 잡았지만 아직 견딜 만한 수준이었고 지연에는 영향이 없었다.\n\n9월 15일 14:09 UTC에 요청 지연은 정상으로 돌아왔지만 데이터베이스 상태는 계속 나빴다. 14:58 UTC부터 15:16 UTC까지 다시 요청 실패와 지연 급증이 있었다. 9월 17일 00:22 UTC에 새 쿼리 최적화를 담은 배포 뒤 데이터베이스 부하 급증이 사라진 듯했지만 01:00 UTC에 다시 나타났다. 이번에는 급증이 대략 15분마다 주기적으로 나타났다. 이 동안 세션 관리는 세션 엔드포인트가 지연을 더 잘 견디고 SDK 가 재시도한 덕분에 동작했다.\n\n9월 17일 07:15 UTC부터 08:10 UTC까지 수동 마이너 버전 업그레이드와 용량 증설을 하던 중 3~6분 동안 새 로그인, 가입, 그 밖의 계정 관리 작업이 실패했다. 사용자 세션은 내내 살아 있었다. 같은 날 15:07 UTC부터 15:14 UTC까지 다시 요청 실패와 지연 급증이 있었고, 15분마다 치솟는 데이터베이스 부하가 또 보였다.\n\n데이터베이스 연결 지표는 60초 간격으로 수집됐지만 연결 재생성은 15분마다 몇 초 안에 일어났다. 대시보드에는 활성 연결이 가끔 치솟는 모습만 보였고, 팀은 급증이 규칙적이지 않고 드문드문하다고 보고 이를 시작 원인이 아니라 밑에 깔린 부하의 증상으로 다뤘다.\n\n같은 시기에 팀은 일부 고객을 노린 대량의 사기 가입을 막고 있었다. 이 공격은 약 10분마다 부하 급증을 만들어 데이터베이스의 15분 주기와 겹쳤고, 팀은 처음에 이 공격을 지연 급증의 주원인으로 의심했다. 10분, 15분, 30분 간격으로 도는 백그라운드 작업과 예약 쿼리도 급증 시각과 우연히 맞아떨어져 조사의 방향을 흐렸다.\n\n지연 급증은 Frontend API 에서 보였지만, 같은 연결 풀링 로직을 쓰는 Backend API 에서는 꾸준히 보이지 않았다. 이 차이 때문에 문제가 시스템 전체의 연결 풀링이 아니라 일부 부하나 쿼리 비효율로 보였다."},"updated_at":"2026-11-14T12:34:14.329133Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0498","org_slug":"clerk"}