Индексное кэширование PostgreSQL

16

У меня возникают трудности с поиском «непрофессиональных» объяснений того, как индексы кэшируются в PostgreSQL, поэтому я хотел бы проверить реальность любого или всех этих предположений:

  1. Индексы PostgreSQL, как и строки, живут на диске, но могут кэшироваться.
  2. Индекс может быть целиком в кеше или не находиться вообще.
  3. Кэшируется он или нет, зависит от того, как часто он используется (как определено планировщиком запросов).
  4. По этой причине самые «разумные» индексы будут постоянно находиться в кеше.
  5. Индексы находятся в том же кэше ( buffer cache?), Что и строки, поэтому пространство, используемое индексом, недоступно для строк.


Моя мотивация для понимания этого вытекает из другого вопроса, который я задал, где предлагалось использовать частичные индексы для таблиц, где к большинству данных никогда не будет доступа.

Прежде чем приступить к этому, я хотел бы пояснить, что использование частичного индекса дает два преимущества:

  1. Мы уменьшаем размер индекса в кеше, освобождая больше места для самих строк в кеше.
  2. Мы уменьшаем размер B-дерева, что приводит к более быстрому ответу на запрос.
dukedave
источник
4
Использование частичного индекса полезно не только в тех случаях, когда к большой части данных обращаются редко, но и в тех случаях, когда определенные значения очень распространены. Когда значение является очень распространенным, планировщик все равно будет использовать сканирование таблицы вместо индекса, поэтому включение значения в индекс не имеет смысла.
Элке

Ответы:

19

Немного поиграв с pg_buffercache , я мог бы получить ответы на некоторые ваши вопросы.

  1. Это вполне очевидно, но результаты для (5) также показывают, что ответ ДА
  2. Мне еще предстоит подать хороший пример для этого, потому что сейчас это скорее да, чем нет :) (См. Мой текст ниже, ответ НЕТ .)
  3. Поскольку планировщик решает, использовать индекс или нет, мы можем сказать ДА , он решает кэширование (но это более сложно)
  4. Точные детали кэширования могут быть получены из исходного кода, я не смог найти слишком много по этой теме, кроме этой (см. Также ответ автора ). Тем не менее, я уверен, что это снова намного сложнее, чем просто да или нет. (Опять же, из моего редактирования вы можете получить некоторое представление - поскольку размер кэша ограничен, эти «разумные» индексы конкурируют за доступное пространство. Если их слишком много, они будут выгружать друг друга из кэша - поэтому ответ скорее НЕТ . )
  5. Как простой запрос с pg_buffercacheшоу, ответ является окончательным ДА . Стоит отметить, что данные временных таблиц здесь не кэшируются.

РЕДАКТИРОВАТЬ

Я нашел потрясающую статью Иеремии Пешки о хранении таблиц и индексов. С информацией оттуда я мог бы также ответить (2) . Я настроил небольшой тест, чтобы вы могли проверить это самостоятельно.

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

В общем, это показывает, что индексы и таблицы могут кэшироваться постранично, поэтому ответ на (2) - НЕТ .

И последнее, чтобы проиллюстрировать временные таблицы, не кэшированные здесь:

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
источник
1
+1 Очень хороший ответ. Имеет смысл, что временные таблицы, живущие в ОЗУ, не кэшируются. Интересно, однако, происходит ли кэширование, как только временные таблицы попадают на диск (из-за отсутствия достаточного количества temp_buffers) - для всей таблицы или только части на диске. Я ожидал бы последнего. Может быть интересным тестом ..
Эрвин Брандштеттер
9

Индексные страницы выбираются, когда запрос решает, что будет полезно сократить объем табличных данных, необходимых для ответа на запрос. Только блоки индекса, по которым осуществляется навигация для выполнения, считываются. Да, они попадают в тот же пул shared_buffers, где хранятся данные таблицы. Оба также поддерживаются кэшем операционной системы как второй уровень кэширования.

Вы можете легко иметь 0,1% индекса в памяти или 100% его. Идея о том, что большинство «разумных» индексов будет постоянно находиться в кэше », не поддается, когда у вас есть запросы, которые касаются только подмножества таблицы. Типичный пример - если у вас есть ориентированные на время данные. Часто они часто перемещаются по последнему концу таблицы, редко просматривая старую историю. Там вы можете найти все индексные блоки, необходимые для навигации по недавнему концу памяти и вокруг него, в то время как очень мало нужно для навигации по более ранним записям.

Сложные части реализации не в том, как блоки попадают в буферный кеш. Это правила, когда они уходят. My Inside the PostgreSQL Buffer Cache и примеры включенных в него запросов могут помочь вам понять, что там происходит, и увидеть, что действительно накапливается на рабочем сервере. Это может быть удивительно. По всем этим темам в моей высокой производительности PostgreSQL 9.0 гораздо больше книге о .

Частичные индексы могут быть полезны, потому что они уменьшают размер индекса и, следовательно, быстрее перемещаются и оставляют больше оперативной памяти для кэширования других вещей. Если ваша навигация по индексу такова, что части, к которым вы прикасаетесь, всегда находятся в оперативной памяти, в любом случае это может не принести реального улучшения.

Грег Смит
источник