실행 계획에서보고 된 인덱스 크기와 버퍼 수 사이의 큰 불일치


10

문제

우리는 같은 쿼리를

SELECT COUNT(1) 
  FROM article
  JOIN reservation ON a_id = r_article_id 
 WHERE r_last_modified < now() - '8 weeks'::interval 
   AND r_group_id = 1 
   AND r_status = 'OPEN';

시간 초과 (10 분 후)가 자주 발생함에 따라 문제를 조사하기로 결정했습니다.

EXPLAIN (ANALYZE, BUFFERS)출력은 다음과 같습니다 :

 Aggregate  (cost=264775.48..264775.49 rows=1 width=0) (actual time=238960.290..238960.291 rows=1 loops=1)
   Buffers: shared hit=200483 read=64361 dirtied=666 written=8, temp read=3631 written=3617
   I/O Timings: read=169806.955 write=0.154
   ->  Hash Join  (cost=52413.67..264647.65 rows=51130 width=0) (actual time=1845.483..238957.588 rows=21644 loops=1)
         Hash Cond: (reservation.r_article_id = article.a_id)
         Buffers: shared hit=200483 read=64361 dirtied=666 written=8, temp read=3631 written=3617
         I/O Timings: read=169806.955 write=0.154
         ->  Index Scan using reservation_r_article_id_idx1 on reservation  (cost=0.42..205458.72 rows=51130 width=4) (actual time=34.035..237000.197 rows=21644 loops=1)
               Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
               Rows Removed by Filter: 151549
               Buffers: shared hit=200193 read=48853 dirtied=450 written=8
               I/O Timings: read=168614.105 write=0.154
         ->  Hash  (cost=29662.22..29662.22 rows=1386722 width=4) (actual time=1749.392..1749.392 rows=1386814 loops=1)
               Buckets: 32768  Batches: 8  Memory Usage: 6109kB
               Buffers: shared hit=287 read=15508 dirtied=216, temp written=3551
               I/O Timings: read=1192.850
               ->  Seq Scan on article  (cost=0.00..29662.22 rows=1386722 width=4) (actual time=23.822..1439.310 rows=1386814 loops=1)
                     Buffers: shared hit=287 read=15508 dirtied=216
                     I/O Timings: read=1192.850
 Total runtime: 238961.812 ms

병목 현상 노드는 분명히 인덱스 스캔입니다. 인덱스 정의를 보자.

CREATE INDEX reservation_r_article_id_idx1 
    ON reservation USING btree (r_article_id)
 WHERE (r_status <> ALL (ARRAY['FULFILLED', 'CLOSED', 'CANCELED']));

크기와 행 번호

\di+실제 파일에서 보고 하거나 실제 파일을 방문하여 크기 는 36MB입니다. 예약은 일반적으로 위에 나열되지 않은 모든 상태에서 비교적 짧은 시간 만 소비하므로 업데이트가 많이 발생하므로 인덱스가 부풀어 오릅니다 (약 24MB가 낭비 됨). 그러나 여전히 크기는 비교적 작습니다.

reservation표는 약 40 만 행을 포함, 크기가 3.8 GB에 관한 것입니다. 아직 닫히지 않은 예약 수는 약 170,000입니다 (정확한 수는 위의 인덱스 스캔 노드에보고 됨).

놀랍게도 : 인덱스 스캔은 대량의 버퍼 (8kb 페이지)를 가져 오는 것을보고합니다.

Buffers: shared hit=200193 read=48853 dirtied=450 written=8

캐시 및 디스크 (또는 OS 캐시)에서 읽은 숫자는 최대 1.9GB입니다.

최악의 시나리오

반면에 최악의 시나리오는 모든 튜플이 테이블의 다른 페이지에있을 때 방문 (21644 + 151549) + 4608 페이지 (테이블에서 가져온 총 행과 실제 페이지의 색인 페이지 번호)를 설명합니다. 크기). 이것은 여전히 ​​180,000 미만으로 관찰 된 거의 250,000보다 훨씬 낮습니다.

흥미롭고 중요한 것은 디스크 읽기 속도가 약 2.2MB / s이며 이는 매우 정상적인 것입니다.

그래서 무엇?

이 불일치가 어디에서 오는지 아는 사람이 있습니까?

참고 : 명확하게하기 위해 여기에서 개선 / 변경해야 할 아이디어가 있지만 실제로 얻은 숫자를 이해하고 싶습니다. 이것이 질문입니다.

업데이트 : 캐싱 또는 미세 진공 효과 확인

