Ruby on Rails 애플리케이션에서 SQL 쿼리 수 추적 및 최적화

Tip: Put your Rails app on a SQL query diet | Software Engineer, Author, High Performance PostgreSQL for Rails

작성자
Ruby Weekly
발행일
2025년 05월 29일

핵심 요약

  • 1 웹 애플리케이션의 성능은 SQL 쿼리 수에 크게 좌우되므로, 불필요하고 중복된 쿼리를 최소화하는 것이 중요합니다.
  • 2 Rails 7.2부터는 SQL 쿼리 수가 내장되어 표시되며, 이전 버전에서는 `query_count` gem을 사용하여 쿼리 수를 모니터링할 수 있습니다.
  • 3 과도한 쿼리 수를 줄이기 위해 반복 쿼리 제거, 데이터 접근 방식 재구성, 메모이제이션 활용 등 다양한 최적화 전략을 적용해야 합니다.

도입

웹 애플리케이션, 특히 Ruby on Rails 기반 애플리케이션에서 HTTP 요청을 처리하는 데 소요되는 시간의 상당 부분은 SQL 쿼리 실행에 할애됩니다. 이는 데이터베이스 엔진이 쿼리를 파싱하고 실행 계획을 수립하며, 이를 실행한 후 결과를 클라이언트에 전송하는 일련의 과정에서 발생하는 오버헤드 때문입니다. 또한, 클라이언트는 수신된 응답을 메모리 내의 애플리케이션 객체로 변환하는 추가 작업을 수행해야 합니다. 따라서 웹 애플리케이션의 성능을 최적화하기 위해서는 불필요하고 중복된 쿼리를 회피하고, 가능한 한 적은 수의 쿼리를 수행하는 것이 필수적입니다. 본문에서는 애플리케이션 작업에서 발생하는 SQL 쿼리 수를 효과적으로 파악하고, 이를 최적화하기 위한 구체적인 방법론을 제시합니다.

웹 애플리케이션의 백엔드 작업 시 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’ 등 다양한 소스에서 발생하는 쿼리 코드를 파악하고, 불필요한 쿼리를 제거하며 유사한 쿼리를 통합하는 과정을 통해 달성 가능합니다. 궁극적으로는 클라이언트가 필요로 하는 최소한의 데이터(테이블, 행, 열)만 접근하도록 데이터 로직을 재구성하는 것이 중요합니다.

결론

결론적으로, Ruby on Rails 애플리케이션의 성능은 SQL 쿼리 최적화에 크게 의존합니다. Rails 버전별로 제공되는 쿼리 수 추적 기능을 적극적으로 활용하고, 불필요하거나 중복되는 쿼리를 제거하는 것이 제한된 시스템 리소스를 효율적으로 사용하고 애플리케이션의 확장성을 확보하는 핵심입니다. 반복 쿼리를 제거하고, 데이터 접근 방식을 재구성하며, 메모이제이션과 같은 기법을 적용하여 쿼리 수를 최소화해야 합니다. 또한, 클라이언트 애플리케이션의 실제 사용 사례에 필요한 데이터만을 접근하도록 데이터 로직을 정교하게 다듬는 것이 중요합니다. 이러한 SQL 쿼리 최적화 노력은 전반적인 웹 애플리케이션의 응답 시간을 단축하고 사용자 경험을 향상시키는 데 결정적인 기여를 할 것입니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!