{"id":"IL-0578","org":"GMO Pepabo","date":"2020-06-26","docs":["https://github.com/torvalds/linux/blob/master/Documentation/networking/ip-sysctl.txt","http://man7.org/linux/man-pages/man7/tcp.7.html"],"title":{"en":"Static content behind an HAProxy load balancer in an OpenStack Octavia amphora was delayed by tens of seconds","ko":"HAProxy 로드 밸런서를 거치는 정적 콘텐츠 전송이 수십 초씩 늦어졌다"},"domain":["networking"],"impact":{"scope":"Delivery of static content through the HAProxy load balancer was delayed by tens of seconds","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"On a VM with little RAM, the boot-time default of net.ipv4.tcp_mem can be low enough to push TCP into memory pressure. TCP out of memory is not the OOM killer, and net.ipv4.tcp_mem is a system-wide limit counted in pages, unlike the per-socket net.ipv4.tcp_rmem and net.ipv4.tcp_wmem counted in bytes. The values to watch are TCP mem in /proc/net/sockstat, press in /proc/net/protocols, and TCPMemoryPressures, TCPMemoryPressuresChrono and TCPAbortOnMemory in /proc/net/netstat.","ko":"net.ipv4.tcp_mem 의 기본값은 부팅 때 RAM 크기로 정해지므로 RAM 이 작은 VM 에서는 한계가 낮게 잡힌다. tcp_mem 은 시스템 전체의 TCP 소켓에 걸리는 페이지 단위의 값이고, 소켓 하나의 버퍼 크기를 바이트 단위로 정하는 net.ipv4.tcp_rmem, net.ipv4.tcp_wmem 과는 다른 설정이다. TCP: out of memory 로그가 가리키는 TCP oom 은 프로세스를 SIGKILL 로 끝내는 OOM Killer 와 관계가 없다. TCP oom 로그가 나왔을 때는 송수신 제한도 함께 일어나고 있다. 문제를 찾거나 감시할 때는 /proc/net/sockstat 의 TCP mem, /proc/net/protocols 의 press, /proc/net/netstat 의 TCPMemoryPressures, TCPMemoryPressuresChrono, TCPAbortOnMemory, OfoPruned, PruneCalled, TCPRcvQDrop, TCPAbortFailed 를 본다."},"related":[],"sources":[{"url":"https://tech.pepabo.com/2020/06/26/kernel-dive-tcp-mem/","kind":"blog","archived":"https://web.archive.org/web/20260616123325/https://tech.pepabo.com/2020/06/26/kernel-dive-tcp-mem/"}],"summary":{"en":"HAProxy ran in a VM inside an amphora of the OpenStack Octavia service, and the VM had only 2 GB of RAM. The Linux kernel sets the default net.ipv4.tcp_mem at boot from the amount of memory, so the TCP memory thresholds on this VM were low. The memory the kernel had allocated to TCP socket buffers went over these thresholds, the kernel entered TCP memory pressure mode and logged TCP out of memory, and performance dropped; the later investigation concluded that this caused the delays. An engineer tuned net.ipv4.tcp_mem with sysctl, which applies immediately to all sockets without restarting processes, and the delays stopped.\n\nThe investigation could not explain at first how this state delayed the content, so the author reproduced it in a Vagrant lab and read the kernel source. Above the pressure threshold the kernel limits send and receive buffer sizes and shrinks receive windows. Above the hard limit, the third value of net.ipv4.tcp_mem, it also drops newly received segments, blocks senders until memory is available, fails connect(2) with ENOBUFS, and resets sockets that are closed with data still in the send buffer, which is when it logs TCP out of memory.","ko":"GMO Pepabo 의 운영 환경에서는 haproxy 가 HTTP 로드 밸런서로 돌고, 그 뒤의 upstream 호스트에서 nginx 가 정적 콘텐츠를 제공했다. 어느 날 정적 콘텐츠 전송이 수십 초 단위로 늦어졌다. 장애 대응 중 동료 엔지니어가 haproxy 호스트의 dmesg 에서 TCP: out of memory -- consider tuning tcp_mem 로그를 찾았다. 글쓴이는 이 로그가 가리키는 상태를 TCP oom 이라고 줄여 불렀다. 동료 엔지니어는 이 로그를 실마리로 해결책을 조사해 sysctl net.ipv4.tcp_mem 을 조정했다. tcp_mem 값은 실행 중에 바꾸면 프로세스를 재시작하거나 소켓을 다시 연결하지 않아도 시스템의 모든 TCP 소켓에 곧바로 반영된다. 이 조정으로 지연이 가라앉으며 장애가 복구됐다.\n\n이후 조사에서 haproxy 를 돌리던 VM 의 RAM 이 2GB 로 작았다는 것이 드러났다. 이 VM 은 OpenStack Octavia 서비스의 Amphora 안에서 돌았다. net.ipv4.tcp_mem 의 기본값은 부팅 때 탑재된 RAM 크기에 따라 자동으로 정해지므로 이 VM 에서는 낮게 잡혔다. 그 때문에 TCP 메모리 사용량이 한계를 넘어 TCP 가 memory pressure 모드에 들어가고 TCP oom 이 났다는 결론이 나왔다. 다만 정적 콘텐츠 전송이 어떤 원리로 늦어졌는지는 이때 설명하지 못했다. 그래서 글쓴이인 보안 대책실 엔지니어가 Vagrant 로 TCP oom 을 재현하고 커널 소스를 읽으며 원리를 조사했다.\n\n조사 결과, TCP 메모리 사용량이 tcp_mem 의 두 번째 값을 넘으면 커널은 memory pressure 모드에 들어가 송수신 버퍼 크기와 수신 윈도를 줄인다. 세 번째 값을 넘으면 커널은 새로 받은 세그먼트를 버리고, 메모리를 얻을 때까지 송신 프로세스를 막아 둔다. 송신 버퍼에 데이터가 남은 소켓을 닫을 때는 RST 를 보내고 TCP oom 로그를 남긴다. 글쓴이는 RAM 이 작아 tcp_mem 기본값이 낮게 잡혔고, 그 값 때문에 memory pressure 와 TCP oom 이 일어나 성능이 떨어졌다고 분석했다."},"trigger":null,"severity":"unknown","symptoms":["high-latency"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":null,"components":["haproxy","linux-kernel","cloud-load-balancer"],"root_cause":"capacity","remediation":[{"text":{"en":"Tuned net.ipv4.tcp_mem with sysctl on the HAProxy host, which ended the delays immediately","ko":"동료 엔지니어가 sysctl net.ipv4.tcp_mem 을 조정해 정적 콘텐츠 전송 지연을 가라앉히고 장애를 복구했다."}}],"action_items":[],"contributing":["default-config-unchanged"],"recurrence_of":null,"symptoms_text":{"en":"In a GMO Pepabo production setup, delivery of static content was delayed by tens of seconds. The static content was served by nginx on upstream hosts behind HAProxy, which ran as the HTTP load balancer.\n\nDuring the response, an engineer found a host whose dmesg showed TCP: out of memory -- consider tuning tcp_mem. That host was the one running HAProxy.","ko":"운영 환경에서 정적 콘텐츠 전송이 수십 초 단위로 늦어졌다.\n\n장애 대응 중 동료 엔지니어가 HTTP 로드 밸런서로 haproxy 를 돌리는 호스트의 dmesg 에서 TCP: out of memory -- consider tuning tcp_mem 로그를 찾았다. haproxy 뒤의 upstream 호스트에서는 nginx 가 정적 콘텐츠를 제공했다."},"updated_at":"2026-11-14T12:34:22.423159Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0578","org_slug":"gmo-pepabo"}