본론에서는 Rails 애플리케이션의 성능을 저해하는 주요 요소들과 그 해결책을 다룹니다.
1. 기본 최적화 기법
-
데이터 페치 최적화:
SELECT *대신 필요한 컬럼만 명시적으로SELECT하여 메모리 사용량과 처리 속도를 개선합니다. -
페이지네이션: 대규모 데이터셋에서는
OFFSET기반 페이지네이션 대신키셋(Keyset) 페이지네이션을 사용하여 일관된 성능을 유지하고 인덱스 활용을 극대화합니다. -
N+1 쿼리 방지: 개발 모드에서 Rails 7+의
strict_loading을 활용하여 N+1 쿼리 발생을 사전에 방지합니다. -
캐싱 전략: 재연산 비용이 캐싱 유지 및 무효화 비용보다 클 경우에만 캐싱을 사용합니다.
2. 메모리 블로트 (Memory Bloat) 및 메모리 누수 (Memory Leak)
- 메모리 블로트: 라이브 객체 수는 적으나 RSS(Resident Set Size)가 비정상적으로 높은 현상으로, 힙 단편화, 할당자 단편화, 할당자의 과도한 메모리 예약, GC의 메모리 유지 등이 원인입니다.
- 진단:
memory_profilerGem을 활용하여 메모리 할당 및 유지 객체를 분석합니다. - 해결: 대량 데이터 처리 시
batches사용, PostgreSQL의row_to_json,json_agg함수를 활용하여 데이터베이스 레벨에서 JSON 생성, CSV 파일 읽기 시line by line또는for_each사용, 루프 내에서 임시 객체 생성 방지 (예:Date.today를 루프 밖에서freeze하여 재사용),include?대신Set을 사용하여 빠른 검색과 객체 생성을 줄임, 레코드별destroy대신delete_all, 레코드별update대신update_all또는upsert_all과 같은 벌크 연산 사용.
- 진단:
- 메모리 누수: RSS가 지속적으로 증가하고 GC가 과도하게 작동하지만 객체가 해제되지 않아 결국 프로세스를 재시작해야 하는 현상입니다.
- 원인: 무제한으로 증가하는 인메모리 객체 (클래스/전역 변수), 큰 객체를 캡처하는 클로저.
- 해결: 클래스/전역 변수 대신 지역 변수 사용, 클로저에 전체 객체 대신 필요한 값만 전달.
3. 데이터베이스 쿼리 최적화
-
쿼리 프로파일링:
EXPLAIN ANALYZE를 사용하여 쿼리 실행 계획과 시간을 분석합니다. -
인덱스 활용: 쿼리 조건에 맞는 부분 인덱스(Partial Index)를 활용하여 성능을 크게 향상시킬 수 있습니다.
- PostgreSQL 고급 기능 활용:
- Materialized Views: 정적 데이터나 복잡한 조인 결과를 미리 계산하여 저장함으로써 반복적인 연산을 줄입니다.
- TS Vectors 및 GIN 인덱스: 다중 컬럼에 대한 전문 검색(Full-Text Search)을 효율적으로 수행합니다.
- GIN 인덱스: JSONB 컬럼의 빠른 검색 및 조회를 가능하게 합니다.
- Sidekiq 최적화: 지연 시간 기반 큐잉(Latency-based Queuing)을 통해 Sidekiq 작업을 자동 스케일링하여 성능을 관리합니다.