{"id":"IL-0177","org":"LINE+","date":"2022-10-14","docs":[],"title":{"en":"WebSocket connections to LINE's messaging-hub on Kubernetes dropped all at once whenever another service's change reloaded a shared HAProxy L7 load balancer, and the reconnect wave overloaded a token API and filled nf_conntrack on Nginx","ko":"다른 팀의 설정 배포로 공유 L7 HAProxy 가 재로드될 때마다 messaging-hub 의 웹소켓 연결이 한꺼번에 끊겼다."},"domain":["kubernetes","networking"],"impact":{"scope":"WebSocket clients of messaging-hub (Demaecan Merchant and Driver apps); messages fell back to FCM, but the token request surge delayed the Merchant system and affected Merchant users","users":"unknown","region":"Japan"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"A reload of a shared load balancer by a different team can cut long-lived connections, and with hard-stop-after the cut comes 30 minutes later, far from the change that caused it. The author's lesson is not to narrow the scope of the search too early: trusting that the infrastructure was fine kept the investigation inside the team's own systems for days.\n\nA reconnect wave from many WebSocket clients can overload the systems that issue connection tokens and fill nf_conntrack on proxy nodes. Scaling Nginx out only helps for conntrack if the pods land on different nodes, and anti-affinity leaves extra replicas Pending once they outnumber the nodes. That messaging-hub problems can still load the connected systems remains an open problem.","ko":"문제의 범위를 미리 단정해 스스로 제한하면 안 된다. 인프라 팀과 네트워크 팀이 문제없다고 답했다는 이유로 직접 다룰 수 있는 영역만 뒤지다가 공유 L7 LB 를 늦게 의심했다. LB 같은 기술 리소스를 다른 서비스와 공유하면 다른 팀의 배포가 내 서비스의 연결을 끊을 수 있다. Nginx 를 늘려 NF_CONNTRACK 부담을 나눌 때는 파드가 서로 다른 노드에 뜨게 해야 하고, 노드 수보다 많이 늘리면 Pending 상태로 남는 파드가 생긴다는 점도 알아야 한다."},"related":[],"sources":[{"url":"https://engineering.linecorp.com/ko/blog/messaing-hub-troubleshooting","kind":"blog","license":"unknown","archived":null}],"summary":{"en":"messaging-hub keeps WebSocket connections to the Demaecan Merchant and Driver apps, behind an L7 load balancer from LINE's internal IaaS, Verda, followed by Ingress Nginx on Kubernetes. A colleague with SRE experience set up HAProxy, the software behind the L7 load balancer, in local Docker and sent it a HUP signal with \"docker-compose kill -s HUP haproxy\". After a delay the existing connections were closed: when HAProxy reloads or restarts, the old process ends after the hard-stop-after time and takes its open connections with it. The infrastructure team confirmed hard-stop-after was 30 minutes, and deploying the team's load balancer in the Verda dashboard reproduced it in production: the HAProxy uptime reset exactly 30 minutes later and the WebSocket drop happened at the same moment.\n\nThe team rarely changed its own load balancer, but the HAProxy was shared with other services, so any of them deploying its own load balancer configuration reloaded HAProxy and, 30 minutes later, cut messaging-hub's connections. That explains why the drops were irregular. Because the Verda dashboard is split by project, the team had not imagined the load balancer was shared, and had trusted the infrastructure team's answers and limited the search to systems it controlled.\n\nBefore the cause was found, the reconnect waves were contained in two places. The Merchant system moved the token issuance API into a separate deployment so token traffic would not slow other Merchant APIs. Nginx was scaled out instead of changing the conntrack limit or unloading the module, with pod anti-affinity and a label selector so each Nginx pod ran on a different node, since nf_conntrack is per node; after that, further drops no longer caused Nginx packet loss. The drops themselves ended when messaging-hub moved from the L7 load balancer to an L4 load balancer.","ko":"messaging-hub 는 LINE+ ABC Studio 가 만든 범용 메시징 플랫폼으로, Demaecan 의 Merchant 앱과 Driver 앱에 웹소켓으로 서버 푸시를 보낸다. 클라이언트 연결은 L7 로드밸런서와 Ingress Nginx 를 거쳐 웹소켓 서버인 connection-manager 로 전달된다. 어느 날부터 웹소켓 연결이 불규칙하게 한꺼번에 끊기기 시작했다.\n\n끊긴 동안의 메시지는 FCM 으로 넘어갔지만, 클라이언트들이 재연결하면서 커넥션 토큰 요청이 Merchant 시스템으로 몰렸다. Merchant 시스템은 토큰 발급 말고도 다른 시스템 연동과 자체 비즈니스 로직을 맡고 있어 지연이 번졌고, 결국 Merchant 사용자가 영향을 받았다. 스트레스 테스트로 Merchant 시스템의 DB 가 병목이라는 것을 찾았고, Merchant 팀이 토큰 발급 API 를 따로 떼어 배포했다. 재연결이 몰릴 때 Nginx 노드의 NF_CONNTRACK 테이블이 차서 패킷이 버려지는 문제도 나왔다. 팀은 운영체제 설정을 바꾸는 대신 Nginx 를 늘렸다. NF_CONNTRACK 는 VM 마다 하나씩이므로 파드 안티 어피니티로 Nginx 파드가 서로 다른 노드에 뜨게 했다. 그 뒤로는 웹소켓 드롭이 다시 일어나도 Nginx 패킷 드롭은 생기지 않았다.\n\n원래 문제인 웹소켓 드롭은 connection-manager, 소켓 수를 정하는 설정, 클라이언트 종류, 통신사, Nginx 타임아웃을 차례로 봐도 풀리지 않았다. SRE 경험이 많은 다른 팀 동료가 L7 에 쓰이는 HAProxy 를 로컬에 띄워 시험하면서 원인을 찾았다. docker-compose kill -s HUP haproxy 로 HAProxy 를 재로드하면 hard-stop-after 설정 시간이 지난 뒤 이전 프로세스가 종료되면서 붙어 있던 웹소켓 연결이 끊겼다. 인프라 팀에 물으니 hard-stop-after 는 30분이었다. 사내 IaaS 인 Verda 대시보드에서 LB 를 배포하자 정확히 30분 뒤 HAProxy uptime 이 갱신되면서 웹소켓 드롭이 재현됐다.\n\n팀은 자기들이 LB 를 거의 바꾸지 않는데도 HAProxy 가 재시작되는 이유도 찾았다. LB 를 다른 서비스와 공유하고 있어서, 다른 서비스 담당자가 자기 LB 설정을 배포하기만 해도 HAProxy 가 재시작됐다. 드롭이 불규칙했던 이유가 이것이었다. Verda 대시보드에서는 LB 를 프로젝트별로 설정하기 때문에 다른 서비스와 공유하고 있다는 생각을 하지 못했고, 이것도 원인 파악을 늦췄다. L7 LB 의 이 운영상 한계는 피할 수 없었기 때문에 팀은 L4 LB 로 바꿨고, 그 뒤 웹소켓 드롭은 사라졌다."},"trigger":{"en":"Another service that shared the same L7 load balancer deployed its own load balancer configuration in the Verda dashboard, which reloaded HAProxy; 30 minutes later, at the hard-stop-after limit, the old HAProxy process exited and closed every WebSocket connection it still held.","ko":"같은 L7 로드밸런서를 공유하는 다른 서비스 담당자가 자기 LB 설정을 배포하면 HAProxy 가 재로드됐다. hard-stop-after 로 정한 30분이 지나 이전 프로세스가 종료되면서 거기 붙어 있던 웹소켓 연결이 모두 끊겼다."},"severity":"unknown","symptoms":["connection-resets","database-overloaded","intermittent-connection-failures","high-latency"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"published","detection":{"en":"The developer noticed deep, sudden drops in the WebSocket connection graph that were not tied to peak traffic and kept recurring irregularly. The Nginx packet drops were found through network monitoring.","ko":"웹소켓 연결 수 그래프에서 최대 트래픽 시간이 아닌데도 연결 수가 순간적으로 뚝 떨어지는 것을 보고 알았다. Nginx 패킷 드롭은 네트워크 모니터링으로 원인을 찾았다."},"components":["haproxy","cloud-load-balancer","conntrack","ingress"],"root_cause":"config-change","remediation":[{"text":{"en":"separated the Merchant system's token issuance API into its own deployment so reconnect traffic did not delay other Merchant APIs","ko":"Merchant 팀이 토큰 발급 API 를 따로 떼어 배포해 토큰 요청이 몰려도 다른 Merchant API 가 지연되지 않게 했다."}},{"text":{"en":"scaled out Nginx with pod anti-affinity and a label selector so each pod ran on a different node, which stopped the nf_conntrack packet drops","ko":"Nginx 를 늘리고 파드 안티 어피니티와 레이블 셀렉터로 Nginx 파드가 서로 다른 노드에 뜨게 해서 NF_CONNTRACK 테이블 포화로 생기던 패킷 드롭을 없앴다."}},{"text":{"en":"raised Ingress Nginx proxy-read-timeout and proxy-send-timeout and the load balancer timeout to 3600s, which did not stop the drops","ko":"Ingress Nginx 의 proxy-read-timeout 과 proxy-send-timeout 을 300s 에서 3600s 로, LB timeout 도 3600s 로 올렸지만 웹소켓 드롭은 멈추지 않았다."}},{"text":{"en":"moved messaging-hub from the shared L7 load balancer to an L4 load balancer, which ended the WebSocket drops","ko":"공유하던 L7 LB 를 L4 LB 로 바꿔 웹소켓 드롭을 없앴다."}}],"action_items":[],"contributing":["delayed-effect-change","blast-radius-too-wide","retry-storm","assumption-unverified"],"recurrence_of":null,"symptoms_text":{"en":"The graph of WebSocket connections to messaging-hub, the messaging platform used by the Japanese food delivery service Demaecan, showed sudden deep drops even outside peak traffic. The drops kept coming back at irregular intervals. Messages still reached clients, because messaging-hub switches to Firebase Cloud Messaging when a WebSocket is gone.\n\nAfter each drop, clients reconnected as agreed, and each reconnect needed a new connection token issued through the Merchant or Delivery system. The token requests piled onto the Merchant system, which saw far more drops than the Delivery system, and a stress test showed its database was the bottleneck. The delay spread to other Merchant components and reached Merchant users directly. On the messaging-hub side, Nginx began dropping packets at the traffic peaks because the nf_conntrack table reached its limit.\n\nThe search for the cause of the drops ruled out many things. Adding 50% more connection-manager instances changed nothing; GC and memory were fine; the server-side disconnect logic ran only in rare cases; idle timeouts were 3 minutes in connection-manager and 5 minutes in the load balancer and Nginx, and per-connection timers could not explain simultaneous drops. About 28,000 local ports were available, TIME_WAIT counts were negligible, the open files limit was 1048576, and Nginx worker limits were above current use. Most dropped clients were Merchant apps, and a reset metric shared by the infrastructure team was first misread as clients closing connections. No Japanese carrier outage was found.\n\nIngress Nginx's proxy-read-timeout and proxy-send-timeout were set to 300s; cutting them to 3s in development reproduced drops, so both, and the load balancer timeout, were raised to 3600s as the Ingress Nginx documentation advises for WebSockets. The drops still happened. The infrastructure and network teams had answered several times that nothing was wrong on their side.","ko":"LINE 의 messaging-hub 는 일본 음식 배달 서비스 Demaecan 의 Merchant 앱과 Driver 앱에 웹소켓으로 메시지를 보낸다. 어느 날 최대 트래픽 시간이 아닌데도 웹소켓 연결 수 그래프가 순간적으로 크게 떨어졌다. 이 현상은 한 번으로 끝나지 않고 불규칙하게 계속 일어났다.\n\n웹소켓이 끊겨도 messaging-hub 는 메시지를 FCM 으로 돌려 보내서 처음에는 클라이언트에 영향이 없었다. 하지만 끊긴 클라이언트들이 재연결하려고 커넥션 토큰을 한꺼번에 요청하면서 토큰을 발급하는 Merchant 시스템에 요청이 몰렸다. 처리가 지연되고 그 지연이 다른 구성 요소로 번지면서 Merchant 사용자가 직접 영향을 받았다.\n\n같은 무렵 messaging-hub 의 Nginx 에서도 패킷이 버려졌다. 네트워크 모니터링으로 보니 트래픽이 몰릴 때 NF_CONNTRACK 모듈의 연결 추적 테이블이 가득 찼고, 테이블이 찬 뒤 들어온 패킷이 드롭되고 있었다.\n\n여러 번 문의했지만 인프라 팀과 네트워크 팀은 문제가 없다고 답했다. 팀은 connection-manager 를 50% 늘려 봤다. 이어서 GC 영향, 서버가 연결을 끊는 로직, 3분짜리 idle 타임아웃과 PING, 약 28,000개의 가용 로컬 포트, TIME_WAIT, open files 값 1048576, Nginx 최대 접속 수를 차례로 확인했지만 이상이 없었다. 연결이 끊기는 클라이언트는 Merchant 앱이 압도적으로 많았다. 인프라 팀이 보여 준 클라이언트 쪽 리셋 지표는 원인이 아니라 결과였다. 일본 통신사 장애도 찾아봤지만 없었다. 개발 환경에서 Nginx 의 proxy-read-timeout 과 proxy-send-timeout 을 3s 로 줄이면 드롭이 재현돼서 운영 값을 3600s 로 올렸지만, 드롭은 또 일어났다."},"updated_at":"2026-11-09T11:55:56.200677Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0177","org_slug":"line"}