Ketidakcocokan besar antara ukuran indeks yang dilaporkan dan jumlah buffer dalam rencana eksekusi


10

Masalah

Kami memiliki pertanyaan seperti

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';

Karena mengalami timeout (setelah 10 menit) lebih sering daripada tidak, saya memutuskan untuk menyelidiki masalah ini.

The EXPLAIN (ANALYZE, BUFFERS)Output terlihat seperti ini:

 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

Simpul bottleneck jelas merupakan pemindaian indeks. Jadi mari kita lihat definisi indeks:

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

Ukuran dan nomor baris

Ukurannya (dilaporkan oleh \di+atau dengan mengunjungi file fisik) adalah 36 MB. Karena pemesanan biasanya hanya menghabiskan waktu yang relatif singkat di semua status yang tidak tercantum di atas, ada banyak pembaruan yang terjadi, sehingga indeksnya cukup besar (sekitar 24 MB terbuang di sini) - masih, ukurannya relatif kecil.

The reservationtabel sekitar 3,8 GB dalam ukuran, yang mengandung sekitar 40 juta baris. Jumlah pemesanan yang belum ditutup adalah sekitar 170.000 (jumlah persisnya dilaporkan dalam simpul pemindaian indeks di atas).

Sekarang yang mengejutkan: laporan pemindaian indeks mengambil sejumlah besar buffer (yaitu, 8 kb halaman):

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

Angka-angka yang dibaca dari cache dan disk (atau cache OS) menambahkan hingga 1,9 GB!

Skenario terburuk

Di sisi lain, skenario terburuk, ketika setiap tuple duduk di halaman berbeda dari tabel, akan menjelaskan kunjungan (21644 + 151549) + 4608 halaman (total baris diambil dari tabel ditambah nomor halaman indeks dari fisik) ukuran). Ini masih hanya di bawah 180.000 - jauh di bawah yang diamati hampir 250.000.

Menarik (dan mungkin penting) adalah bahwa kecepatan baca disk adalah sekitar 2,2 MB / s, yang cukup normal, saya kira.

Terus?

Adakah yang tahu dari mana perbedaan ini bisa terjadi?

Catatan: Untuk lebih jelasnya, kami memiliki ide tentang apa yang harus diperbaiki / diubah di sini, tetapi saya benar-benar ingin memahami angka yang saya dapatkan - inilah pertanyaannya.

Pembaruan: memeriksa efek caching atau microvacuuming

Berdasarkan jawaban jjanes , saya telah memeriksa apa yang terjadi ketika saya langsung menjalankan permintaan yang sama persis. Jumlah buffer yang terpengaruh tidak benar-benar berubah. (Untuk melakukan ini, saya menyederhanakan kueri ke minimumnya yang masih menunjukkan masalah.) Inilah yang saya lihat dari jalankan pertama:

 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

dan setelah yang kedua:

 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
Mungkin tidak relevan tetapi apakah Anda perlu bergabung article? Sepertinya semua kolom yang terlibat berasal dari reservationtabel dan (dengan asumsi) ada FK, hasilnya harus sama.
ypercubeᵀᴹ

Itu pertanyaan yang sangat bagus. Dan Anda benar, itu tidak diperlukan - ini adalah permintaan yang digunakan dalam pemantauan oleh tim lain. Namun, setidaknya melihat rencana permintaan, yang lainnya hanyalah dekorasi untuk pemindaian indeks jahat :)
dezso

1
Izinkan saya menambahkan bahwa menghapus gabungan tidak membuat perbedaan besar - pemindaian indeks yang berlebihan tetap ada.
dezso

Akses tabel roti panggang? Meskipun saya ragu salah satu kolom yang Anda tunjukkan di sana akan bersulang. Jika Anda memiliki klon idle dari database untuk tujuan pengujian, Anda bisa menjalankannya pg_stat_reset(), dan kemudian menjalankan kueri, dan kemudian melihat pg_statio_user_tableske dalam untuk melihat di mana itu atribut blok.
jjanes

Jawaban:


4

Saya pikir kuncinya di sini adalah banyak pembaruan, dan mengasapi indeks.

Indeks berisi pointer ke baris dalam tabel yang tidak lagi 'hidup'. Ini adalah versi lama dari baris yang diperbarui. Versi baris lama disimpan untuk sementara waktu, untuk memenuhi permintaan dengan snapshot lama, dan kemudian disimpan untuk sementara waktu lebih karena tidak ada yang ingin melakukan pekerjaan menghapusnya lebih sering daripada yang diperlukan.

Saat memindai indeks, ia harus mengunjungi baris-baris ini, dan kemudian memperhatikan bahwa mereka tidak lagi terlihat, jadi abaikan saja. The explain (analyze,buffers)pernyataan tidak melaporkan kegiatan ini secara eksplisit, kecuali melalui penghitungan buffer membaca / hit dalam proses memeriksa baris ini.

Ada beberapa kode "microvacuum" untuk btrees, sehingga ketika pemindaian kembali ke indeks lagi, ia mengingat bahwa penunjuk yang dikejar tidak lagi hidup, dan menandainya sudah mati dalam indeks. Dengan begitu permintaan serupa berikutnya yang dijalankan tidak perlu mengejarnya lagi. Jadi, jika Anda menjalankan kueri yang sama persis lagi, Anda mungkin akan melihat akses buffer turun mendekati apa yang Anda prediksi.

Anda juga dapat VACUUMmembuat tabel lebih sering, yang akan membersihkan tupel mati dari tabel itu sendiri, bukan hanya dari indeks parsial. Secara umum, tabel dengan indeks parsial turn-over tinggi cenderung mendapat keuntungan dari kekosongan yang lebih agresif daripada tingkat standar.


Silakan lihat hasil edit saya - bagi saya, sepertinya caching, bukan microvacuuming.
dezso

Angka baru Anda jauh berbeda dari yang lama (kira-kira dua kali lipat), sehingga sulit untuk menafsirkan apa artinya tanpa melihat juga angka baru untuk baris dan baris aktual yang difilter untuk pemindaian indeks.
jjanes

Menambahkan paket lengkap seperti yang terlihat hari ini. Jumlah buffer yang terpengaruh tumbuh banyak sejak hari Jumat, begitu pula jumlah baris.
dezso

Apakah Anda memiliki transaksi jangka panjang yang berkeliaran? Jika demikian, ada kemungkinan bahwa pemindaian indeks masih melacak baris-baris yang tidak terlihat olehnya (yang menyebabkan tambahan buffer hit), tetapi itu tidak dapat membuat microvacuum mereka pergi karena mereka mungkin terlihat oleh orang lain dengan yang lebih tua foto.
jjanes

Saya tidak punya - transaksi tipikal memakan waktu kurang dari satu detik. Kadang-kadang beberapa detik, tetapi tidak lebih lama.
dezso
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.