이 사이트의 콘텐츠는 인공지능(AI) 또는 기계 번역 기술을 사용하여 번역되었으며 오류가 있을 수 있습니다.

Skip to content

메모리 누수 추적을 통한 서버 리소스 사용량 개선

약 1년 전, 저희는 게임 서버의 처리 용량이 감소하는 패턴이 나타나기 시작했다는 초기 징후를 포착했습니다. 초기에는 여유 용량이 충분했기 때문에 플레이어에게 미치는 영향은 거의 없었지만, 여유 용량은 빠르게 줄어들고 있었습니다. 게임 서버는 충분한 CPU 및 메모리 자원이 있는 한 새로운 플레이어를 수용하거나 새로운 게임을 시작하는 데 아무런 문제가 없습니다. 이번 경우, 서버에서 호스팅할 수 있는 게임 수를 제한하는 요인은 메모리 한계였습니다. 이 문제를 해결하지 못하면 플레이어 기반을 지원하기 위해 데이터 센터를 확장해야 했으며, 이는 막대한 인력과 재정적 비용을 수반했을 것입니다.

우리가 먼저 탐구한 가설은 게임의 메모리 사용 패턴이 변화하고 있다는 것이었습니다. 우리는 개발자들이 플랫폼의 한계를 뛰어넘고, 주어진 자원을 활용해 훌륭하고 획기적인 게임을 만들기를 장려합니다. 이를 확인하는 것은 간단했습니다. 모든 게임의 메모리 사용량을 집계하여 증가했는지 확인할 수 있었기 때문입니다. 하지만 결과는 달랐습니다. 게임당 평균 메모리 사용량과 백분위수 집계치는 비교적 평평하게 유지된 반면, 우리의 용량은 꾸준히 감소하고 있었습니다.

우리는 매월 테라바이트 단위의 성능 및 리소스 사용량 데이터를 저장하고 있으며, 이를 집계하고 필터링하여 이와 같은 문제의 근본 원인을 찾는 데 활용할 수 있습니다. 우리는 문제를 특정 지역, 하드웨어 유형, 소프트웨어 버전으로 좁혀보려 했지만, 안타깝게도 이 문제는 어디서나 발생하고 있었습니다. 그래서 우리는 몇 개의 게임 서버를 무작위로 선정해 세밀하게 조사하기로 결정했습니다. 저는 처음에 리눅스 시스템의 "사용 가능한(free)" 메모리 개념에 현혹되었습니다. 이는 너무나 흔한 문제라 누군가가 도메인을 등록하고 메모리 범주를 설명하는 웹사이트를 개설하기까지 했습니다: https://www.linuxatemyram.com.

요약하자면, 메모리의 대부분이 사용되고 있는 것은 좋은 일이며, 여유 메모리는 낭비되는 메모리입니다. 사용 가능한 메모리가 0에 가까워질 때만 걱정하면 됩니다.

메모리 추적 방식이 정확하다는 것을 확인한 후, 우리는 메모리가 어디에 사용되고 있는지 파악하기 위한 일련의 실험을 시작했습니다. 우리의 접근 방식은 구체적인 메모리 하위 범주를 추적하여, 이를 바탕으로 한 맞춤형 해결책을 마련하는 것이었습니다.

(참고: 메모리 사용률(%)은 (총 물리적 메모리 - 사용 가능 메모리) / 총 물리적 메모리로 계산됩니다)

사용 가능한 메모리는 대략 MemFree + Active(file) + Inactive(file) + SReclaimable의 합으로 계산됩니다.

MemFree는 사용되지 않은 메모리를 추적합니다.

Active(file) 및 Inactive(file)은 페이지 캐시 메모리를 추적합니다. 페이지 캐시는 디스크 I/O 양을 줄이기 위해 액세스된 데이터를 메모리에 저장합니다.

SReclaimable은 회수 가능한 슬랩 메모리를 추적합니다. 슬랩 메모리는 커널에서 흔히 사용하는 초기화된 객체의 캐시를 유지하는 데 사용됩니다.

첫 번째 실험은 캐시 튜닝, 특히 vm.vfs_cache_pressure와 vm.dirty_background_ratio를 조정하는 것이었습니다.

vfs_cache_pressure 값을 높이면 회수 가능한 캐시에서 객체를 회수할 가능성이 높아집니다. 이는 성능에 영향을 미칩니다(캐시 미스 및 해제 가능한 객체를 찾는 조회 시간 모두에서).

dirty_background_ratio는 비차단 방식으로 디스크에 쓰기를 시작할 때 (더티 상태인 페이지 캐시 메모리의) 비율을 나타냅니다.

우리는 다음과 같은 변경 사항을 적용하고 메모리, slabinfo, cgroups에서 그 효과를 관찰했습니다.

vm.vfs_cache_pressure= 100 ==&gt; 10000<br>vm.dirty_background_ratio= 10 ==&gt; 5

결과를 추적하는 비침습적인 방법은 메모리 상태의 스냅샷을 찍는 cron 작업을 주기적으로 실행하는 것이었습니다. 대략 다음과 같은 방식입니다:

#!/bin/bash<br>now=`date +%Y-%m-%d.%H:%M`
  # create test dir<br>mkdir -p ~/memtest&nbsp;
