Caching Indeks PostgreSQL

16

Saya mengalami kesulitan menemukan penjelasan 'awam' tentang bagaimana indeks di-cache di PostgreSQL, jadi saya ingin cek kenyataan pada salah satu atau semua asumsi ini:

  1. Indeks postgreSQL, seperti baris, hidup di disk tetapi mungkin di-cache.
  2. Indeks mungkin seluruhnya ada dalam cache atau tidak sama sekali.
  3. Apakah itu di-cache atau tidak tergantung pada seberapa sering digunakan (seperti yang didefinisikan oleh perencana kueri).
  4. Untuk alasan ini sebagian besar indeks 'masuk akal' akan berada di cache sepanjang waktu.
  5. Indeks hidup dalam cache yang sama (the buffer cache?) Seperti baris, dan oleh karena itu ruang cache yang digunakan oleh indeks tidak tersedia untuk baris.


Motivasi saya untuk memahami ini mengikuti dari pertanyaan lain saya bertanya di mana disarankan agar indeks parsial dapat digunakan pada tabel di mana sebagian besar data tidak akan pernah diakses.

Sebelum melakukan ini, saya ingin menjelaskan bahwa menggunakan indeks parsial menghasilkan dua keuntungan:

  1. Kami mengurangi ukuran indeks dalam cache, membebaskan lebih banyak ruang untuk baris di cache.
  2. Kami mengurangi ukuran B-Tree, menghasilkan respons permintaan yang lebih cepat.
dukedave
sumber
4
Menggunakan indeks parsial tidak hanya berguna ketika sebagian besar data akan jarang diakses tetapi juga ketika nilai-nilai tertentu sangat umum. Ketika suatu nilai sangat umum, perencana akan menggunakan pemindaian tabel sebagai gantinya indeks sehingga memasukkan nilai dalam indeks tidak memiliki tujuan.
Eelke

Jawaban:

19

Bermain sedikit dengan pg_buffercache , saya bisa mendapatkan jawaban atas beberapa pertanyaan Anda.

  1. Ini cukup jelas, tetapi hasil untuk (5) juga menunjukkan bahwa jawabannya adalah YA
  2. Saya belum menyiapkan contoh yang baik untuk ini, untuk saat ini lebih ya daripada tidak :) (Lihat hasil edit saya di bawah, jawabannya adalah TIDAK .)
  3. Karena perencana adalah siapa yang memutuskan apakah akan menggunakan indeks atau tidak, kita dapat mengatakan YA , itu memutuskan caching (tapi ini lebih rumit)
  4. Detail persis caching dapat diturunkan dari kode sumber, saya tidak bisa menemukan terlalu banyak tentang topik ini, kecuali yang ini (lihat jawaban penulis juga). Namun, saya cukup yakin bahwa ini lagi jauh lebih rumit daripada ya atau tidak yang sederhana. (Sekali lagi, dari edit saya, Anda bisa mendapatkan beberapa gagasan - karena ukuran cache terbatas, indeks 'masuk akal' itu bersaing untuk mendapatkan ruang yang tersedia. Jika terlalu banyak, mereka akan saling menendang dari cache - jadi jawabannya adalah TIDAK . )
  5. Sebagai permintaan sederhana dengan pg_buffercachemenunjukkan, jawabannya adalah YA yang pasti . Perlu dicatat bahwa data tabel sementara tidak di -cache di sini.

EDIT

Saya telah menemukan artikel hebat Jeremiah Peschka tentang penyimpanan tabel dan indeks. Dengan informasi dari sana, saya bisa menjawab (2) juga. Saya membuat tes kecil, jadi Anda bisa memeriksanya sendiri.

-- we will need two extensions
CREATE EXTENSION pg_buffercache;
CREATE EXTENSION pageinspect;


-- a very simple test table
CREATE TABLE index_cache_test (
      id serial
    , blah text
);


-- I am a bit megalomaniac here, but I will use this for other purposes as well
INSERT INTO index_cache_test
SELECT i, i::text || 'a'
FROM generate_series(1, 1000000) a(i);


-- let's create the index to be cached
CREATE INDEX idx_cache_test ON index_cache_test (id);


-- now we can have a look at what is cached
SELECT c.relname,count(*) AS buffers
FROM 
    pg_class c 
    INNER JOIN pg_buffercache b ON b.relfilenode = c.relfilenode 
    INNER JOIN pg_database d ON (b.reldatabase = d.oid AND d.datname = current_database())
GROUP BY c.relname
ORDER BY 2 DESC LIMIT 10;

             relname              | buffers
----------------------------------+---------
 index_cache_test                 |    2747
 pg_statistic_relid_att_inh_index |       4
 pg_operator_oprname_l_r_n_index  |       4
... (others are all pg_something, which are not interesting now)

-- this shows that the whole table is cached and our index is not in use yet

-- now we can check which row is where in our index
-- in the ctid column, the first number shows the page, so 
-- all rows starting with the same number are stored in the same page
SELECT * FROM bt_page_items('idx_cache_test', 1);

 itemoffset |  ctid   | itemlen | nulls | vars |          data
