Rust 메모리 고정 현상, 할당자 문제 해결기
Rust 서비스에서 부하 시 메모리 사용량 급증 후 고정되는 현상 발생
Tokio 태스크 및 Semaphore를 사용한 이벤트 기반 아키텍처에서 문제 진단
glibc의 ptmalloc 할당자가 메모리 반환 지연 및 단편화(Fragmentation)를 유발했음
jemalloc으로 전환하여 메모리 사용량 안정화 및 문제 해결 성공
glibc ptmalloc의 메모리 관리 메커니즘과 문제점
글에서는 glibc의 ptmalloc 할당자가 메모리를 관리하는 방식, 특히 아레나(Arena)와 스레드 아레나(Thread Arena)의 동작 방식을 상세히 설명합니다. 동시성 작업(Concurrent Tasks)이 증가할 때 메모리 블록이 아레나 내에 인터리빙(Interleaving)되어 할당되고, 해제 시에도 재사용을 위해 캐싱되어 OS에 즉시 반환되지 않는 점을 지적합니다. 이는 특히 버스티 워크로드(Bursty Workload)에서 RSS(Resident Set Size)가 고정되는 현상의 주요 원인으로 분석됩니다.
Tokio 태스크와 메모리 단편화(Fragmentation)의 상관관계
이벤트 기반 아키텍처에서 Tokio 태스크(Task)가 짧은 생명주기 동안 다수의 메모리 할당을 생성하고 해제하는 과정이 힙 단편화(Heap Fragmentation)를 야기한다고 설명합니다. 특히 mmap 임계값(mmap Threshold) 이하의 할당들이 아레나 내부에 갇히면서, 일부 라이브 할당이 없더라도 OS가 메모리를 회수하지 못하는 상황이 발생합니다. `malloc_trim(0)` 호출 시 메모리가 즉시 해제되는 실험 결과는 이러한 할당자 내부의 메모리 관리 방식에 기인함을 보여줍니다.
jemalloc 도입을 통한 메모리 회수 최적화
문제 해결을 위해 jemalloc 할당자로 전환한 결과, 메모리 사용량이 안정화되었습니다. jemalloc은 슬랩(Slab) 기반의 독립적인 메모리 관리와 백그라운드 스레드를 통한 비동기 메모리 회수(Asynchronous Memory Reclamation) 메커니즘을 통해 glibc의 문제를 해결합니다. 각 크기별 슬랩이 독립적으로 관리되므로, 특정 할당이 다른 메모리 회수를 방해하는 glibc와 달리 효율적인 메모리 반환이 가능해집니다.
MiMalloc의 한계와 Tokio 워크로드에서의 부적합성
고성능 서비스에 자주 추천되는 MiMalloc 역시 특정 워크로드에서는 한계를 보였습니다. 특히 크로스 스레드 해제(Cross-thread Frees) 시 메모리가 즉시 반환되지 않고 `xthread_free` 리스트에 쌓였다가 추후에야 처리되는 방식은, 작업 스레드가 자주 유휴 상태가 되는 버스티 워크로드에서 메모리 회수를 지연시키는 요인이 되었습니다. `MIMALLOC_PURGE_DELAY` 등의 설정을 조정해도 근본적인 해결책이 되지 못했음을 언급합니다.