PostgreSQL Index Caching


16

Tengo dificultades para encontrar explicaciones "simples" de cómo se almacenan en caché los índices en PostgreSQL, por lo que me gustaría comprobar la realidad de cualquiera o de todos estos supuestos:

  1. Los índices PostgreSQL, como las filas, viven en el disco pero pueden almacenarse en caché.
  2. Un índice puede estar completamente en la caché o no estar en absoluto.
  3. Si se almacena en caché o no depende de la frecuencia con la que se usa (según lo definido por el planificador de consultas).
  4. Por esta razón, la mayoría de los índices 'sensibles' estarán en la caché todo el tiempo.
  5. Los índices viven en el mismo caché (el buffer cache?) Que las filas y, por lo tanto, el espacio de caché utilizado por un índice no está disponible para las filas.


Mi motivación para entender esto se deriva de otra pregunta que pregunté, donde se sugirió que se pueden usar índices parciales en tablas donde nunca se accederá a la mayoría de los datos.

Antes de emprender esto, me gustaría tener claro que emplear un índice parcial produce dos ventajas:

  1. Reducimos el tamaño del índice en el caché, liberando más espacio para las filas en el caché.
  2. Reducimos el tamaño del B-Tree, lo que resulta en una respuesta de consulta más rápida.

44
El uso de un índice parcial no solo es útil cuando rara vez se accede a una gran parte de los datos, sino también cuando ciertos valores son muy comunes. Cuando un valor es muy común, el planificador utilizará una exploración de tabla de todos modos en lugar del índice, por lo que incluir el valor en el índice no sirve de nada.
Eelke

Respuestas:


19

Jugando un poco con pg_buffercache , podría obtener respuestas a algunas de sus preguntas.

  1. Esto es bastante obvio, pero los resultados para (5) también muestran que la respuesta es
  2. Todavía tengo que establecer un buen ejemplo para esto, por ahora es más sí que no :) (Vea mi edición a continuación, la respuesta es NO ).
  3. Como el planificador es quien decide si usar un índice o no, podemos decir , decide el almacenamiento en caché (pero esto es más complicado)
  4. Los detalles exactos del almacenamiento en caché se pueden derivar del código fuente, no pude encontrar demasiado sobre este tema, excepto este (ver también la respuesta del autor ). Sin embargo, estoy bastante seguro de que esto nuevamente es mucho más complicado que un simple sí o no. (Nuevamente, de mi edición puede tener alguna idea, ya que el tamaño de la memoria caché es limitado, esos índices 'sensibles' compiten por el espacio disponible. Si son demasiados, se patearán entre sí de la memoria caché, por lo que la respuesta es NO . )
  5. Como una simple consulta con pg_buffercacheespectáculos, la respuesta es un SI definitivo . Vale la pena señalar que los datos de la tabla temporal no se almacenan en caché aquí.

EDITAR

He encontrado el excelente artículo de Jeremiah Peschka sobre el almacenamiento de tablas e índices. Con información de allí, también podría responder (2) . Configuré una pequeña prueba, para que pueda verificarlos usted mismo.

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

En general, esto muestra que los índices y las tablas se pueden almacenar en caché página por página, por lo tanto, la respuesta para (2) es NO .

Y una última para ilustrar las tablas temporales que no se almacenan en caché aquí:

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

1
+1 Muy buena respuesta. Tiene sentido que las tablas temporales que viven en RAM no estén en caché. Sin embargo, me pregunto si el almacenamiento en caché ocurre tan pronto como una tabla temporal se derrama en el disco (por falta de suficiente temp_buffers), para toda la tabla o solo la parte del disco. Yo esperaría lo último. Podría ser una prueba interesante ..
Erwin Brandstetter

9

Las páginas de índice se obtienen cuando una consulta decide que serán útiles para reducir la cantidad de datos de tabla necesarios para responder una consulta. Solo se leen los bloques del índice navegado para lograr eso. Sí, van al mismo grupo shared_buffers donde se almacenan los datos de la tabla. Ambos también están respaldados por la memoria caché del sistema operativo como una segunda capa de almacenamiento en caché.

Puede tener fácilmente el 0.1% de un índice en la memoria o el 100% de él. La idea de que la mayoría de los "índices 'sensibles' van a estar en la caché todo el tiempo" se resiente cuando tienes consultas que solo tocan un subconjunto de una tabla. Un ejemplo común es si tiene datos orientados al tiempo. A menudo, esos navegan comúnmente por el final reciente de la tabla y rara vez visitan la historia antigua. Allí puede encontrar todos los bloques de índice necesarios para navegar hacia y alrededor del final reciente en la memoria, mientras que muy pocos necesitan navegar por los registros anteriores.

Las partes complicadas de la implementación no son cómo los bloques entran en la memoria caché del búfer. Son las reglas sobre cuándo se van. Mi charla sobre el caché del búfer de PostgreSQL y las consultas de muestra incluidas allí pueden ayudarlo a comprender lo que está sucediendo allí y a ver qué se está acumulando realmente en un servidor de producción. Puede ser sorprendente Hay mucho más sobre todos estos temas en mi libro PostgreSQL 9.0 High Performance también.

Los índices parciales pueden ser útiles porque reducen el tamaño del índice y, por lo tanto, son más rápidos de navegar y dejan más RAM para almacenar en caché otras cosas. Si su navegación por el índice es tal que las partes que toca siempre están en la RAM, de todos modos, eso podría no ser una mejora real.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.