------------+---------+---------+-------+------+-------------------------
          1 | (1,164) |      16 | f     | f    | 6f 01 00 00 00 00 00 00
          2 | (0,1)   |      16 | f     | f    | 01 00 00 00 00 00 00 00
          3 | (0,2)   |      16 | f     | f    | 02 00 00 00 00 00 00 00
          4 | (0,3)   |      16 | f     | f    | 03 00 00 00 00 00 00 00
          5 | (0,4)   |      16 | f     | f    | 04 00 00 00 00 00 00 00
          6 | (0,5)   |      16 | f     | f    | 05 00 00 00 00 00 00 00
...
         64 | (0,63)  |      16 | f     | f    | 3f 00 00 00 00 00 00 00
         65 | (0,64)  |      16 | f     | f    | 40 00 00 00 00 00 00 00

-- with the information obtained, we can write a query which is supposed to
-- touch only a single page of the index
EXPLAIN (ANALYZE, BUFFERS) 
    SELECT id 
    FROM index_cache_test 
    WHERE id BETWEEN 10 AND 20 ORDER BY id
;

 Index Scan using idx_test_cache on index_cache_test  (cost=0.00..8.54 rows=9 width=4) (actual time=0.031..0.042 rows=11 loops=1)
   Index Cond: ((id >= 10) AND (id <= 20))
   Buffers: shared hit=4
 Total runtime: 0.094 ms
(4 rows)

-- let's have a look at the cache again (the query remains the same as above)
             relname              | buffers
----------------------------------+---------
 index_cache_test                 |    2747
 idx_test_cache                   |       4
...

-- and compare it to a bigger index scan:
EXPLAIN (ANALYZE, BUFFERS) 
SELECT id 
    FROM index_cache_test 
    WHERE id <= 20000 ORDER BY id
;


 Index Scan using idx_test_cache on index_cache_test  (cost=0.00..666.43 rows=19490 width=4) (actual time=0.072..19.921 rows=20000 loops=1)
   Index Cond: (id <= 20000)
   Buffers: shared hit=4 read=162
 Total runtime: 24.967 ms
(4 rows)

-- this already shows that something was in the cache and further pages were read from disk
-- but to be sure, a final glance at cache contents:

             relname              | buffers
----------------------------------+---------
 index_cache_test                 |    2691
 idx_test_cache                   |      58

-- note that some of the table pages are disappeared
-- but, more importantly, a bigger part of our index is now cached

Secara keseluruhan, ini menunjukkan bahwa indeks dan tabel dapat di-cache halaman demi halaman, oleh karena itu jawaban untuk (2) adalah TIDAK .

Dan yang terakhir untuk menggambarkan tabel sementara tidak di-cache di sini:

CREATE TEMPORARY TABLE tmp_cache_test AS 
SELECT * FROM index_cache_test ORDER BY id FETCH FIRST 20000 ROWS ONLY;

EXPLAIN (ANALYZE, BUFFERS) SELECT id FROM tmp_cache_test ORDER BY id;

-- checking the buffer cache now shows no sign of the temp table
dezso
sumber
1
+1 Jawaban yang sangat bagus. Masuk akal bahwa tabel temp yang hidup dalam RAM tidak di-cache. Saya bertanya-tanya, apakah caching terjadi segera setelah tabel temp menumpahkan ke disk (karena tidak cukup temp_buffers) - untuk seluruh tabel atau hanya bagian pada disk. Saya harapkan yang terakhir. Mungkin ujian yang menarik ..
Erwin Brandstetter
9

Halaman indeks diambil ketika kueri memutuskan mereka akan berguna untuk mengurangi jumlah data tabel yang dibutuhkan untuk menjawab kueri. Hanya blok indeks yang dinavigasi untuk menyelesaikan yang dibaca. Ya, mereka masuk ke kumpulan shared_buffers yang sama di mana data tabel disimpan. Keduanya juga didukung oleh cache sistem operasi sebagai lapisan kedua caching.

Anda dapat dengan mudah memiliki 0,1% dari indeks dalam memori atau 100% dari itu. Gagasan bahwa sebagian besar indeks "masuk akal" akan berada di cache sepanjang waktu "jatuh dengan keras ketika Anda memiliki kueri yang hanya menyentuh sebagian dari tabel. Contoh umum adalah jika Anda memiliki data yang berorientasi waktu. Sering kali mereka biasanya menavigasi ujung meja, jarang melihat sejarah lama. Di sana Anda mungkin menemukan semua blok indeks yang diperlukan untuk menavigasi ke dan di sekitar akhir memori baru-baru ini, sementara sangat sedikit yang diperlukan untuk menavigasi catatan sebelumnya ada di sana.

Bagian rumit dari implementasi bukanlah bagaimana blok masuk ke cache buffer. Itu aturan tentang kapan mereka pergi. My Inside the PostgreSQL Buffer Cache talk dan contoh pertanyaan yang disertakan di sana dapat membantu Anda memahami apa yang terjadi di sana, dan melihat apa yang sebenarnya terakumulasi di server produksi. Ini bisa mengejutkan. Ada banyak lagi tentang semua topik ini di buku PostgreSQL 9.0 Kinerja Tinggi saya juga.

Indeks parsial dapat membantu karena mengurangi ukuran indeks, dan karena itu keduanya lebih cepat untuk menavigasi dan meninggalkan lebih banyak RAM untuk melakukan caching hal-hal lain. Jika navigasi indeks Anda sedemikian rupa sehingga bagian yang Anda sentuh selalu dalam RAM, bagaimanapun, itu mungkin tidak membeli peningkatan nyata sekalipun.

Greg Smith
sumber