pile·
백엔드·카카오 엔터테인먼트 FE카카오 엔터테인먼트 FE·

마법소녀 이세계 아이돌 웹툰 런칭! BFF 장애 대응기

카카오페이지 웹은 GraphQL 기반 BFF를 운영하는데, 이세계 아이돌 웹툰이 런칭한 날 트래픽이 3배 이상 몰리면서 BFF 서버 CPU가 100%를 찍고 수 분간 페이지가 뜨지 않는 장애가 났다. 리소스 대비 TPS가 지나치게 낮다는 점에 주목해 가설을 하나씩 지워나갔고, 최종 원인은 node.js https.Agent의 keepAlive 기본값이 false라는 것이었다. 프론트엔드 팀이 쿠버네티스와 서버 운영까지 직접 맡는 조직에서 나온 장애 회고다.

핵심 포인트
  • 최종 원인은 node.js agent의 keepAlive 기본값이 false여서 백엔드 호출마다 TCP 연결을 새로 맺고 있었던 것이다.
  • 수정 후 nGrinder 부하 테스트에서 프록시 TPS가 720에서 3600으로, GraphQL TPS가 560에서 1800으로 올랐다.
  • node 19 미만은 기본 agent도 keepAlive가 false이므로 custom agent를 쓰지 않더라도 확인이 필요하다.
  • 백엔드 API, ingress, pod 개수, 미들웨어, apollo-server, TIME_WAIT 순으로 가설을 지워가며 범위를 좁혔다.
  • pod 개수 실험에서 코어 수와 같을 때 TPS가 최고였고 코어의 2배를 주면 오히려 떨어졌다.
  • util.inspect의 depth를 null로 두면 객체 전체를 탐색해 로깅 비용이 10배 차이 났다.
상세 정리
  • 장애 상황: 2023년 6월 21일 웹툰 런칭 생중계와 함께 트래픽이 몰려 서버가 응답을 내리지 못했고 수 분간 페이지가 뜨지 않았다.
  • 구성: 쿠버네티스 위에 서버사이드 렌더링용 next.js pod와 BFF pod가 따로 떠 있고, BFF는 node.js express에 apollo server를 올려 REST 응답을 gql 포맷으로 가공한다.
  • 이상한 점: 렌더링까지 하는 next.js 서버보다 가공만 하는 BFF의 성능이 더 안 나왔다.
  • 가설 1 백엔드 API: 백엔드 팀 모니터링에서 느린 응답이나 부하가 없었다. nGrinder로 백엔드 직접 호출, express 프록시, graphql 세 경로를 비교하니 백엔드와 BFF의 TPS가 약 10배 차이였다.
  • 대조군: express에 문자열만 반환하는 라우트를 만들어 재보니 성능이 정상이었다. BFF 자체가 느린 게 아니라 백엔드를 경유할 때만 느렸다는 뜻이다.
  • 가설 2 인프라: ingress(nginx) CPU는 트래픽이 튈 때도 20%를 넘지 않아 앞단 지연은 아니었고, 워커 노드 CPU가 치솟은 것으로 보아 컨테이너 처리 단계가 병목이었다.
  • pod 개수 실험: 8코어 서버 6대로 pod 수를 바꿔가며 측정했더니 코어 수x1 지점에서 TPS가 최고였고 2배에서는 감소했다. 장애 당시는 코어 수x1.5였으므로 pod 부족은 아니었다.
  • 가설 3 미들웨어: 쿠키 파싱을 중복 수행하던 부분을 res.locals에 저장해 한 번만 실행하도록 고쳤고, 로깅용 util.inspect의 depth 옵션이 null이라 객체 전체를 탐색하던 문제도 필요한 범위만 변환하도록 바꿨다. 개선은 됐지만 목표 성능에는 못 미쳤다.
  • 가설 4 apollo-server: 공개 벤치마크상 apollo-server-express도 초당 2486 요청은 나오는데 그에도 못 미쳤고, apollo를 거치지 않는 axios 프록시 경로도 똑같이 느렸으므로 라이브러리 문제가 아니었다.
  • 가설 5 TIME_WAIT: 로컬 포트 고갈을 의심했지만 netstat에 TIME_WAIT이 잡히지 않았고 EADDRNOTAVAIL 에러도 없었으며 서버 대수 대비 RPS도 여유가 있었다.
  • 진짜 원인: apollo server의 RESTDataSource를 상속해 willSendRequest에서 custom https agent를 붙이고 있었는데 그 agent의 keepAlive 기본값이 false였다. 요청마다 연결을 새로 맺느라 CPU를 쓰고 있었다.
  • 조치 후: keepAlive를 true로 바꾸자 프록시가 최대 4000 TPS, graphql이 2000 TPS 수준이 됐고 트래픽이 몰리지 않는 평시 CPU도 안정화됐다.
  • 곁가지 팁: 맥에서 쓸 수 있는 로컬 포트는 약 16,000개라 로컬 부하 테스트가 딱 그 지점에서 멈추며, net.inet.ip.portrange.first로 범위를 조정할 수 있다.
왜 읽나node.js로 서버 간 호출을 하는 BFF나 프록시가 리소스 대비 TPS가 안 나온다면 agent의 keepAlive 기본값부터 확인하게 해주는 사례다.
카카오 엔터테인먼트 FE
카카오 엔터테인먼트 FE 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·twilio-engTwilio Engineering·

    Programmable Messaging에서 Verify API로 마이그레이션하기

    Twilio의 Programmable Messaging API로 자체 OTP 솔루션을 운영하던 서비스가 Verify API로 전환하는 방법을 코드 예시와 함께 설명한다. Verify는 OTP 전송·검증을 위한 전용 API로, 전화번호 구매, 토큰 생성, DB 저장·만료 관리를 내부에서 처리해 개발자가 직접 구현할 코드를 크게 줄인다.

    요약 이어보기
    #authentication#twilio#sms+2
  2. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 2부

    포스타입 백엔드 엔지니어가 벡터 기반 개인화 추천 시스템을 실제 운영하며 맞닥뜨린 성능 장애와 용량 문제를 해결한 과정을 담은 2부다. 수백만 개의 벡터 KNN 검색이 피크 시간대에 전체 Elasticsearch 검색 성능을 흔드는 문제부터 클러스터 OOM 사태까지, 쿼리 최적화와 인프라 분리 두 가지 경로로 근본 해결에 이른다.

    요약 이어보기
    #elasticsearch#vector-search#recommendation-system+2
  3. 백엔드·포스타입포스타입·

    포스타입이 개인화 추천을 하는 방법 1부

    포스타입이 태그 기반 추천의 한계를 극복하고 벡터 임베딩 기반 개인화 추천 시스템을 구축한 과정을 담은 1부다. 유사한 콘텐츠가 다른 용어를 쓰거나 동일한 태그가 전혀 다른 톤의 콘텐츠를 가리키는 문제를 임베딩 벡터로 해결하고, OpenSearch의 HNSW ANN 검색으로 수백만 벡터를 실시간 검색하는 시스템을 구축해 구매율 15% 향상을 달성했다.

    요약 이어보기
    #opensearch#vector-search#recommendation-system+2