워라밸 브레이커, 메모리릭을 찾아라(2/4)

| 2021-11-27

안녕하세요, 넷마블 TPM실 기술분석팀 김범진입니다.

이전 글에서는 비주얼 스튜디오 진단 도구를 활용해 메모리릭을 찾는 방법을 살펴봤습니다. 이번 글에서는 수동으로 메모리릭을 찾는 방법을 공유합니다.

플레이 1시간마다 200MB씩 메모리가 증가

우선, 5000명이 플레이하는 월드에서 플레이 1시간마다 200MB씩 계속 메모리가 증가하는 상황을 가정해보겠습니다. 이 메모리릭을 찾아야 할까요?

충분한 스펙을 갖춘 장비를 준비해 서비스를 돌려 봅시다. 얼마나 서비스할 수 있는지 숫자로 표현해보겠습니다. 1시간에 200MB면, 하루에 4.8GB씩 메모리가 늘어납니다. 이를 서비스 가능일로 환산하면 128GB를 준비해도 26.6일이면 가득찹니다. MMORPG 점검 주기가 약 3주라면 커버가 될 것 같지만, 서버를 그렇게 운영하다간 여러분의 몸이 남아나지 않을 수 있습니다.

그래도 장비로 커버할 방법을 구상해 봅시다. 다만, 장비에서 램을 꽂을 수 있는 슬롯은 유한합니다. 무한히 램을 구성할 수는 없습니다. 또한 추후 어떤 이슈가 있을지 모르니 슬롯 사용 100%는 피해야 합니다. 서버 머신에 총 8개 슬롯이 있다고 하면, 여유로 2~3개는 남겨둬야 합니다.

최대 6개 슬롯을 사용한다고 가정하고, 램을 투입하기 위해 쇼핑해 봅시다. 더 많은 메모리를 활용하려면 슬롯당 메모리 밀집도를 올리면 됩니다. 다만, 밀집도가 높은 고용량 램은 그에 상응할 만큼 가격이 높을 뿐입니다. 적당한 타협이 필요할 것 같습니다. 게다가 램은 반영구적인 장치가 아닙니다. 소모품이죠. 주기적으로 교체를 해야 합니다. 서버도 한 대가 아닙니다. 계산하면 할수록 운영비용이 계속 올라갑니다. 장비로 커버하는 방법은 임시로는 문제를 막을 수 있지만, 원초적인 해결책이 될 수 없습니다. 처음에는 비교적 작은 메모리릭에서 출발했지만, 업데이트를 반복하면서 2배 3배로 커질 우려도 있습니다.

즉, 메모리릭을 해소하면 운영 비용을 꽤 줄일 수 있다는 의미가 되기도 합니다. 메모리릭을 찾아서 수정해 둔다면 훗날 편하게 운영할 수 있습니다.

200MB가 작은 릭인가요?

1시간에 200MB란 메모리릭은 왠지 프로파일러를 돌리면 찾을 수 있을 것 같은 충분한 크기로 느껴집니다. 프로파일러를 활용해 문제를 찾아봅시다. 하지만 프로파일러는 고려할 사항이 있습니다. 성능 저하를 발생시키므로 실제 5000명 수준으로 부하를 주며 테스트하기가 쉽지 않습니다. 테스트를 위해 유저 수를 줄여야 합니다. 1/50로 줄여 100명 수준으로 테스트를 한다면, 단순 산술계산으로 시간당 4MB씩 증가한다고 볼 수 있습니다.

하지만 100명이라고 해도 월드를 시뮬레이션하기 위한 비용이 있기 때문에, 메모리 사용량은 상당합니다. 대개 5GB 이상은 사용될 것으로 보입니다. 과연 5GB 중, 1시간에 4MB씩 증가 하는 영역을 프로파일러로 찾을 수 있을까요? 전체 영역에서 대략 1000분의 1을 점유하는 구간을 잡아야 합니다. 물론 오랜 시간 테스트하면 메모리릭이 점유하는 영역이 커지긴 합니다만, 프로파일러가 그 시간까지 버텨줄지가 의문입니다.

메모리 프로파일러 없이 풀 부하 테스트를?

만약에 프로파일러 없이 풀 부하 테스트를 할 수 있다면 어떨까요? 그럼 라이브 환경과 비슷한 수준의 메모리릭이 발생할 것입니다. 프로파일러를 사용하지 않기 때문에 성능 저하는 없을 것이고, 테스트 시간 단축은 덤입니다. 거기다 프로파일러로 인한 서버 불안정함도  없으니 편하게 테스트할 수 있습니다. 아이디어는 다음과 같습니다.

  • 프로파일러 없이 라이브 환경과 동일하게 테스트를 진행합니다.
  • 주기적으로 모든 덤프(Full Dump)를 수집합니다.
  • 그리고 수집한 덤프 메모리 전체 영역을 조사해 수치화합니다.

