Jason Evans

ko생성일: 2025. 6. 13.갱신일: 2025. 6. 15.

jemalloc 메모리 할당기의 개발 역사와 각 단계별 성공과 실패, 그리고 이에 대한 회고를 담은 글입니다.

Jason Evans

jemalloc 메모리 할당기는 2004년 초 처음 고안되어, 약 20년간 공개적으로 사용되어 왔습니다. 오픈 소스 소프트웨어 라이선스 덕분에 jemalloc은 앞으로도 무기한 공개적으로 이용 가능할 것입니다. 하지만 업스트림(공식 저장소) 개발은 종료되었습니다. 이 글에서는 jemalloc의 개발 단계를 간략하게 설명하고, 각 단계별 성공과 실패 사례, 그리고 회고를 전합니다.

0단계: Lyken

2004년, 저는 과학 컴퓨팅을 위한 Lyken 프로그래밍 언어 개발을 시작했습니다. Lyken은 결국 막다른 길로 이어졌지만, 수동 메모리 할당기는 2005년 5월에 기능적으로 완성되어 있었습니다. (해당 기능을 활용할 예정이었던 가비지 컬렉터는 완성되지 못했습니다.) 2005년 9월, 이 할당기를 FreeBSD에 통합하기 시작했고, 2006년 3월에는 시스템의 할당기 기능을 얇게 감싸는 래퍼로 대체하며 Lyken에서 할당기를 제거했습니다.

이미 많은 노력을 투입한 할당기를 왜 Lyken에서 제거했을까요? FreeBSD에 통합한 후, 시스템 할당기에 부족했던 유일한 기능은 각 스레드별로 할당량을 추적해 가비지 컬렉션을 트리거하는 메커니즘뿐임이 명확해졌고, 이는 스레드별 데이터와 dlsym(3)을 활용한 얕은 래퍼로 구현할 수 있었기 때문입니다. 흥미롭게도, 수년 뒤 jemalloc은 결국 Lyken이 필요로 했던 통계 수집 기능을 자체적으로 추가하게 되었습니다.

1단계: FreeBSD

