{"id":"IL-0324","org":"Wikimedia Foundation","date":"2025-03-12","docs":[],"title":{"en":"Three MediaWiki deployments lost Memcached after a PHP 8.1 rollout and overloaded the External Storage database cluster","ko":"External Storage 데이터베이스 클러스터에 과부하가 걸려 mw-api-int, mw-parsoid, mw-jobrunner 의 지연과 데이터베이스 오류가 늘었다"},"domain":["database","kubernetes","observability"],"impact":{"scope":"mw-api-int, mw-parsoid and mw-jobrunner had high latency and database errors on 12 and 13 March; global traffic had about a 0.03-0.04% error rate and edits were affected very briefly","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The write-up puts the weight not on the technical root cause but on why the team could not see, even after the 12 March incident, that three MediaWiki deployments were running without Memcached. Alert dashboards showed what was happening, such as many busy PHP workers, but not why; dashboards linking components that affect each other, such as SQLBlobStore keys and External Storage, would have helped. The alert that pointed in the right direction did not stay firing and would probably have been lost among 78 others. The incident coordinator could not tell who was working on what or who had joined or left, the channels were noisy, the external impact was judged more critical than it was, and trace.wikimedia.org was not used although it might have helped.","ko":"Wikimedia 는 기술적 원인보다, 3월 12일 장애 때부터 MediaWiki 배포 세 곳이 Memcached 클러스터에 닿지 못하고 있다는 것을 알아채지 못한 이유가 더 중요하다고 봤다. 알림에 연결된 대시보드는 무엇이 일어나는지만 보여 줬다. Wikimedia 는 서로 의존하거나 영향을 주는 구성 요소를 함께 보여 주는 대시보드가 있었다면 달랐을 것이라고 적었다. 예를 들어 SQLBlobStore 키는 External Storage 와 바로 이어져 있다. 원인을 가리킬 알림은 곧바로 회복됐다. 그 알림이 울렸더라도 알림 78개 사이에서는 부수 증상으로 봤을 수 있었다. 응답자가 많았지만 누가 무엇을 하는지, 누가 대응에 들어오고 나갔는지 분명하지 않아 장애 조율자가 조율하기 어려웠다. 외부 사용자 영향을 제대로 가늠하지 못해 실제보다 심각하게 여겼다. trace.wikimedia.org 도 장애 중에 쓰지 않았다."},"related":[],"sources":[{"url":"https://wikitech.wikimedia.org/wiki/Incidents/2025-03-12_ExternalStorage_Database_Cluster_Overload","kind":"runbook","archived":"https://web.archive.org/web/20260515162923/https://wikitech.wikimedia.org/wiki/Incidents/2025-03-12_ExternalStorage_Database_Cluster_Overload"}],"summary":{"en":"Scap, Wikimedia's MediaWiki deployment tool, builds and pushes MediaWiki images, updates helmfile-defaults with the latest image tag for each deployment and release, and runs helmfile on every MediaWiki deployment. PHP-FPM reads two environment variables from a ConfigMap called mediawiki-main-php-envvars, mounted as /etc/php/<X.Y>/fpm/env/envvars.inc: MCROUTER_SERVER, the Memcached and mcrouter address, which defaults to 127.0.0.1:11213, and STATSD_EXPORTER_PROMETHEUS_SERVICE_HOST, which is unset by default. The include directory is hardcoded per PHP version, /etc/php/7.4/fpm/env/*.inc in the publish-74 image and /etc/php/8.1/fpm/env/*.inc in the publish-81 image. The PHP 8.1 rollout was two patches: 1126607 in puppet switched mw-api-int, mw-parsoid and mw-jobrunner from the publish-74 to the publish-81 image flavour, and 1126650 in deployment-charts moved the envvars.inc mount to the 8.1 path.\n\nScap was run with -Dbuild_mw_container_image:False, a flag SREs commonly use because most changes need no image rebuild, and publish-81 was already built and cached. Nobody realised that the flag also makes Scap skip the helmfile-defaults update, so the three deployments kept the publish-74 image while the ConfigMap was mounted at the 8.1 path. The 7.4 include directory held only default values, so the pods ran without access to the Memcached cluster and without a valid prometheus StatsD address; the StatsD gap had no effect because MediaWiki also pushed stats to Graphite. Without Memcached, MediaWiki queried the databases for objects that are normally cached, and the load on the External Storage cluster and the core databases rose.\n\nOn 12 March responders lowered the load from cirrus-streaming-updater and changeprop-jobqueue and, at 13:44, reverted both patches and re-ran Scap. On 13 March the rollout was attempted again with patches 1127476 and 1127478, and the three deployments again ran without access to Memcached; after rolling restarts of changeprop-jobqueue and mw-api-int and disabling the categorymembership job, the patches were reverted at 11:46, mw-api-int was redeployed and given more replicas, and mw-jobrunner and mw-parsoid were redeployed by 14:06. The write-up names Scap, the php-fpm envvars.inc include file and the logstash-prometheus pipeline as the factors that caused the incident and delayed finding its root cause, and says coordination among the 9 responders was difficult.","ko":"Wikimedia 는 2025-03-12 와 2025-03-13 에 MediaWiki 의 비사용자 대상 배포인 mw-api-int, mw-parsoid, mw-jobrunner 를 PHP 8.1 로 옮기려고 Scap 으로 배포했다. Scap 은 MediaWiki 배포 도구다. Scap 은 MediaWiki 이미지를 빌드해 올리고, 배포와 릴리스마다 helmfile-defaults 의 이미지 태그를 갱신하고, MediaWiki 를 돌리는 모든 배포에 helmfile 을 실행한다. 이번 배포는 패치 두 개로 이뤄졌다. puppet 패치는 이미지 종류를 publish-74 에서 publish-81 로 바꾸고, deployment-charts 패치는 mediawiki-main-php-envvars ConfigMap 의 마운트 위치를 /etc/php/7.4/fpm/env/envvars.inc 에서 /etc/php/8.1/fpm/env/envvars.inc 로 바꿨다.\n\nScap 은 SRE 들이 흔히 쓰는 -Dbuild_mw_container_image:False 플래그로 실행됐다. 이 플래그가 이미지 빌드뿐 아니라 helmfile-defaults 갱신까지 건너뛴다는 것은 바로 드러나지 않았다. 그 결과 세 배포는 publish-74 이미지로 도는데 ConfigMap 은 8.1 경로에 마운트된 상태가 됐다. PHP-FPM 은 PHP 버전에 따라 정해진 경로에서 환경 변수를 읽으므로 7.4 경로의 기본값을 읽었다. 그래서 memcached 와 mcrouter 주소인 MCROUTER_SERVER 는 기본값 127.0.0.1:11213 이 됐고 STATSD_EXPORTER_PROMETHEUS_SERVICE_HOST 는 비어 있었다. MediaWiki 는 통계를 Graphite 로도 보내고 있어 statsd 주소가 없는 것은 영향이 없었다. 하지만 Memcached 에 닿지 못한 MediaWiki 는 평소 캐시에 있던 객체를 데이터베이스에 물었다. 위키 페이지 리비전의 압축된 본문을 저장하는 External Storage 클러스터의 부하와 연결 수가 크게 늘었고, 그 결과 세 배포에서 지연과 데이터베이스 오류가 났다.\n\nchannel: memcached 오류 이벤트가 분당 약 200만 건 쏟아져 logstash 가 따라가지 못했다. 그 때문에 MediaWikiMemcachedHighErrorRate 알림이 빠지고 Grafana 그래프는 데이터가 없다고 나왔다. 3월 12일 팀은 cirrus-streaming-updater 의 SUP 병렬도와 categoryMembershipChange 작업의 동시성을 낮췄고, 13:44 에 두 패치를 되돌려 Scap 을 다시 돌렸다. 3월 13일 11:15 에 같은 롤아웃을 다시 배포하자 장애가 되풀이됐다. 이번에는 changeprop-jobqueue 와 mw-api-int 를 롤링 재시작하고 categorymembership 작업을 껐다. 그다음 패치를 되돌린 뒤 세 배포를 다시 배포하고 mw-api-int 의 레플리카를 늘렸다. 사용자에게 보이는 영향은 작았다. 전체 트래픽 오류율은 약 0.03~0.04% 였고, 편집은 아주 짧은 동안 영향을 받았으며, 웹과 외부 API 의 p50, p75 지연은 그대로였다."},"trigger":{"en":"At 10:44 UTC on 12 March a Scap deployment of the PHP 8.1 rollout for mw-api-int, mw-parsoid and mw-jobrunner was run with the flag -Dbuild_mw_container_image:False; the rollout was deployed with Scap again at 11:15 UTC on 13 March.","ko":"mw-api-int, mw-parsoid, mw-jobrunner 를 PHP 8.1 로 옮기는 예정된 Scap 배포가 장애를 일으켰다. 3월 12일 10:44 UTC 에 1126607[puppet] 과 1126650 [deployment-charts] 를, 3월 13일 11:15 UTC 에 1127476[puppet] 과 1127478[deployment-charts] 를 배포했다."},"severity":"unknown","symptoms":["database-overloaded","high-latency","high-error-rate","log-flood","monitoring-down"],"timeline":{"detected_after":"14m","resolved_after":"3h"},"date_kind":"incident","detection":{"en":"Alerts for high backend response times started at 10:58 UTC on 12 March, 14 minutes after the Scap deployment; 2 on-call engineers were paged. The MediaWikiMemcachedHighErrorRate alert that pointed at the cause fired only briefly because its log pipeline was overloaded.","ko":"3월 12일 10:58 UTC 에 백엔드 응답 시간이 높다는 알림이 들어오기 시작했다. 처음 세운 가설은 특정 네트워크나 클라이언트의 많은 트래픽, 잘못 실행된 예약 작업, 자원을 많이 먹는 changeprop-jobqueue 작업이었다. 원인을 가리켰을 MediaWikiMemcachedHighErrorRate 알림은 logstash-prometheus 파이프라인이 밀려 빠지거나 곧바로 회복됐다. 장애 중에는 알림 78개가 울리고 있어서, 그 알림이 울렸더라도 부수 증상으로 보고 넘겼을 가능성이 컸다."},"components":["database","cache","configmap","helm","ci-system"],"root_cause":"deploy-rollout","remediation":[{"text":{"en":"Deployed a patch to cirrus-streaming-updater that reduced SUP parallelism","ko":"3월 12일 13:19 UTC 에 cirrus-streaming-updater 의 SUP 병렬도를 낮추는 패치 1126988[deployment-charts] 를 배포했다."}},{"text":{"en":"Reduced the concurrency of the categoryMembershipChange job in changeprop-jobqueue","ko":"13:33 에 1127000[deployment-charts] 로 changeprop-jobqueue 의 categoryMembershipChange 작업 동시성을 낮췄다."}},{"text":{"en":"On 12 March, reverted 1126607 and 1126650 and re-ran Scap","ko":"13:44 에 1126607[puppet] 과 1126650 [deployment-charts] 를 되돌리고 Scap 을 다시 돌렸다."}},{"text":{"en":"On 13 March, rolling-restarted changeprop-jobqueue","ko":"3월 13일 11:34 UTC 에 changeprop-jobqueue 를 롤링 재시작했다."}},{"text":{"en":"Rolling-restarted mw-api-int","ko":"11:43 에 mw-api-int 를 롤링 재시작했다."}},{"text":{"en":"Disabled the categorymembership job in changeprop-jobqueue","ko":"11:44 에 1127500[deployment-charts] 로 changeprop-jobqueue 의 categorymembership 작업을 껐다."}},{"text":{"en":"Reverted 1127476 and 1127478","ko":"11:46 에 1127476[puppet] 과 1127478[deployment-charts] 를 되돌렸다."}},{"text":{"en":"Redeployed mw-api-int to pick up the revert","ko":"11:58 에 되돌린 내용을 반영하려고 mw-api-int 를 다시 배포했다."}},{"text":{"en":"Increased the number of mw-api-int replicas","ko":"12:07 에 mw-api-int 의 레플리카 수를 늘렸다."}},{"text":{"en":"Redeployed mw-jobrunner to pick up the revert","ko":"12:28 에 되돌린 내용을 반영하려고 mw-jobrunner 를 다시 배포했다."}},{"text":{"en":"Redeployed mw-parsoid to pick up the revert","ko":"14:06 에 되돌린 내용을 반영하려고 mw-parsoid 를 다시 배포했다."}}],"action_items":[{"text":{"en":"Reduce the number of messages sent through channel:Memcached during failures","ko":"장애 때 channel:Memcached 로 보내는 메시지 양을 줄인다."}},{"text":{"en":"Add helm rollback functionality to Scap","ko":"Scap 에 helm 롤백 기능을 넣는다."}},{"text":{"en":"Decide whether Scap should update helmfile-defaults when -Dbuild_mw_container_image:False is set","ko":"-Dbuild_mw_container_image:False 로 실행할 때도 Scap 이 helmfile-defaults 를 갱신해야 하는지 검토한다."}},{"text":{"en":"Consider removing envvars.inc from MediaWiki images","ko":"MediaWiki 이미지에서 envvars.inc 를 뺄지 검토한다."}},{"text":{"en":"Consider \"Why\" dashboards that give an overview of a group of related components together with relevant links to follow","ko":"서로 관련된 구성 요소를 한꺼번에 보여 주고 따라갈 링크를 담은 \"Why\" 대시보드를 만들지 검토한다."}},{"text":{"en":"Coordinate incident responders better","ko":"장애 대응 조율을 개선한다. 누가 무엇을 맡았는지, 누가 대응에 들어오고 나갔는지 장애 조율자가 알 수 있어야 한다. 또 맥락 없는 의견과 대응과 관계없는 논의로 채널이 시끄러워지지 않아야 한다."}},{"text":{"en":"Be able to work with MediaWiki developers during incidents","ko":"장애 중에 MediaWiki 개발자와 연락해 개발자의 관점을 바로 들을 수 있게 한다."}}],"contributing":["assumption-unverified","warm-cache-dependency","alert-noise"],"recurrence_of":null,"symptoms_text":{"en":"On 2025-03-12 from 10:58 UTC, alerts for high backend response times came in, and the MediaWiki deployments mw-api-int, mw-parsoid and mw-jobrunner, which serve MediaWiki for non-user-facing purposes, showed high latency and database errors. The External Storage cluster, whose hosts store the compressed text of wiki page revisions, had a sharp rise in load and connections. The same happened again on 2025-03-13 from 11:15 UTC.\n\nMost MediaWiki errors in channel: error at the time were database related. External Storage queries per second and connection counts jumped, and circuit breakers kept the databases from collapsing; the core databases followed a similar pattern. WANObjectCache and the SQLBlobStore keygroup showed a high rate of miss.compute keys, while Memcached and mcrouter traffic dropped sharply. User-facing traffic was barely affected: global traffic had an error rate of about 0.03-0.04%, edits were affected for a very short time, and p50 and p75 latency of mw-web and the External API did not change.\n\nThe signal that pointed at the cause was hard to see. The deployments produced about 2mil/min memcached error events in channel: memcached, more than logstash could process, so the channel:memcached logstash dashboard probably could not render until logstash worked through its backlog after the incident. Memcached errors collected by Prometheus spiked at 50k/min, but delays in the mw→logstash→prometheus-es-exporter→prometheus pipeline kept the MediaWikiMemcachedHighErrorRate alert from staying up: it went off and recovered quickly, and the matching Grafana graph showed no data during the incidents. 78 alerts were firing during the incident, and the team thought it likely that even a firing MediaWikiMemcachedHighErrorRate would have been taken for a side effect.\n\nResponders first worked through the usual suspects for MediaWiki latency and database load: high traffic from a specific network or client, scheduled jobs gone wrong, and greedy changeprop-jobqueue jobs. On 12 March they reduced SUP parallelism in cirrus-streaming-updater and the concurrency of the categoryMembershipChange job, and assumed the Scap rollout was not the problem. By 13 March they were certain the Scap rollout was the cause, but it took until after the incident to understand how.","ko":"2025-03-12 10:58 UTC 부터 백엔드 응답 시간이 높다는 알림이 들어왔다. 3월 12일과 13일 이틀 동안 mw-api-int, mw-parsoid, mw-jobrunner 에서 지연이 커지고 데이터베이스 오류가 늘었다. External Storage 클러스터의 부하와 연결 수도 크게 늘었다.\n\n그때 channel: error 에 찍힌 MediaWiki 오류는 대부분 데이터베이스 오류였다. External Storage 의 QPS 와 연결 수가 급격히 늘었고 서킷 브레이커가 데이터베이스가 무너지는 것을 막았다. 코어 데이터베이스에서도 비슷한 추이가 보였다. 사용자 대상 배포인 mw-web 과 mw-api-ext 는 거의 영향을 받지 않았고, 전체 트래픽의 오류율은 약 0.03~0.04% 였다.\n\nMemcached 트래픽과 mcrouter 요청이 급격히 떨어졌다. WANObjectCache 와 SQLBlobStore 키 그룹에서 miss.compute 키 비율이 높았다. 이 키 그룹의 메타데이터는 Memcached 에 있는데, 세 배포가 Memcached 에 닿지 못했기 때문이다.\n\nchannel: memcached 오류 이벤트가 분당 약 200만 건 쏟아져 logstash 가 제때 처리하지 못했다. channel:memcached 대시보드는 장애가 끝나 logstash 가 밀린 이벤트를 다 처리할 때까지 그려지지 않았던 것으로 보였다. mw→logstash→prometheus-es-exporter→prometheus 파이프라인이 밀려 MediaWikiMemcachedHighErrorRate 알림이 빠졌다. 해당 Grafana 그래프는 장애 중 데이터가 없다고 나왔다. Prometheus 가 모은 Memcached 오류는 분당 5만 건까지 올랐지만 알림은 울렸다가 곧 회복됐다.\n\n처음에는 특정 네트워크나 클라이언트의 많은 트래픽, 잘못 실행된 예약 작업, 자원을 많이 먹는 changeprop-jobqueue 작업을 의심했다. MediaWiki 지연과 데이터베이스 부하가 보일 때 늘 의심하던 것들이었다. 3월 12일에는 Scap 배포가 문제가 아니라고 봤다. 3월 13일에야 Scap 배포가 원인이라고 확신했지만, 그 배포가 어떻게 장애로 이어졌는지는 이해하기 어려웠다. 장애 중에 알림 78개가 울리고 있었다."},"updated_at":"2026-11-14T12:33:57.733860Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0324","org_slug":"wikimedia-foundation"}