위 아이디어를 실현할 때 제일 중요한 것은 WinDbg입니다. WinDbg는 메모리 전체 영역을 객체 사이즈별로 할당 개수를 수치화 할 수 있습니다. 이런 점을 활용해 수치화한 데이터를 비교 분석하면 메모리릭을 찾을 수 있습니다.

수동탐지 

지금부터 프로파일러 없이 수동탐지를 시작하겠습니다. 이 테스트는 총 3단계로 진행합니다. 

  • 1단계 – 덤프 수집 단계: 데이터 샘플링을 위해 모든 덤프를 주기적으로 수집합니다.
  • 2단계 – 데이터 수집 단계: 비교분석을 위해 수집한 덤프를 WinDbg로 수치화해 데이터를 추출합니다.
  • 3단계 – 분석 단계: 정렬한 데이터를 비교 분석해 증가하는 객체를 찾습니다.

탐지를 위한 준비물은 다음과 같습니다.

예시 코드

이제 메모리릭을 유발하는 테스트 코드를 구동해 메모리를 찾아보겠습니다. 소스는 간단합니다. 두 가지 객체 ‘vir_obj’와 ‘obj’를 루프에 넣어 0.1초 주기로 의도한 메모리릭을 만드는 소스입니다.

메모리릭을 유발하는 예시 코드

각 객체마다 디버깅이 제대로 되는지 확인하기 위해 생성자에 태그로 활용할 실제 스트링을 넣었습니다. 특이한 점으로 ‘vir_obj’와 ‘obj’ 소멸자 간에 ‘virtual’ 키워드 유무 차이가 있는데, 이는 뒤에서 다시 설명하겠습니다.

수동으로 덤프 수집하기

 Process Explorer를 활용해 덤프를 수집해 봅시다. 사용법은 간단합니다. 구동 중인 프로세스 이미지를 찾아서 우클릭하고, ‘Create Dump’ → ‘Create Full Dump’ → “경로 및 파일 이름을 지정”으로 덤프가 생성됩니다.

Process Explorer

여기에서는 Process Explorer에 있는 기능 중에서 덤프를 받는 기능만 사용했지만, 이 기능 이외에도 모니터링용 프로세스 상태 등을 간략히 확인하는 기능도 있습니다. 또한 스레드별 현재 콜스택이나 메모리의 string 확인 등 여러 기능이 더 있으므로 개발 및 모니터링에 활용해 보는 것도 좋을 것입니다.

주기적으로 덤프 수집하기

테스트를 하면서 덤프를 매번 수동으로 수집하기엔 왠지 자동화를 해보고 싶습니다. 자동으로 덤프를 수집하는 툴이 있지 않을까요? MS에서 제공하는 ProcDump라는 툴이 있습니다. 여기서 사용할 주요 옵션은 다음과 같습니다. 

  • s 옵션: 덤프 생성 주기 지정. (기본값은 10초)
  • n 옵션: 반복 횟수 지정. (추가 옵션이 없다면, 기본적으로 미니덤프를 생성)
  • ma 옵션: 모든 덤프 생성

ProcDump를 활용해 자동으로 덤프를 수집해 봅시다. 마지막에는 프로세스명 혹은 pid를 넣어야 타겟팅되니 잊지 말고 넣어 줍시다. 예시에서는 메모리릭이 충분히 발생할 수 있도록 1시간 단위로 수집하게 했습니다. 

procdump.exe -s 3600 -n 5 -ma {process name / pid}
60 초마다 풀덤프를 생성하며 5회 반복합니다.

빠른 테스트를 위해 60초에 한번씩 생성하도록 설정했습니다. 파일 목록을 보시면, 실제 덤프 파일이 60초에 하나씩 생성된 것을 볼 수 있습니다. 모든 덤프는 애플리케이션이 사용하는 모든 메모리를 기록하니, 만약 릭이 의도대로 동작한다면 점차 파일 사이즈가 커질 것입니다. 위 스크린샷에서 실제 파일 사이즈가 점점 커지고 있으므로, 소스가 의도대로 동작하고 있음을 확인할 수 있습니다.

여기까지 메모리릭 수동탐지를 위해 덤프를 수집하는 방법을 살펴봤습니다.

수집한 덤프를 분석하는 과정은 다음 글에서 공유하겠습니다.