2005년 즈음, 다중 프로세서 컴퓨터로의 전환이 진행 중이었습니다. FreeBSD에는 Poul-Henning Kamp의 훌륭한 phkmalloc 메모리 할당기가 있었지만, 멀티스레드 실행에 대한 대비가 없었습니다. Lyken의 할당기는 확장성 측면에서 명백한 개선점으로 보였고, 친구 및 동료들의 격려 속에 곧 'jemalloc'이라 불리게 된 할당기를 통합했습니다. 그런데, 그렇게 간단하지는 않았습니다! 통합 직후 KDE(https://kde.org/) 앱 등 일부 워크로드에서 jemalloc이 심각한 단편화 문제를 드러냈습니다. 이제 거의 끝났다고 생각한 시점에 발생한 이 실 사용 실패는 jemalloc의 타당성에 큰 의문을 던졌습니다.

요약하자면, 단편화 문제는 단일한 확장(extent) 할당 방식을 사용한 것(즉, 사이즈 클래스 분리가 없음)에서 비롯됐습니다. Doug Lea의 dlmalloc에서 기본적인 영감을 받았으나, 단편화 문제를 회피하는 노련한 휴리스틱을 충분히 참고하지 않았습니다. 이에 급히 많은 연구와 실험이 뒤따랐습니다. jemalloc이 FreeBSD 릴리즈에 포함될 즈음엔, 관련 알고리즘이 2006 BSDCan jemalloc 논문에 기술된 바와 같이 완전히 크기별 분리 영역을 사용하도록 바뀌었습니다.

1.5단계: Firefox

2007년 11월, Mozilla Firefox 3의 출시가 임박했으나, 마이크로소프트 윈도우에서 특히 심한 메모리 단편화 문제가 해결되지 않은 상태였습니다. 이때부터 Mozilla와 메모리 할당에 대해 협업한 1년이 시작되었습니다. jemalloc의 리눅스 포팅은 간단했지만, 윈도우 포팅은 쉽지 않았습니다. jemalloc 소스는 FreeBSD libc에 있었으므로, 사실상 jemalloc을 포크(fork)해서 포팅 코드를 추가하고, FreeBSD에 유의미한 부분만 다시 업스트림에 반영하는 방식이었습니다. 이 시기까지 구현이 한 파일에 있었기에 포크 유지관리는 쉬웠지만, 복잡성은 이미 한 파일에 담기엔 지나치게 높아진 상태였습니다.

몇 년 후, Mozilla 개발자들은 포크를 벗어나기 위해 업스트림 jemalloc에 상당한 기여를 했습니다. 하지만 Mozilla 벤치마크에선 포크 버전이 꾸준히 업스트림보다 더 좋은 성능을 보였습니다. 이는 국지적 최적화의 과적합(overfitting) 탓인지, 진짜로 업스트림 성능이 저하된 탓인지는 명확하지 않지만, 개인적으로 jemalloc에서 가장 아쉬운 대목 중 하나입니다.

2단계: Facebook

2009년 Facebook에 입사했을 때, Facebook 인프라에서 jemalloc 사용을 가로막는 최대 장애물이 계측 도구(Instrumentation)임을 알게 되었습니다. 중요 내부 서비스들은 메모리 단편화 제어에 jemalloc을 의존하지만, 엔지니어들은 tcmallocgperftoolspprof 힙 프로파일링 도구를 써서 메모리 누수를 디버깅해야 했습니다. jemalloc 1.0.0 릴리즈에는 pprof 호환 힙 프로파일링 기능이 핵심입니다.

이후 jemalloc 개발은 GitHub로 옮겨가 이슈나 기회가 있을 때마다 간헐적으로 이어졌고, 타 개발자들도 여러 기능을 기여하기 시작했습니다. 3.0.0 버전에서는 광범위한 테스트 인프라와 Valgrind 지원이 도입되었습니다. 4.x 릴리즈에선 decay 기반 퍼지(purging) 및 JSON 텔레메트리 기능이 추가되었고, 5.x 시리즈에서는 "청크(chunk)"에서 "익스텐트(extent)"로 넘어가 2 MiB의 huge page와 더 잘 연동할 기반을 마련했습니다.

다소 논란이 있었던 변경점으론, 5.0.0에서 Valgrind 지원이 제거된 일입니다. 유지관리 부담이 컸고(미묘한 부분에까지 영향), Facebook 내부에서는 전혀 쓰이지 않았기 때문입니다. 대신 pprofMemorySanitizer와 같은 다른 툴이 대세였으니까요. Valgrind 관련 피드백이 거의 없었던 터라 이용자가 없는 줄 알았습니다. 돌이켜보니 사실이 아니었습니다. 특히 Rust 언어는 jemalloc을 직접 컴파일된 프로그램에 포함시켰고, 러스트 개발자와 Valgrind 사용자층이 겹쳤던 것 같습니다. 많은 이들이 분노했고, 러스트 바이너리에서 jemalloc이 자연적 진화보다 더 빨리 제거되었던 것 같습니다.

Facebook의 내부 텔레메트리는 경이롭고, 수많은 서비스에서 수집된 성능 데이터를 통해 메모리 할당기 개발에 큰 도움을 받았습니다. 지난 10년간 가장 빠른 메모리 할당기(tcmalloc, jemalloc) 둘이 모두 이런 데이터를 활용했다는 사실은 우연이 아닙니다. 빠른 경로 최적화처럼 "단순"한 것들도 리눅스 perf 집계가 있으면 설계가 훨씬 쉬워지니까요. 단편화 회피처럼 어려운 문제도, 수천 워크플로가 편차 없이 잘 동작한다면 안전합니다. jemalloc은 Facebook 인프라의 핵심 요소로서 성능, 견고함, 일관성 측면에서 막대한 이익을 쌓아 왔습니다. 또한, jemalloc 자체의 통계 리포팅 기능도 이런 원격/집계 환경에 대응하며 생겼는데, 이것은 비(非)Facebook 앱의 튜닝/디버깅에도 크게 기여했습니다.

Facebook에서 마지막 해를 보내며 작지만 강한 jemalloc 팀을 꾸려, 원래라면 벅찼을 과제를 여러 개 풀 수 있었습니다. 큰 성능 향상 외에도, 지속적인 CI 테스트와 광범위한 텔레메트리도 달성했습니다. 2017년 Facebook을 떠날 때, 저 없이도 훌륭하게 개발 및 유지관리를 이어가는 팀이 있었고, Qi Wang의 리더십 하에, 그리고 커밋 기록만 봐도 알 수 있듯 수많은 기여자 덕분이었습니다.

Facebook이 Meta로 리브랜딩하던 시기, jemalloc 개발 양상도 눈에 띄게 달라졌습니다. 핵심 기술에 대한 투자보다 투자 대비 "수익"을 중시했습니다. 이는 jemalloc 커밋 기록에도 드러납니다. 예를 들어 huge page allocation(HPA)에 대한 원칙적인 설계 시도는 2016년으로 거슬러 올라갑니다! HPA 작업은 몇 년간 이어지다 점점 삽질로 침체했고, 코드베이스 정리가 안 되며 결국 좌초했습니다. 저는 이미 손을 뗀 상태라 아쉬움이 덜하지만, 최근 Meta의 변화로 인해, 이제 더는 jemalloc의 장기 발전을 이끄는 사람이 없게 되었습니다.

이야기를 너무 억지로 끄는 건 원치 않지만, Facebook/Meta에서 jemalloc이 이렇게 끝난 점은, 대부분이 선의로 행동했음에도 아쉬움이 남습니다. 기업 문화는 외부·내부 압력에 따라 변합니다. 그리고 사람들은 1) 극심한 압박 속 부적절한 결정을 내리거나, 2) 압박에 순응하거나, 3) 건너뛰기를 당하는 등, 불가능한 상황에 놓이게 됩니다. 개인의 영향력으로 조직의 퇴락을 늦추거나, 고립된 부흥을 만들 수도 있지만, 궁극적 흐름을 막을 수는 없습니다.

