{"id":"IL-0462","org":"Shopify","date":"2021-10-21","docs":[],"title":{"en":"Some MySQL pods on Shopify's GKE clusters took up to 30 minutes to start, slowing instance replacement","ko":"GKE 위 MySQL 파드 일부가 시작과 종료에 오래 걸려 KateSQL 인스턴스 교체가 느려졌다"},"domain":["kubernetes","gcp","database"],"impact":{"scope":"KateSQL instance replacement slowed: MySQL pod creation took 10 to 30 minutes, and affected pods also served slower queries","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The investigation was one of the longest the Database Platform group had carried out, because of the nature of the problem, how hard it was to reproduce and the lack of an established method to follow; once the cause was known, there were several ways to fix it.","ko":"이번 조사는 Shopify 데이터베이스 플랫폼 그룹이 한 조사 가운데 가장 오래 걸린 축에 들었다. 문제의 성격, 재현의 어려움, 따를 만한 정해진 방법이 전혀 없었다는 점 때문에 근본 원인을 찾는 데 오래 걸렸다. 그래도 문제를 풀 방법은 여러 가지가 있었다."},"related":[],"sources":[{"url":"https://shopify.engineering/debugging-systems-cloud-mysql-kubernetes-cgroups","kind":"blog","archived":"https://web.archive.org/web/20260606203935/https://shopify.engineering/debugging-systems-cloud-mysql-kubernetes-cgroups"}],"summary":{"en":"KateSQL runs MySQL as Kubernetes pods on GKE across several Google Cloud regions. Earlier in 2021 some MySQL pods began taking far longer than others to start and stop, slowing the instance replacement procedure. As a first mitigation the team cordoned older nodes, replaced MySQL instances so their pods moved to new nodes added by the GKE autoscaler, and drained the old nodes, which improved performance on the new pods.\n\nA deeper analysis traced the slowness to memory allocation during InnoDB buffer pool initialisation, which spent its time in the kernel's mem_cgroup_commit_charge. Affected nodes had about 50K memory cgroups, which pointed to a cgroup leak: charging each page may have to traverse many cgroups. Bug reports, an lwn.net article and a Linux kernel mailing list thread matched the problem, and Google's kernel engineers confirmed it was another side effect of reparenting slab memory on cgroup removal in the Linux kernel memory cgroup controller. Writing 1 to /sys/fs/cgroup/memory/kubepods/memory.force_empty on an affected node brought memory cgroups down to about 1800, and a restarted MySQL pod then initialised an 80G InnoDB buffer pool in five seconds.\n\nShopify and Google weighed several workarounds: rebooting or cordoning nodes found by monitoring /proc/cgroups, a cron job that drops slab and page caches, moving short-lived pods to a dedicated node pool, a memory.force_empty call in the preStop hook of short-lived pods, and upgrading to COS 85, which has the upstream fixes, with the move from GKE 1.16 to 1.18 expected to bring Linux kernel 5.4 with the relevant fixes. Since a GKE upgrade was due, the team created new GKE 1.18 clusters and started creating MySQL pods there, and after several weeks buffer pool initialisation and query performance stayed consistent.","ko":"KateSQL 은 Shopify 가 GKE 위에 직접 만든 데이터베이스 서비스 플랫폼이고, 여러 Google Cloud 리전과 많은 GKE 클러스터에서 운영 MySQL 인스턴스 수백 개를 관리한다. 2021년에 인스턴스 교체 작업에서 일부 MySQL 파드의 시작과 종료가 같은 데이터를 가진 다른 파드보다 훨씬 느리다는 것을 알았다. 인스턴스 교체는 새 MySQL 레플리카 파드를 만들고 옛 파드를 멈추는 일을 모든 인스턴스가 바뀔 때까지 되풀이하는 절차다. 오래 돈 노드보다 새 노드가 빨라서, 우선 옛 노드를 cordon 하고 인스턴스 교체로 MySQL 파드를 새 노드로 옮긴 뒤 옛 노드를 drain 해 줄였다.\n\n깊이 조사해 보니 InnoDB 버퍼 풀 초기화 중 메모리 할당이 커널의 mem_cgroup_commit_charge 에서 시간을 썼다. 정상 노드와 문제 노드의 메모리 cgroup 수를 비교하자 문제 노드에는 약 50,000개가 있었다. 팀은 cgroup 누수를 의심했다. 페이지를 charge 할 때마다 많은 cgroup 을 훑어야 한다면 성능이 떨어질 수 있었다. Google 의 리눅스 커널 엔지니어는 cgroup 을 지울 때 slab 메모리를 상위로 넘기는 동작의 또 다른 부작용이라고 확인했다. 근본 원인은 리눅스 커널 메모리 cgroup 컨트롤러의 버그였다.\n\nkubepods cgroup 에 memory.force_empty 를 써 넣자 메모리 cgroup 수가 정상 노드 수준인 약 1800개로 빠르게 줄었고, 그 노드에서 MySQL 파드를 재시작하자 80G InnoDB 버퍼 풀이 5초 만에 초기화됐다. Google 엔지니어와 함께 여러 우회책과 수정을 검토했다. 마침 GKE 를 업그레이드할 시기여서 GKE 1.18 로 새 클러스터를 만들고 새 MySQL 파드를 그 클러스터에 띄웠다. GKE 1.16 에서 1.18 로 올리면 관련 수정이 들어간 리눅스 커널 5.4 를 받는다. 몇 주 동안 InnoDB 버퍼 풀 초기화 시간과 질의 성능이 고르게 유지됐다."},"trigger":null,"severity":"unknown","symptoms":["slow-pod-startup","high-latency"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"KateSQL's instance replacement operations showed inconsistent MySQL pod creation times, and the team then measured the mysql-init container and MySQL startup times across pods.","ko":"KateSQL 인스턴스 교체 작업에서 MySQL 파드 생성 시간이 10분에서 30분까지 들쭉날쭉한 것을 보고 알았다."},"components":["linux-kernel","node-image","database"],"root_cause":"software-bug","remediation":[{"text":{"en":"Cordoned older nodes, moved MySQL pods to new nodes through instance replacement and drained the old nodes","ko":"옛 노드를 cordon 하고, KateSQL 인스턴스 교체로 MySQL 파드를 새 노드로 옮기며 GKE 오토스케일러가 노드를 늘리게 한 뒤, 옛 노드를 drain 해 클러스터를 줄였다. 새 MySQL 파드의 성능이 좋아졌다."}},{"text":{"en":"Forced the kubepods memory cgroup to empty its dead cgroups on an affected node to test the fix","ko":"`echo 1 | sudo tee /sys/fs/cgroup/memory/kubepods/memory.force_empty` 로 kubepods cgroup 이 죽은 cgroup 을 비우게 해 메모리 cgroup 수를 약 1800개로 줄였다. 재시작한 MySQL 파드는 80G 버퍼 풀을 5초 만에 초기화했다."}},{"text":{"en":"Created new GKE 1.18 clusters and started creating MySQL pods on them","ko":"GKE 1.18 로 새 GKE 클러스터를 만들어 새 MySQL 파드를 그 클러스터에 띄웠다. 몇 주 동안 버퍼 풀 초기화 시간과 질의 성능이 고르게 유지됐다."}}],"action_items":[],"contributing":["known-bug-unpatched"],"recurrence_of":null,"symptoms_text":{"en":"In 2021 Shopify's KateSQL, a database-as-a-service platform that runs several hundred production MySQL instances on GKE, found that some MySQL pods started and shut down much more slowly than similar pods with the same data set. Instance replacement, the procedure that creates a new MySQL replica pod and stops the old one, showed pod creation times ranging from 10 to 30 minutes, which held up maintenance such as configuration changes and upgrades.\n\nTwo pods with the same CPU, memory, storage and data set differed widely: katesql-n4sx0 took 2120 seconds to initialise and 1104 seconds to start, while katesql-jxijq took 74 and 17 seconds. The slow pods also became gradually slower at queries, with more slow queries among those that used temporary memory tables.\n\nNodes that had been running for a few months performed worse than newly provisioned ones, although kubelet, kernel and Container-Optimized OS versions were the same. Storage was ruled out. During InnoDB buffer pool initialisation mysqld used about 100% of one CPU, strace showed each mmap() call taking around 100 ms to allocate chunks of about 128MB, and a perf flame graph showed the jemalloc allocator spending most of its time in the kernel function mem_cgroup_commit_charge.\n\nslabtop showed a dentry cache of around 20GB on a node with about 64 pods, and dropping it with echo 2 > /proc/sys/vm/drop_caches brought an immediate improvement like a node restart, although the team confirmed that its case was not the one described in a sysdig post it had followed. An affected node had approximately 50K memory cgroups, far more than a good node.","ko":"KateSQL 의 인스턴스 교체 작업에서 MySQL 파드 생성 시간이 10분에서 30분까지 들쭉날쭉했다. 일부 MySQL 파드는 데이터가 같은 다른 파드보다 시작과 종료가 훨씬 느렸고, 설정 변경이나 업그레이드 같은 유지 보수 때 MySQL 인스턴스를 빨리 교체하기 어려워졌다.\n\nCPU, 메모리, 스토리지와 데이터가 똑같은 두 파드를 비교하자 katesql-n4sx0 은 초기화에 2120초, 시작에 1104초가 걸렸고 katesql-jxijq 는 초기화 74초, 시작 17초였다. MySQL 오류 로그에 찍힌 InnoDB 버퍼 풀 초기화 타임스탬프로 어느 단계가 느린지 알 수 있었다.\n\n생성이 느린 파드는 시간이 지나며 성능도 서서히 떨어졌다. 임시 메모리 테이블을 쓰는 질의에서 슬로 쿼리가 늘었다.\n\n느린 mysqld 를 strace 로 보면 mmap() 호출 하나가 약 128MB 를 잡는 데 약 100 ms 가 걸렸다. top 으로 보면 버퍼 풀 초기화는 단일 스레드로 CPU 를 약 100% 썼다. perf 플레임그래프에서는 메모리 할당기 jemalloc 이 커널 함수 mem_cgroup_commit_charge 에서 대부분의 시간을 보냈다. slabtop 으로 보면 노드 하나에 파드 약 64개가 도는 상태에서 dentry 캐시가 20GB 안팎으로 비정상적으로 컸다.\n\n빠른 점검에서 새로 만든 노드가 몇 달째 돌던 노드보다 성능이 좋았다. kubelet, 리눅스 커널, Container-optimized OS 버전은 같았고 다른 것은 가동 시간뿐이었다. 스토리지 하위 시스템은 원인에서 뺐다. 커진 dentry 캐시를 의심해 sysdig 의 글을 따라 확인했지만 그 글의 경우와는 달랐다. 그래도 dentry 캐시를 비우자 노드를 재시작한 것처럼 성능이 곧바로 좋아졌다."},"updated_at":"2026-11-14T12:34:11.028875Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0462","org_slug":"shopify"}