이 문서에서는 Rails 애플리케이션의 성능을 최적화하기 위한 여러 기법을 다룹니다.
뷰 렌더링 최적화
- 컬렉션 렌더링 활용: 루프 내에서 파셜을 개별적으로 호출하는 대신
render partial: "partial", collection: records방식을 사용하면 파셜 조회가 한 번만 발생하고 단일 계측이 생성되어 속도가 훨씬 빠릅니다. - 컬렉션 캐싱:
render partial: "partial", collection: records, cached: true옵션을 사용하면read_multi를 통해 모든 프래그먼트가 단일 호출로 검색되어 루프 내 캐싱보다 효율적입니다. - 작은 파셜 지양: 템플릿 조회 오버헤드 때문에 작은 파셜을 자주 호출하는 것은 성능 저하를 유발할 수 있습니다. 대신 헬퍼(helper)나 뷰 컴포넌트(ViewComponent)를 고려하는 것이 좋습니다. 벤치마크 결과, 헬퍼의 문자열 보간 방식이 가장 빠르며, ViewComponent도 파셜보다 훨씬 빠릅니다.
- 콘텐츠 지연 로딩:
IntersectionObserver또는Turbo-frame의loading="lazy"옵션을 사용하여 사용자가 실제로 볼 가능성이 낮은 페이지 영역(예: 댓글 섹션)을 필요할 때만 로드하여 불필요한 요청을 줄일 수 있습니다.
데이터베이스 및 백그라운드 작업 효율화
- 대량 레코드 생성:
Model#create는 각 레코드마다BEGIN,INSERT,COMMIT의 세 번의 DB 왕복을 유발합니다. 대신User.insert_all(array_of_attributes)를 사용하면 단일INSERT쿼리로 N개의 레코드를 생성하여 네트워크 지연을 크게 줄일 수 있습니다. 단, 이 방식은 유효성 검사나 콜백을 건너뜁니다. - 연관 관계 포함 대량 임포트:
activerecord-import젬을 활용하면 연관 관계가 있는 레코드들도 테이블당 단일 쿼리로 대량 임포트할 수 있어create루프보다 훨씬 빠릅니다. - Redis 파이프라이닝: Redis 명령을 개별적으로 보내는 대신
redis.pipelined블록을 사용하면 여러 명령을 단일 네트워크 왕복으로 전송하여 네트워크 지연 시간을 최소화할 수 있습니다. - 대량 작업 큐잉:
ActiveJob::Base#perform_later를 루프 내에서 사용하는 대신ActiveJob.perform_all_later(jobs)를 사용하면 모든 작업을 단일 단계로 큐잉하여 3배 이상 빠르게 처리할 수 있습니다. 또한, 메모리 사용량을 줄이기 위해 전체 ActiveRecord 인스턴스 대신 ID만 전달하는 것이 효율적입니다.
모델 및 배열 검색 로직 개선
- 모델 메서드에서 관계 반환: 모델 메서드가 레코드나 관계를 반환할 때마다 SQL 쿼리가 트리거될 수 있습니다.
has_one :active_subscription, -> { active }, class_name: "Subscription"과 같이 관계를 정의하면 관계가 캐시되어reload호출 전까지는 추가 쿼리 없이 재사용됩니다. Array#bsearch활용:Array#find는 배열의 크기에 비례하여 선형적으로 느려지지만, 정렬된 배열에서는Array#bsearch가 이진 탐색을 통해 O(log n)의 복잡도로 훨씬 빠르게 데이터를 찾을 수 있습니다. 이는 대량의 데이터에서 특정 값을 효율적으로 검색하는 데 매우 유용합니다.