이 글은 다중 테넌트 Rails 애플리케이션 환경에서 직면하는 용량 계획 및 메모리 관리 문제를 다루며, 현재 직면한 문제점과 탐색 중인 해결책, 그리고 최종적으로 구현된 개선 사항을 상세히 설명합니다. 현재 시스템은 8대의 2GB RAM 머신에 70개 이상의 사용자가 분산되어 있으며, 메모리 부족 시 OOM 킬 및 성능 저하가 발생하고 있습니다. 이 문제를 해결하기 위한 다양한 접근 방식이 고려되었습니다.
현재 시스템 구성 및 메모리 관리 노력
-
아키텍처: 각 사용자(댄스 스튜디오)는 여러 이벤트(테넌트)를 가지며, 각 이벤트는 자체 SQLite 데이터베이스와 Rails 애플리케이션 인스턴스를 사용합니다. 모든 테넌트는 특정 머신에 고정되며, 여러 사용자가 동일한 머신을 공유합니다.
-
Puma 설정: 테넌트당 3개의 스레드로 구성되어 있으며, 유휴 상태인 테넌트는 5분 후, 머신은 30분 후 자동으로 서스펜드됩니다.
-
jemalloc 활용: 멀티스레드 Puma 환경에서 메모리 단편화를 줄이기 위해 jemalloc을 메모리 할당자로 사용합니다.
-
Web Socket: Web Socket 연결은 메모리 사용량이 100KB 미만으로, 주요 메모리 우려 사항이 아닙니다.
-
Fly.io 서스펜션: Fly.io의 머신 서스펜드 기능이 잘 작동하여 자원 효율성을 높이고 있습니다.
탐색 중인 해결책
-
머신 증설 및 메모리 축소: 사용자당 더 많은 머신을 할당하고 개별 머신의 메모리를 1GB로 줄이는 방안이 고려되었습니다. 이는 특정 사용자 문제 발생 시 다른 사용자에게 영향을 주지 않는다는 장점이 있지만, 배포 및 관리 오버헤드 증가와 라우팅 복잡성 증가라는 단점이 있습니다.
-
테넌트별 메모리 제한 (cgroups): 초기에는 Fly.io에서 Linux cgroups v2를 통한 테넌트별 메모리 제한 구현에 어려움이 있었습니다. 이 방법은 특정 테넌트가 메모리 한계를 초과했을 때 해당 테넌트만 OOM 킬하고 재시작하여 다른 테넌트에 영향을 주지 않도록 하는 이상적인 해결책입니다.
업데이트: cgroup v1을 통한 테넌트별 메모리 제한 구현 성공
-
문제 진단: Fly.io가 cgroup v2 파일이 존재하지만 실제로는 cgroup v1이 활성화된 하이브리드 구성을 사용하고 있음을 파악했습니다.
-
해결책: Navigator는 이제 cgroup v2에서 메모리가 활성화되지 않은 경우
/sys/fs/cgroup/memory/경로의 cgroup v1으로 자동 폴백하도록 구현되었습니다. -
결과: 이를 통해
memory.limit_in_bytes를 설정하여 테넌트당 512MiB와 같은 개별 메모리 제한을 성공적으로 적용할 수 있게 되었습니다. 이제 특정 테넌트가 메모리 한계를 초과하면 해당 Rails 프로세스만 OOM 킬되고 자동으로 재시작되며, 동일 머신의 다른 테넌트들은 정상적으로 계속 실행됩니다. 이는privileged컨테이너나 호스트 cgroup 네임스페이스 접근 없이 테넌트별 진정한 메모리 격리를 제공합니다.