# log meminfo<br>sudo cat /proc/meminfo 
  ~/memtest/meminfo_$now
# log slabinfo<br>sudo cat /proc/slabinfo
  ~/memtest/slabinfo_$now&nbsp;
# log cgroups<br>sudo cat /proc/cgroups
  ~/memtest/cgroups_$now&nbsp;

캐시 압력 변경 사항을 적용하고 몇 시간 동안 관찰한 후 분석을 시작했습니다. 우리는 약 8GB의 여유 메모리를 확보했다는 결론을 내렸지만, 그 메모리는 페이지 및 디스크 캐시, 즉 Active(file) 및 Inactive(file) 범주에서 직접 나온 것이었습니다. 이는 실망스러운 결과였습니다. 사용 가능한 메모리의 순증가는 유의미하지 않았을 뿐만 아니라, 더 이상 이 메모리를 효율적으로 활용하지 못하고 있었기 때문입니다. 우리는 다른 곳에서 메모리를 회수해야 했습니다.

사용 가능한 메모리를 직접 늘리는 데 실패한 후, 우리는 경쟁 관계에 있는 범주들을 줄여보기로 했습니다. SUnreclaim 메모리 범주가 상당히 크다는 것을 발견했는데, 경우에 따라 몇 달 사이에 60GB까지 급증하기도 했습니다. SUnreclaim 범주는 메모리 압박 상황에서도 회수할 수 없는, 운영 체제가 객체 풀에 사용하는 메모리를 추적합니다. 문제의 첫 징후는 끊임없이 늘어나는 cgroup의 수였습니다. 우리는 도커화된 프로세스를 실행할 때 기껏해야 수백 개의 cgroup이 생길 것으로 예상했지만, 실제로는 수십만 개에 달하는 cgroup이 생성되고 있었습니다. 다행히도 페이스북의 다른 엔지니어인 Roman Gushchin이 최근 커널 수준에서 바로 이 문제를 발견하고 수정했던 것으로 보입니다(https://patchwork.kernel.org/cover/10943797/). 그는 다음과 같이 설명합니다:

근본적인 문제는 매우 간단합니다. cgroup에 할당된 모든 페이지는 해당 cgroup에 대한 참조를 가지고 있으므로, 할당된 페이지가 모두 사라지지 않는 한 cgroup을 회수할 수 없습니다. 슬랩 객체가 다른 cgroup에서 활발히 사용 중이라면 회수되지 않으며, 이로 인해 원래 cgroup이 회수되는 것을 막게 됩니다.

이것이 바로 우리의 문제인 것 같아서, 수정 사항이 제대로 적용되었는지 확인하기 위해 커널 5.3을 간절히 기다렸습니다.

우리는 캐시 부하 실험에서 사용했던 메모리 추적 스크립트를 재사용했지만, 커널 실험을 위해 대조군과 실험군을 설정하고자 했습니다. 서버 2개의 랙에서 운영 트래픽을 일시적으로 차단한 후, 한 랙은 커널을 5.3으로 업그레이드하고 다른 랙은 5.0을 유지했습니다. 그런 다음 두 랙 모두를 재부팅하고 다시 운영 트래픽을 허용했습니다. 약 일주일 후, cgroups와 회수 불가능한 슬랩 메모리가 시간에 따라 어떻게 변화하는지 추적했습니다. 결과는 다음과 같습니다:

커널 5.0.0은 cgroups가 지속적으로 증가하여 일주일 만에 회수 불가능한 슬랩 메모리가 4GB 증가해 총 6GB가 되었습니다. 반면, 커널 5.3.7은 cgroups가 매일 크게 감소하며, 회수 불가능한 슬랩 메모리의 증가 속도가 매우 느립니다. 일주일 후, 회수 불가능한 슬랩 메모리는 약 2GB입니다. 새로운 커널을 사용하면, 수개월간 가동한 후에도 회수 불가능한 슬랩 메모리가 약 4GB 수준에서 안정화됩니다.

우리가 해결하고자 했던 궁극적인 문제는 게임 서버의 용량이 시간이 지남에 따라 감소하고 있었다는 점입니다. 이는 지속적으로 증가하는 회수 불가능한 슬랩 메모리로 인해 사용 가능한 메모리가 줄어들었기 때문이었습니다. 따라서 커널 수정으로 이 문제를 비교적 통제할 수 있게 되었을 때, 서버 용량에는 어떤 영향이 있었을까요?

왼쪽에서 보시다시피, 서버당 게임 수가 꾸준히 감소하여 인프라에 큰 부담을 주는 문제가 발생했습니다. 2020년 3월경 전 세계 서버에 커널 버전 5.3을 배포한 이후, 지금까지 높은 서버 용량을 유지할 수 있었습니다. 커널 수정 작업을 수행해 주신 Roman Gushchin 님과 문제 조사 및 수정 배포를 도와주신 Andre Tran 님께 깊은 감사를 드립니다.

Roblox Corporation이나 이 블로그는 어떠한 회사나 서비스도 추천하거나 지지하지 않습니다. 또한, 이 블로그에 포함된 정보의 정확성, 신뢰성 또는 완전성에 대해 어떠한 보증이나 약속도 하지 않습니다.

이 블로그 게시물은 원래 Roblox Tech Blog에 게시되었습니다.