웹 애플리케이션의 백엔드 작업 시 ORM(예: Active Record)이나 외부 라이브러리가 예상보다 많은 쿼리를 생성하거나, 개발자가 인지하지 못하는 사이에 쿼리가 중복되는 경우가 빈번하게 발생합니다. 이러한 과도한 쿼리는 성능 저하의 주요 원인이 됩니다. 첫째, 데이터베이스 서버는 max_connections
와 같은 설정에 따라 클라이언트로부터 받을 수 있는 TCP 연결 수에 하드웨어적인 상한선을 가지고 있습니다. 쿼리 수가 많아지면 이 제한을 초과할 위험이 커지며, 이는 애플리케이션의 안정성에 직접적인 영향을 미칩니다. 둘째, 메모리 사용량 측면에서 Rails는 기본적으로 SQL 캐시를 활성화하여 반복 쿼리에 대한 왕복 시간을 절약하지만, 이 과정에서 메모리 사용량이 증가할 수 있습니다. Rails 7.1부터는 LRU(Least Recently Used) 알고리즘을 사용하여 캐시된 쿼리를 관리하며, 기본적으로 100개의 쿼리를 캐시하도록 설정되어 있습니다.
SQL 쿼리 수를 모니터링하는 방법은 Rails 버전에 따라 다릅니다. Rails 7.2 이전 버전에서는 query_count
gem을 사용하여 SQL Queries: 100 (50 cached)
와 같이 Rails 로그에서 쿼리 수를 확인할 수 있었습니다. 반면, Rails 7.2부터는 쿼리 수 카운트 기능이 내장되어 ActiveRecord: 105.5ms (10 queries, 1 cached)
와 같이 표시되므로 별도의 gem이 필요하지 않습니다.
성능 최적화를 위해서는 단순히 캐시를 활용하는 것을 넘어 반복 쿼리 자체를 제거하는 것이 중요합니다. 이를 위한 전략으로는 데이터 접근 방식을 재구성하거나, 메모이제이션(memoization)을 활용하여 컨트롤러 액션 처리 기간 동안 계산된 결과를 저장하고 재사용하는 방법이 있습니다. 쿼리의 소스 코드 위치를 파악하는 것도 핵심입니다. 느린 API 엔드포인트를 식별한 후 개발 환경에서 모니터링을 시작하고, Marginalia 및 Query Logs와 같은 도구를 사용하여 데이터베이스 쿼리가 발생하는 정확한 소스 위치를 찾아야 합니다. 이를 통해 공유 가능한 데이터 접근 방식을 파악하고 통합할 수 있습니다.
“많은 쿼리”에 대한 명확한 기준은 없지만, 중복 쿼리는 반드시 제거해야 할 대상입니다. Rails 로그에서 Book Load
와 같은 반복적인 패턴이 나타난다면, 동일한 데이터를 다른 소스 코드 위치에서 반복적으로 접근하는 경우일 수 있습니다. 이러한 경우, 기존에 로드된 컬렉션을 활용하거나 메모이제이션을 통해 이전에 계산된 결과를 재사용하는 방식으로 데이터 로드를 통합하고 최적화할 수 있습니다. 필자의 경험에 따르면, 이러한 단계를 거쳐 250개 이상의 SQL 쿼리가 발생하던 컨트롤러 액션을 50개 이하로 줄일 수 있었습니다. 이는 로그 모니터링, 자체 코드, ORM, 라이브러리(gem), Rails 컨트롤러 액션의 ‘before filters’ 등 다양한 소스에서 발생하는 쿼리 코드를 파악하고, 불필요한 쿼리를 제거하며 유사한 쿼리를 통합하는 과정을 통해 달성 가능합니다. 궁극적으로는 클라이언트가 필요로 하는 최소한의 데이터(테이블, 행, 열)만 접근하도록 데이터 로직을 재구성하는 것이 중요합니다.