jemalloc에 기여한 전 동료들과, 오랜 기간 아낌없는 투자를 해준 Facebook/Meta에 진심으로 고마운 마음입니다.

4단계: 정체(Stasis)

이제 어떻게 될까요? 제 기준에서 "upstream" jemalloc 개발은 끝났습니다. Meta의 필요는 한참 전부터 외부와 잘 맞지 않고, 독자 노선을 걷는 게 낫습니다. 제가 다시 참여한다면, 먼저 수백 시간의 리팩토링으로 쌓인 기술부채를 청산해야 하며, 이후의 과제는 그만한 비용을 치르기엔 충분히 매력적이지 않습니다. 다른 이들이 dev 브랜치 또는 벌써 3년 된 5.3.0 릴리즈에서 포크를 진행할 수도 있겠지요.

앞서 각 단계별 실패를 언급했지만, 전반적으로 놀랐던 실패들도 있었습니다.

  • 앞서 언급한 Valgrind 지원 제거로 인해 불만이 컸지만, 근본 원인은 외부 용도와 수요에 대한 인식 부족이었습니다. 그게 중요하단 사실을 알았다면 협업해 지원을 유지했을지도 모릅니다. 또 하나의 예로, jemalloc이 Android 메모리 할당기로 쓰이던 사실을 2년간 전혀 몰랐고, 물론 나중에 대체된 것도 사후적으로 알았습니다.

  • jemalloc 개발은 완전히 공개적으로 진행됐지만(Facebook 내에만 갇혀 있었던 게 아님), 타 조직에서 코어 기여자를 배출한 적은 거의 없었습니다. Mike Hommey가 Mozilla의 Firefox를 업스트림 jemalloc으로 전환하려던 시도는 임계 포인트에 다다랐다가 불발이었고, CMake 기반 빌드 시스템을 도입하려는 외부 시도들은 여러 번 좌초했습니다. Darwin 등에서 뼈아픈 경험을 통해, 내부에만 갇힌 오픈소스는 번성할 수 없다는 걸 알았지만(HHVM도 반복 교훈), 단지 공개 개발만으론 독립 프로젝트로서의 jemalloc 생존엔 부족했습니다.

개인적으로 25년 넘게 수동 메모리 관리보다 가비지 컬렉션을 지지해왔던 저에겐 jemalloc이 이례적인 경험이었지만, 정말 보람찬 프로젝트였습니다. 이 프로젝트를 가치있게 만들어 주신 모든 협력자, 지원자, 사용자분들께 깊이 감사드립니다.