관계형 데이터베이스와 Active Record 같은 ORM을 사용하는 웹 앱에서 데이터 및 쿼리 볼륨이 증가하면 성능 문제가 발생할 수 있습니다. 특히 `IN` 절에 많은 값을 포함하는 'Big INs' 쿼리는 목록이 길어질수록 성능이 저하되어 사용자 경험에 부정적 영향을 미치거나 부분적인 서비스 중단을 초래할 수 있습니다. 본문에서는 이 패턴의 발생 원인, 성능 저하 이유, 그리고 프로젝트에서 활용할 수 있는 다양한 대안들을 심층적으로 탐구합니다.
IN
절은 필터링에 사용되지만, 큰 값 목록은 상수로 처리되어 통계 정보가 부족합니다. 이로 인해 PostgreSQL 플래너가 카디널리티를 잘못 추정하여 인덱스 스캔 대신 순차 스캔을 선택하고, 파싱 및 메모리 사용량 증가로 성능이 저하됩니다. 이 패턴은 Active Record의 pluck()
사용이나 includes()
/preload()
와 같은 즉시 로딩 메서드 사용 시 발생할 수 있으며, 특히 N+1
문제 해결 시 IN
절이 포함된 두 번째 쿼리가 생성되어 대규모 데이터에서 문제가 됩니다.
이러한 문제에 대한 대안으로 eager_load
메서드를 사용할 수 있습니다. eager_load
는 LEFT OUTER JOIN
을 사용하여 단일 쿼리를 생성하며, IN
절이 없고 양쪽 테이블의 통계를 활용해 더 정확한 쿼리 계획을 수립합니다. 이는 플래너의 부하를 줄이는 효과가 있습니다. 다른 SQL 대안으로는 ANY
또는 SOME
연산자(ARRAY
와 함께 사용 시 효율적), VALUES
절(IN
목록을 관계처럼 처리), 임시 테이블 생성 후 JOIN
, 그리고 준비된 문장(prepared statements)을 활용한 재사용이 있습니다.
쿼리 최적화를 위해서는 실제 환경과 유사한 데이터에서 EXPLAIN (ANALYZE, BUFFERS)
를 통해 쿼리 실행 계획을 분석하는 것이 필수적입니다. 이를 통해 더 적은 버퍼 접근, 낮은 비용, 적은 행 평가, 적은 루프 횟수로 효율적인 실행을 목표로 해야 합니다. pg_stat_statements
를 통해 문제적인 IN
절 쿼리를 식별할 수 있으나, 유사 쿼리 그룹화에 한계가 있습니다.
PostgreSQL 자체에서도 이러한 문제를 개선하기 위한 노력이 진행 중입니다. PostgreSQL 17(2024년)에서는 스칼라 표현식 및 인덱스 처리 효율성 개선으로 IN
절 성능이 향상되었으며, PostgreSQL 18(2025년)에서는 VALUES
절의 자동 변환 및 pg_stat_statments
의 쿼리 그룹화 개선이 예정되어 있습니다. Ruby on Rails 측에서도 IN
대신 ANY
를 사용하여 그룹화 문제를 해결하려는 작업이 진행 중입니다.
본문에서는 'Big INs'와 같이 크고 비효율적인 `IN` 절 목록이 포함된 쿼리 패턴의 문제점을 다루었습니다. 이러한 패턴은 직접적인 코드 작성 또는 ORM 메서드 사용을 통해 코드베이스에 존재할 수 있으며, 파싱, 계획, 실행에 리소스를 소모하며 `JOIN` 연산에 비해 인덱싱 옵션이 적어 성능이 저하됩니다. `pg_stat_statements`를 활용하여 문제적 쿼리를 찾아내고, 가능하면 `JOIN`으로 변환하거나, `ANY` 연산자, `VALUES` 절, 준비된 문장을 사용하는 등의 대안을 고려하는 것이 중요합니다. 이 글을 통해 데이터베이스 성능 문제를 야기하는 'Big INs'를 만났을 때, 쿼리를 재구성하고 최적화하는 데 더 잘 대비할 수 있기를 바랍니다.