Sharp 라이브러리 사용 시 메모리 이슈 해결하기
최근 서비스 운영 중 이유를 알 수 없는 메모리 점유율 상승(Memory Leak) 현상이 발생했습니다. 로컬 환경에서는 괜찮은데, 유독 리눅스 기반의 운영 서버(특히 컨테이너 환경)에서만 메모리가 계속 치솟다가 프로세스가 죽어버리는 현상이었죠. 오늘은 이 문제를 해결하기 위해 적용했던 설정과 그 배경에 대해 정리해 보려 합니다. 1. 문제는 '메모리 파편화'와 '캐시' Sharp는 내부적으로 C++로 작성된 libvips 라이브러리를 사용합니다. 이 덕분에 굉장히 빠르지만, Node.js의 가비지 컬렉션(GC) 범위 밖에서 메모리를 관리하는 부분이 있어 주의가 필요합니다. 특히 glibc를 사용하는 리눅스 환경(Debian, Ubuntu 등)에서는 기본 메모리 할당자(allocator)가 멀티 스레드 환경에서 메모리를 반환하지 않고 붙들고 있는 '메모리 파편화(Fragmentation)' 이슈가 고질적으로 발생하곤 합니다. 2. 해결을 위한 두 가지 설정 공식 문서와 여러 트러블슈팅 사례를 참고하여 서비스에 아래 두 가지 설정을 적용했습니다. sharp.concurrency(1) Sharp는 기본적으로 CPU 코어 수에 맞춰 멀티 스레드로 동작합니다. 하지만 앞서 언급한 리눅스의 glibc 환경에서는 스레드가 많아질수록 메모리 파편화가 심해지는 경향이 있습니다 공식 문서에서도 jemalloc 같은 별도의 메모리 할당자를 사용하지 않는 리눅스 환경이라면, concurrency를 1로 설정하는 것을 권장하고 있습니다. 스레드 수를 제한함으로써 불필요한 메모리 경합과 점유를 막아주는 것이죠. sharp.cache(false) Sharp는 성능 향상을 위해 처리한 이미지 데이터를 메모리에 캐싱합니다. 하지만 트래픽이 몰리거나 큰 이미지를 반복해서 처리해야 하는 서버 환경에서는 이 캐시가 오히려 독이 되어 메모리 부족(OOM)을 유발할 수 있습니다. sharp.cache(false)를 통해 캐시 기능을 끄면, 매 작업마다 사용한 메모리를 즉시 해제하도록 유도할 수 있어 메모리 사용량을 훨씬 안정적으로 유지할 수 있습니다. 적용 결과 설정 적용 후, 일정하게 상승하던 메모리 그래프가 안정적인 수준에서 유지되는 것을 확인할 수 있었습니다. 물론 concurrency를 1로 줄였기에 CPU 작업 효율은 조금 떨어질 수 있지만, 서버가 메모리 부족으로 다운되는 것보다는 훨씬 안전한 선택이었습니다. 아래는 전/후의 메모리 사용률 그래프입니다. (서로 다른 x,y축 스케일의 캡처입니다. 그래프의 양상만 참고해주세요) Before
- 1more
