{"id":"IL-0251","org":"Slack","date":"2020-05-12","docs":[],"title":{"en":"Most of Slack's HAProxy load balancers routed to a stale, shrinking set of webapp backends and served HTTP 503 errors","ko":"HAProxy 가 낡은 백엔드 목록에 묶여 일부 webapp 서버에만 요청을 보내면서 Slack 이 HTTP 503 오류를 냈다"},"domain":["networking","aws"],"impact":{"scope":"Slack requests through the HAProxy tier failed with HTTP 503 errors from 4:45pm Pacific","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The alerting for this exact failure had stopped working, and nobody noticed because the HAProxy stack had run for a long time without needing changes, so few engineers worked with its monitoring and alerting. A slot limit that had been ample a few months earlier was reached after pandemic-driven growth and one large autoscaling event.","ko":"변화가 적은 시스템은 엔지니어가 모니터링과 알림을 들여다볼 일이 적어서, 알림이 고장 나도 오래 모른 채 지나간다. HAProxy 서버 상태를 바꾸는 복잡한 파이프라인에는 이번 장애가 드러낸 것 같은 운영상의 위험이 있다."},"related":[],"sources":[{"url":"https://slack.engineering/a-terrible-horrible-no-good-very-bad-day-at-slack/","kind":"blog","archived":"https://web.archive.org/web/20261105011050/https://slack.engineering/a-terrible-horrible-no-good-very-bad-day-at-slack/"}],"summary":{"en":"Slack sends requests to its webapp tier through a fleet of HAProxy instances behind a layer 4 load balancer. Consul provides service discovery, and consul-template renders the list of healthy webapp backends. To avoid reloading HAProxy for every change, a Slack program called haproxy-server-state-management reads that list and updates HAProxy through its Runtime API. HAProxy is configured with server templates that act as slots: N slots for webapp backends in each of M Availability Zone pools, N * M in total, which a few months earlier had been far more than Slack ever needed.\n\nAt about 8:30am that day a configuration change, rolled out as a percentage-based feature flag, triggered a longstanding performance bug and raised load on part of Slack's database infrastructure. The flag was rolled back and customer impact lasted three minutes, but slow database requests saturated webapp workers and autoscaling raised the instance count by 75%, the most webapp hosts Slack had ever run and slightly more than N * M. haproxy-server-state-management had a bug: it always tried to place new webapp instances in a slot before freeing the slots of instances that had gone away. With no empty slot it failed and exited early, so the running HAProxy instances stopped receiving state updates. Over the day the backend lists grew more and more stale. When the webapp tier scaled down at the end of the US business day, autoscaling removed the older instances first, and the backends still listed in HAProxy could no longer serve the demand.\n\nOnce the cause was known, a rolling restart of the HAProxy fleet resolved the outage. Slack found that its alerting for this failure was broken, which had gone unnoticed because the system had worked without changes for a long time. Slack was already moving its ingress load balancing to Envoy Proxy, whose xDS integration replaces this pipeline and is not exposed to this failure.","ko":"그날 아침 8시 30분 무렵 설정 변경 배포가 오래된 성능 버그를 건드려 데이터베이스 부하가 크게 늘었다. 이 변경은 비율을 정해 조금씩 켜는 기능 플래그였기 때문에 금방 찾아 롤백할 수 있었고, 고객이 영향을 받은 시간은 3분이었다. 그사이 데이터베이스 요청이 늦어져 webapp 워커가 포화됐고, 자동 확장이 webapp 인스턴스를 75% 늘려 사상 가장 많은 수가 됐다.\n\nSlack 에서는 4계층 로드 밸런서 뒤의 HAProxy 들이 webapp 계층으로 요청을 나눈다. 서비스 디스커버리는 Consul 이고, consul-template 이 정상 webapp 백엔드 목록을 만든다. HAProxy 를 자주 리로드하면 프로세스가 너무 많아지므로, Slack 이 만든 haproxy-server-state-management 가 그 목록을 읽어 HAProxy Runtime API 로 서버 상태를 바꾼다. HAProxy 에는 가용 영역마다 N 개의 백엔드 슬롯이 정의돼 있고 가용 영역이 M 개라서 모두 N * M 개다. 아침 사고 뒤 webapp 인스턴스가 N * M 개를 조금 넘었다. 슬롯을 얻지 못한 몇 대는 트래픽을 받지 못했지만, 처리 용량이 넉넉해서 문제가 되지 않았다.\n\n문제는 haproxy-server-state-management 의 버그였다. 이 프로그램은 사라진 인스턴스의 슬롯을 비우기 전에 늘 새 인스턴스의 슬롯부터 찾았다. 빈 슬롯이 없자 프로그램이 도중에 실패하고 끝나 버렸다. 그 뒤로 돌고 있는 HAProxy 의 상태가 더 이상 갱신되지 않았다. 하루 동안 webapp 이 늘고 줄면서 HAProxy 의 백엔드 목록은 점점 낡아 대부분 이미 없어진 서버를 가리켰다. 미국 업무 시간이 끝나 webapp 계층이 줄기 시작하자 남은 오래된 서버로는 요청을 감당하지 못해, 태평양 시간 오후 4시 45분부터 503 이 났다. 원인을 안 뒤 HAProxy 를 순차 재시작해 금방 복구했다. 이 상황을 잡을 알림이 있었지만 제대로 동작하지 않았다. 시스템이 오래 바뀌지 않아 엔지니어들이 모니터링을 들여다볼 일이 적었고, 그래서 알림이 고장 난 것을 아무도 몰랐다."},"trigger":{"en":"At the end of the US business day the webapp tier began its usual scale-down, and autoscaling terminated the oldest instances first, which were the only backends left in most HAProxy instances' state.","ko":"미국 업무 시간이 끝나 트래픽이 줄자 webapp 계층이 축소되기 시작했다. 자동 확장은 오래된 인스턴스부터 종료하므로, HAProxy 상태에 남아 있던 오래된 webapp 서버만으로는 요청을 감당하지 못하게 됐다."},"severity":"unknown","symptoms":["high-error-rate","uneven-load","stale-service-routing"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"incident","detection":{"en":"An alert for more HTTP 503 errors than normal. The alerting meant to catch stale HAProxy state was not working.","ko":"평소보다 HTTP 503 오류가 많다는 알림으로 알아챘다. 이 상황을 직접 잡도록 만든 알림은 고장 나 있어 울리지 않았다."},"components":["haproxy","service-discovery"],"root_cause":"software-bug","remediation":[{"text":{"en":"Did a rolling restart of the HAProxy fleet","ko":"HAProxy 서버 전체를 순차 재시작했다."}}],"action_items":[{"text":{"en":"Move all ingress load balancing from HAProxy to Envoy Proxy, with endpoint discovery through an xDS control plane","ko":"모든 인그레스 로드 밸런싱을 Envoy Proxy 로 옮기고, HAProxy 서버 상태를 바꾸던 파이프라인을 Envoy 가 xDS 컨트롤 플레인에서 엔드포인트를 받는 방식으로 바꾼다."}}],"contributing":["automation-amplified","no-alert-on-limit"],"recurrence_of":null,"symptoms_text":{"en":"At 4:45pm Pacific on 12 May 2020, Slack was alerted that it was serving more HTTP 503 errors than normal. A subset of the webapp fleet was under heavy load while the rest of the webapp instances were not.\n\nThe on-call engineer for the webapp tier first scaled up the webapp fleet by hand, which did not help at all. Investigation then looked at both webapp performance and the load balancer tier, and a few minutes later found that most HAProxy instances could only send requests to the webapp backends that had been running since that morning, which by then were a minority of the fleet. Only a few HAProxy instances provisioned recently had the correct backend list.\n\nThe backend state inside HAProxy had been going stale for about eight hours without any visible effect. The program that copies the host list from consul-template into HAProxy had been failing and exiting early, so as the webapp autoscaling group scaled up and down during the day, HAProxy's backends came to refer mainly to hosts that no longer existed. Alerting for exactly this situation was in place but was not working as intended.","ko":"2020년 5월 12일 태평양 시간 오후 4시 45분 무렵, Slack 이 평소보다 많은 HTTP 503 오류를 내고 있다는 알림이 왔다. 사용자에게 보이는 장애가 이때 시작됐다.\n\nwebapp 담당 온콜 엔지니어가 첫 조치로 webapp 서버 수를 손으로 늘렸지만 전혀 나아지지 않았다. 서버 수를 늘려도 전혀 나아지지 않는 경우는 드물었다.\n\n곧 webapp 서버 가운데 일부만 부하가 몰리고 나머지는 한가하다는 것이 보였다. 팀은 webapp 성능과 로드 밸런서 계층을 나눠 조사했고, 몇 분 뒤 원인을 찾았다.\n\n대부분의 HAProxy 는 아침부터 떠 있던 webapp 서버에만 요청을 보낼 수 있었고, 이 서버들은 이제 전체의 소수였다. 새로 띄운 몇 대의 HAProxy 만 올바른 백엔드 목록을 갖고 있었다. 대부분은 여덟 시간 넘게 떠 있던 인스턴스라, 슬롯이 다 찬 채 갱신되지 않은 백엔드 상태에 묶여 있었다."},"updated_at":"2026-11-14T12:33:49.956092Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0251","org_slug":"slack"}