Beenslab Blog

예상과 달랐던 PostgreSQL 인덱스 사용 — 왜 인덱스를 타지 않았을까?

beenchangseo·2024년 12월 3일Hits

🔍 예상과 달랐던 PostgreSQL 인덱스 사용 — 왜 인덱스를 타지 않았을까?

느려진 쿼리, 의외의 원인

최근 운영 중인 시스템에서 Datadog APM을 통해 슬로우 쿼리를 발견했다. 아래처럼 간단한 조건과 LIMIT 1만 있는 쿼리였다.

SELECT *
FROM user_wallets
WHERE network_type = 'mainnet'
  AND account_type = 'user'
  AND is_blocked = false
  AND chain = 'BTC'
  AND is_active = true
LIMIT 1

하지만 EXPLAIN ANALYZE 결과를 보니, 예상과 달리 Index Scan이 아닌 Seq Scan(전체 테이블 스캔) 이 발생하고 있었다.

-> Seq Scan on user_wallets
   Filter: ((NOT is_blocked) AND is_active AND ...)
   Rows Removed by Filter: 4,247,359
   Execution Time: 958ms

쿼리 결과는 단 1건만 필요했지만, 전체 400만 건이 넘는 데이터를 스캔하면서 조건에 맞는 결과를 찾고 있었던 것이다.


❓ 인덱스가 왜 안 타졌을까?

해당 테이블에는 아래와 같은 인덱스가 존재하고 있었다.

CREATE INDEX idx_chain_active_wallets
ON user_wallets (chain, is_active)
WHERE network_type = 'mainnet'
  AND account_type = 'user'
  AND is_blocked = false;

조건도 잘 맞고, LIMIT 1도 붙어 있었기 때문에 인덱스를 사용할 줄 알았지만... PostgreSQL은 인덱스를 사용하지 않았다.

그 이유는 바로:

"정렬 기준이 명확하지 않았기 때문"이다.


✅ 해결 방법: ORDER BY로 정렬 기준을 알려주자

PostgreSQL 입장에서 LIMIT 1이 있다면 "어떤 순서로 정렬해서 가장 앞의 1건을 가져올지" 를 알아야 한다.

그렇지 않으면 전체를 다 스캔한 뒤 조건에 맞는 1건을 찾아낼 수밖에 없다.

따라서 아래와 같이 ORDER BY를 명시해 정렬 기준을 알려주면 인덱스를 활용할 수 있게 된다.

SELECT *
FROM user_wallets
WHERE network_type = 'mainnet'
  AND account_type = 'user'
  AND is_blocked = false
  AND chain = 'BTC'
  AND is_active = true
ORDER BY is_active DESC
LIMIT 1;

결과:

-> Index Scan Backward using idx_chain_active_wallets on user_wallets
   Index Cond: ((chain = 'BTC') AND (is_active = true))
   Execution Time: 0.042ms

불과 1초 가까이 걸리던 쿼리0.04ms로 개선됐다.


🔁 ASC vs DESC: 정렬 방향이 성능에 영향을 미친 이유

BOOLEAN 타입은 PostgreSQL에서 기본적으로 false → true 순서(ASC)로 정렬된다.

예를 들어,

  • ORDER BY is_active ASC: false부터 시작해서 true를 찾음
  • ORDER BY is_active DESC: true부터 시작해서 첫 번째 true를 바로 찾음

위 쿼리에서는 is_active = true 조건을 포함하고 있기 때문에, DESC 정렬이 훨씬 유리했다.

요약하면:

정렬 방식인덱스 탐색 방향결과를 언제 찾는가
ASCForward Scanfalse부터 → true까지 가야 함
DESCBackward Scantrue부터 바로 찾음

마무리하며

LIMIT 1이 있는 단순 조회 쿼리에서도, 인덱스를 제대로 타지 않으면 성능이 수백 배 이상 느려질 수 있다.

이번 케이스를 통해 얻은 교훈은 아래와 같다.

✅ 꼭 점검하자!

  • 인덱스가 있어도 정렬 기준이 없으면 전체 스캔이 일어날 수 있다.
  • ORDER BY를 추가해서 정렬 방향을 명확히 지정해주자.
  • BOOLEAN 컬럼은 false → true로 정렬되며, true를 빠르게 찾고 싶다면 DESC를 써야 한다.

이런 식으로 사소해 보이는 쿼리도 인덱스 사용 방식과 정렬 방향에 따라 성능 차이가 엄청날 수 있다.

Datadog 같은 APM 도구로 슬로우 쿼리를 발견하고 원인을 분석해보는 습관은 꼭 들여두자.