Cache de índice do PostgreSQL

16

Estou tendo dificuldade em encontrar explicações 'leigas' de como os índices são armazenados em cache no PostgreSQL, portanto, gostaria de verificar a realidade de qualquer uma ou todas essas suposições:

  1. Os índices do PostgreSQL, como linhas, ficam no disco, mas podem ser armazenados em cache.
  2. Um índice pode estar inteiramente no cache ou não ter.
  3. Se ele é armazenado em cache ou não, depende da frequência com que é usado (conforme definido pelo planejador de consultas).
  4. Por esse motivo, a maioria dos índices 'sensíveis' estará no cache o tempo todo.
  5. Os índices vivem no mesmo cache (o buffer cache?) Que as linhas e, portanto, o espaço em cache usado por um índice não está disponível para as linhas.


Minha motivação para entender isso se segue de outra pergunta que fiz, onde foi sugerido que índices parciais possam ser usados ​​em tabelas nas quais a maioria dos dados nunca será acessada.

Antes de fazer isso, gostaria de deixar claro que o emprego de um índice parcial gera duas vantagens:

  1. Reduzimos o tamanho do índice no cache, liberando mais espaço para as próprias linhas no cache.
  2. Reduzimos o tamanho da Árvore B, resultando em uma resposta mais rápida à consulta.
dukedave
fonte
4
O uso de um índice parcial não é útil apenas quando uma grande parte dos dados raramente é acessada, mas também quando certos valores são muito comuns. Quando um valor é muito comum, o planejador utilizará uma verificação de tabela de qualquer maneira, em vez do índice, portanto, a inclusão do valor no índice não serve para nada.
Eelke

Respostas:

19

Brincando um pouco com pg_buffercache , eu poderia obter respostas para algumas de suas perguntas.

  1. Isso é óbvio, mas os resultados para (5) também mostram que a resposta é SIM
  2. Ainda estou para dar um bom exemplo disso, por enquanto é mais sim do que não :) (Veja minha edição abaixo, a resposta é NÃO) ).
  3. Como o planejador é quem decide se deve usar um índice ou não, podemos dizer SIM , ele decide o cache (mas isso é mais complicado)
  4. Os detalhes exatos do armazenamento em cache podem ser derivados do código-fonte; não pude encontrar muito sobre esse tópico, exceto este (veja a resposta do autor também). No entanto, tenho certeza de que isso novamente é muito mais complicado do que um simples sim ou não. (Mais uma vez, na minha edição, você pode ter uma idéia - já que o tamanho do cache é limitado, esses índices 'sensíveis' competem pelo espaço disponível. Se forem muitos, eles se afastarão do cache - então a resposta é bastante NÃO . )
  5. Como uma consulta simples com pg_buffercacheprogramas, a resposta é um SIM definitivo . Vale ressaltar que os dados temporários da tabela não são armazenados em cache aqui.

EDITAR

Encontrei o excelente artigo de Jeremiah Peschka sobre armazenamento de tabelas e índices. Com informações de lá, eu poderia responder (2) também. Eu montei um pequeno teste para que você possa verificar isso sozinho.

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

Em suma, isso mostra que índices e tabelas podem ser armazenados em cache página por página; portanto, a resposta para (2) é NÃO .

E uma final para ilustrar as tabelas temporárias não armazenadas em cache aqui:

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
fonte
11
+1 resposta muito boa. Faz sentido que as tabelas temporárias que vivem na RAM não sejam armazenadas em cache. Eu me pergunto, no entanto, se o armazenamento em cache ocorre assim que uma tabela temporária é derramada no disco (por falta de suficiente temp_buffers) - para toda a tabela ou apenas a parte do disco. Eu esperaria o último. Pode ser um teste interessante ..
Erwin Brandstetter
9

As páginas de índice são buscadas quando uma consulta decide que será útil reduzir a quantidade de dados da tabela necessária para responder a uma consulta. Somente os blocos do índice navegaram para realizar a leitura. Sim, eles entram no mesmo pool shared_buffers em que os dados da tabela são armazenados. Ambos também são apoiados pelo cache do sistema operacional como uma segunda camada de cache.

Você pode facilmente ter 0,1% de um índice na memória ou 100% dele. A idéia de que a maioria dos índices "sensíveis" ficará no cache o tempo todo "fica mais difícil quando você tem consultas que tocam apenas um subconjunto de uma tabela. Um exemplo comum é se você tiver dados orientados ao tempo. Geralmente, esses navegam pelo final recente da tabela, raramente observando a história antiga. Lá, você pode encontrar todos os blocos de índice necessários para navegar até o final recente e na memória, enquanto poucos são necessários para navegar pelos registros anteriores.

As partes complicadas da implementação não são como os blocos entram no cache do buffer. São as regras sobre quando eles partem. A palestra My Inside the PostgreSQL Buffer Cache e as consultas de amostra incluídas lá podem ajudá-lo a entender o que está acontecendo lá e a ver o que realmente está se acumulando em um servidor de produção. Isso pode ser surpreendente. Há muito mais sobre todos esses tópicos no meu livro PostgreSQL 9.0 High Performance .

Índices parciais podem ser úteis porque reduzem o tamanho do índice e, portanto, são mais rápidos de navegar e deixam mais RAM para armazenar outras coisas em cache. Se sua navegação no índice for de tal forma que as partes tocadas estejam sempre na RAM, de qualquer maneira, isso pode não significar uma melhoria real.

Greg Smith
fonte