를 기반으로 jjanes의 대답 , 내가 바로 동일한 쿼리를 다시 실행하면 어떻게되는지 확인했습니다. 영향을받는 버퍼 수는 실제로 변경되지 않습니다. (이를 수행하기 위해 쿼리를 최소한으로 단순화하여 여전히 문제를 보여줍니다.) 이것은 첫 번째 실행에서 볼 수있는 것입니다.

 Aggregate  (cost=240541.52..240541.53 rows=1 width=0) (actual time=97703.589..97703.590 rows=1 loops=1)
   Buffers: shared hit=413981 read=46977 dirtied=56
   I/O Timings: read=96807.444
   ->  Index Scan using reservation_r_article_id_idx1 on reservation  (cost=0.42..240380.54 rows=64392 width=0) (actual time=13.757..97698.461 rows=19236 loops=1)
         Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
         Rows Removed by Filter: 232481
         Buffers: shared hit=413981 read=46977 dirtied=56
         I/O Timings: read=96807.444
 Total runtime: 97703.694 ms

그리고 두 번째 후 :

 Aggregate  (cost=240543.26..240543.27 rows=1 width=0) (actual time=388.123..388.124 rows=1 loops=1)
   Buffers: shared hit=460990
   ->  Index Scan using reservation_r_article_id_idx1 on reservation  (cost=0.42..240382.28 rows=64392 width=0) (actual time=0.032..385.900 rows=19236 loops=1)
         Filter: ((r_group_id = 1) AND (r_status = 'OPEN') AND (r_last_modified < (now() - '56 days'::interval)))
         Rows Removed by Filter: 232584
         Buffers: shared hit=460990
 Total runtime: 388.187 ms

1
아마 관련이 없지만 가입해야 article합니까? 관련된 모든 열이 reservation테이블에 있고 FK가 있다고 가정하면 결과는 동일해야합니다.
ypercubeᵀᴹ

아주 좋은 질문입니다. 그리고 당신은 옳습니다, 그것은 필요하지 않습니다-이것은 다른 팀의 모니터링에 사용되는 쿼리입니다. 아직도, 적어도 쿼리 계획을 보면, 다른 모든 것은 그 불쾌한 인덱스 스캔에 장식
일뿐입니다.

1
조인을 제거해도 큰 차이는 없습니다. 과장된 인덱스 스캔은 그대로 유지됩니다.
dezso

토스트 테이블 액세스? 나는 당신이 보여주는 기둥 중 어느 것이 토스트 될지 의심합니다. 테스트 목적으로 데이터베이스의 유휴 복제본이있는 경우 데이터베이스를 실행 pg_stat_reset()한 다음 쿼리를 실행 한 다음 pg_statio_user_tables블록의 특성을 파악할 수 있습니다 .
jjanes

답변:


4

여기서 핵심은 많은 업데이트와 인덱스의 팽창이라고 생각합니다.

색인에는 더 이상 '실시간'이 아닌 테이블의 행에 대한 포인터가 포함됩니다. 업데이트 된 행의 이전 버전입니다. 이전 행 버전은 오래된 스냅 샷으로 쿼리를 만족시키기 위해 잠시 동안 유지 된 다음, 필요한 것보다 더 자주 제거하는 작업을 수행하려는 사람이 없기 때문에 더 오래 유지됩니다.

인덱스를 스캔 할 때이 행을 방문한 다음 더 이상 표시되지 않으므로 무시합니다. explain (analyze,buffers)문이 행을 검사하는 과정에서 / 히트를 읽어 버퍼의 계산 통하지 않고, 명시 적으로이 활동에보고하지 않습니다.

btrees에 대한 "microvacuum"코드가 있는데, 스캔이 다시 인덱스로 돌아올 때 추적 한 포인터가 더 이상 존재하지 않음을 기억하고 인덱스에서 죽은 것으로 표시합니다. 이렇게하면 다음에 실행되는 유사한 쿼리가 쿼리를 다시 추적 할 필요가 없습니다. 따라서 정확히 동일한 쿼리를 다시 실행하면 버퍼 액세스가 예상 한 값에 가깝게 떨어질 수 있습니다.

또한 VACUUM테이블을 더 자주 사용할 수 있으며 , 이는 부분 인덱스가 아닌 테이블 자체에서 죽은 튜플을 정리합니다. 일반적으로 회전율 부분 지수가 높은 테이블은 기본 수준보다 더 강한 진공의 이점을 얻을 수 있습니다.


내 편집 내용을 참조하십시오-저에게는 마이크로 진공 청소기가 아닌 캐싱처럼 보입니다.
dezso

새 숫자는 기존 숫자와 거의 다르므로 (대략 두 배) 인덱스 스캔을 위해 필터링 된 실제 행과 행의 새 숫자도 보지 않고 의미를 해석하기가 어렵습니다.
jjanes

오늘 계획대로 전체 계획을 추가했습니다. 영향을받는 버퍼 수는 행 수와 마찬가지로 금요일 이후로 많이 증가했습니다.
dezso

오래 지속되는 거래가 있습니까? 그렇다면 인덱스 스캔이 여전히 보이지 않는 행을 추적 할 가능성이 있지만 (추가 버퍼 적중을 유발 함) 더 오래된 사람이 볼 수 있기 때문에 아직 미세 진공 처리 할 수 ​​없습니다. 스냅 사진.
jjanes

나는 아무것도 없다-전형적인 거래는 1 초도 걸리지 않는다. 때때로 몇 초이지만 더 이상은 아닙니다.
dezso
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.