{"id":"IL-0352","org":"Heroku","date":"2025-07-18","docs":["https://devcenter.heroku.com/articles/http-routing#legacy-router-and-router-2-0","https://devcenter.heroku.com/articles/http-routing#request-distribution","https://devcenter.heroku.com/articles/error-codes","https://www.heroku.com/blog/router-2dot0-the-road-to-beta/#load-test-continuously","https://devcenter.heroku.com/changelog-items/2682","https://help.salesforce.com/s/articleView?id=005123907&type=1"],"title":{"en":"Heroku Router 2.0 spread HTTP requests unevenly across dynos for four days, causing latency and timeouts","ko":"Router 2.0 을 쓰는 앱에서 HTTP 요청이 web dyno 에 고르지 않게 나뉘어 지연과 타임아웃이 늘었다"},"domain":["networking"],"impact":{"scope":"HTTP request distribution across web dynos for apps using Router 2.0 on the Common Runtime, with higher latency, timeouts, reduced availability and Heroku error codes","users":"Some customer applications using Router 2.0 on Heroku's Common Runtime","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":null,"related":[],"sources":[{"url":"https://status.heroku.com/incidents/2863","kind":"official-status","archived":"https://web.archive.org/web/20251114162615/https://status.heroku.com/incidents/2863"}],"summary":{"en":"Router 2.0, the default router of Heroku's Common Runtime, uses entropy metadata to spread dyno selection. A code condition present since the Router 2.0 beta reduced the variance of that selection, and a router instance that hit it kept balancing badly for all later requests. Hitting it became more likely as request volume on Router 2.0 grew with the migration of about 94% of Common Runtime apps. From 22:00 UTC on 18 July 2025 more and more router instances hit the condition, and requests piled onto some dynos of affected apps, which caused latency and timeouts. The bug also showed in staging load tests, but their alerts watch availability and latency, not request distribution, so nothing fired, and Heroku learned of the problem only from customers on 20 July.\n\nOn 22 July Heroku declared an internal investigation at 14:35 UTC and a formal incident at 17:48 UTC, posted to the status site at 18:36 UTC, and advised customers to fall back to the legacy router. After seeing the same pattern in staging, engineers cycled the production router fleets at 19:42 UTC, which improved balancing, identified the broken entropy metadata at 20:05 UTC, and at 21:01 UTC reproduced the fault with a new load balancing benchmark test and prepared a fix that passed it. The fix was deployed to staging at 21:13 UTC, judged safe at 21:30 UTC, rolled out to production from 21:38 UTC in stages, and reached the last partition at 00:12 UTC on 23 July, which ended the incident.","ko":"Heroku Common Runtime 의 기본 라우터인 Router 2.0 은 엔트로피 메타데이터로 요청을 받을 dyno 를 고른다. 2025-07-18 22:00 UTC 무렵부터 라우터 인스턴스가 어떤 코드 조건에 닿기 시작했고, 이 조건에서는 메타데이터와 그 로직이 dyno 선택의 폭을 줄였다. 한 번 이 조건에 닿은 인스턴스는 그 뒤 모든 HTTP 요청에서 부하 분산이 깨진 채로 남았다. 요청 처리 자체는 계속됐지만 앱의 web dyno 들에 요청이 고르게 가지 않았고, 조건에 닿는 인스턴스가 늘수록 증상이 천천히 나빠졌다. 이 코드 조건은 Router 2.0 베타 때부터 코드에 있었다. Common Runtime 앱의 94% 남짓이 Router 2.0 으로 옮겨 오면서 새 라우터가 처리하는 요청이 크게 늘었고, 그만큼 조건에 닿을 가능성도 커졌다.\n\nHeroku Support 는 07-20 16:46 UTC 에 첫 고객 신고를 받았다. 07-22 14:35 UTC 에 기술 팀이 내부 조사를 열었고, 지원 팀은 영향을 받은 고객에게 Router 2.0 을 끄고 legacy router 로 돌아가라고 안내했다. 기술 팀은 라우터 dyno 상태가 불완전하다는 가설을 17:01 UTC 에 버렸고, 17:48 UTC 에 정식 사고를 선언했다. 17:53 UTC 에 스테이징의 지속 부하 테스트 로그에도 같은 불균형이 있음을 확인하고 부하 분산 메타데이터를 살피기 시작했다. 19:42 UTC 에는 스테이징에서 라우터 인스턴스를 차례로 새로 띄우면 분산이 나아지는 것을 보고 운영 라우터 인스턴스도 차례로 새로 띄웠다. 20:05 UTC 에 깨진 엔트로피 메타데이터를 근본 원인으로 찾았고, 21:01 UTC 에 새 부하 분산 벤치마크 테스트로 문제를 재현하고 이 테스트를 통과하는 코드 변경을 준비했다.\n\n21:13 UTC 에 변경을 스테이징에 배포해 21:30 UTC 에 안전하다고 판단했고, 21:38 UTC 에 운영 배포를 서둘러 시작했다. 07-23 00:12 UTC 에 마지막 파티션까지 배포를 마쳐 부하 분산이 정상으로 돌아왔다. 스테이징 부하 테스트는 성능이 나쁘면 알림을 울리고 배포를 막지만, 성능은 라우팅 가용성과 HTTP 요청 지연으로만 쟀고 요청 분배는 알림 신호가 아니었다. 스테이징의 테스트 앱은 가용성이나 지연에 드러날 만큼 부하를 받지 않아 알림이 울리지 않았고, 기술 팀은 고객이 영향을 받기 전까지 문제를 몰랐다."},"trigger":{"en":"From 22:00 UTC on 18 July 2025 router instances began hitting a code condition that broke the entropy metadata Router 2.0 uses to choose dynos. The condition had been in the code since the Router 2.0 beta and became more likely as more HTTP requests went through the new router, with about 94% of Common Runtime apps migrated.","ko":"2025-07-18 22:00 UTC 무렵부터 Router 2.0 인스턴스가 어떤 코드 조건에 닿기 시작했다. 이 조건에 닿으면 부하 분산용 엔트로피 메타데이터와 그 로직이 dyno 선택의 폭을 줄였다."},"severity":"unknown","symptoms":["uneven-load","high-latency","intermittent-timeouts"],"timeline":{"detected_after":"42h 46m","resolved_after":"98h 12m"},"date_kind":"incident","detection":{"en":"No alert fired. Heroku Support received the first customer reports of uneven request balancing at 16:46 UTC on 20 July 2025, and an internal investigation was declared on 22 July.","ko":"07-20 16:46 UTC 에 Heroku Support 가 요청이 dyno 에 고르지 않게 가서 지연과 타임아웃이 늘었다는 첫 고객 신고를 받았다. 스테이징 부하 테스트에도 같은 불균형이 있었지만, 알림은 라우팅 가용성과 HTTP 요청 지연만 보고 요청 분배는 보지 않아 울리지 않았다."},"components":["cloud-load-balancer"],"root_cause":"software-bug","remediation":[{"text":{"en":"Advised affected customers to disable Router 2.0 and fall back to the legacy router","ko":"지원 팀이 영향을 받은 고객에게 Router 2.0 을 끄고 legacy router 로 돌아가라고 안내했다."}},{"text":{"en":"Cycled the production router fleets, which improved load balancing for affected apps","ko":"운영 라우터 인스턴스를 차례로 새로 띄워 영향을 받은 앱의 부하 분산을 나아지게 했다."}},{"text":{"en":"Deployed the router fix to staging, then rolled it out to all Common Runtime partitions by 00:12 UTC on 23 July","ko":"부하 분산을 고친 라우터 코드를 스테이징에 먼저 배포한 뒤 운영에 단계적으로 배포해 07-23 00:12 UTC 에 마지막 파티션까지 마쳤다."}}],"action_items":[{"text":{"en":"Introduced gating and notifications so that load balancing behaviour regressions do not occur","ko":"부하 분산 동작이 다시 망가지지 않도록 막는 게이트와 알림을 넣었다."}},{"text":{"en":"Communicate the timeline and send reminders to apps still on the legacy router before automated migrations resume","ko":"자동 이전을 다시 시작하기 전에, 아직 legacy router 를 쓰는 앱의 고객에게 일정과 안내를 꾸준히 알린다."}}],"contributing":["monitoring-blind-spot","staging-not-representative"],"recurrence_of":null,"symptoms_text":{"en":"From about 22:00 UTC on 18 July 2025, some apps on Heroku's Common Runtime that used Router 2.0 had their HTTP requests balanced unevenly across their web dynos. The dynos that got more requests were overloaded, and the apps saw higher HTTP request latency, timeouts, reduced availability and various Heroku error codes in application logs and dashboards.\n\nThe imbalance, measured as the percentage variance in HTTP requests per dyno, grew steadily over the following days. Once a router instance hit the faulty code condition, its load balancing stayed broken for every later request, and more router instances hit the condition over time.\n\nHeroku Support received the first customer reports of uneven request distribution, with higher latency and timeouts, at 16:46 UTC on 20 July. An internal investigation was declared at 14:35 UTC on 22 July, and Support began advising affected customers to disable Router 2.0 and fall back to the legacy router. Engineers first suspected incomplete router dyno state and dropped that theory at 17:01 UTC. At 17:53 UTC they saw the same uneven distribution in the logs of the router's continuous load tests in staging, turned to the load balancing metadata, and found at 19:42 UTC that cycling router instances improved balancing.\n\nThe staging load tests had not raised an alert. They alert and block deployments on reduced routing availability and higher latency, but request distribution was not an alerting signal, and the uneven distribution in staging did not strain the test apps enough to move availability or latency.","ko":"Heroku Common Runtime 에서 Router 2.0 을 쓰는 일부 앱의 HTTP 요청이 web dyno 사이에 고르게 나뉘지 않았다. 요청이 몰린 dyno 가 과부하에 빠지면서 HTTP 요청 지연과 타임아웃이 늘었다.\n\n앱에서는 가용성 저하와 서비스 품질 저하, HTTP 요청 지연 증가가 나타났고, 애플리케이션 로그와 대시보드에 여러 가지 Heroku 오류 코드가 찍혔다.\n\ndyno 당 HTTP 요청 수의 백분율 편차로 잰 불균형은 2025-07-18 22:00 UTC 무렵부터 꾸준히 커졌다. 문제의 코드 조건에 닿는 라우터 인스턴스가 늘수록 증상이 천천히 나빠졌다.\n\nHeroku Support 는 07-20 16:46 UTC 에 요청이 dyno 에 고르지 않게 가서 지연과 타임아웃이 늘었다는 첫 고객 신고를 받았다. 07-22 14:35 UTC 에 내부 조사가 열렸고, 기술 팀은 라우터 dyno 상태가 불완전한 것이 원인이라고 보고 살폈지만 17:01 UTC 에 이 가설을 버렸다.\n\n스테이징의 지속 부하 테스트 로그에도 같은 불균형이 있었지만 알림은 울리지 않았다. 알림은 라우팅 가용성과 HTTP 요청 지연만 봤고 요청 분배는 보지 않았다."},"updated_at":"2026-11-14T12:34:00.663371Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0352","org_slug":"heroku"}