Mise en cache des index PostgreSQL

16

J'ai du mal à trouver des explications «profanes» sur la façon dont les index sont mis en cache dans PostgreSQL, donc je voudrais une vérification de la réalité sur l'une ou l'ensemble de ces hypothèses:

  1. Les index PostgreSQL, comme les lignes, vivent sur le disque mais peuvent être mis en cache.
  2. Un index peut être entièrement dans le cache ou pas du tout.
  3. Le fait qu'il soit mis en cache ou non dépend de la fréquence à laquelle il est utilisé (tel que défini par le planificateur de requêtes).
  4. Pour cette raison, la plupart des index «sensibles» seront toujours dans le cache.
  5. Les index vivent dans le même cache (le buffer cache?) Que les lignes, et donc l'espace de cache utilisé par un index n'est pas disponible pour les lignes.


Ma motivation pour comprendre cela découle d' une autre question. J'ai demandé où il était suggéré que des index partiels puissent être utilisés sur des tables où la majorité des données ne seront jamais consultées.

Avant d'entreprendre cela, je tiens à préciser que l'utilisation d'un indice partiel présente deux avantages:

  1. Nous réduisons la taille de l'index dans le cache, libérant ainsi plus d'espace pour les lignes elles-mêmes dans le cache.
  2. Nous réduisons la taille de l'arbre B, résultant en une réponse de requête plus rapide.
dukedave
la source
4
L'utilisation d'un index partiel n'est pas seulement utile lorsqu'une grande partie des données sera rarement accédée mais également lorsque certaines valeurs sont très courantes. Lorsqu'une valeur est très courante, le planificateur utilisera de toute façon une analyse de table au lieu de l'index, donc l'inclusion de la valeur dans l'index ne sert à rien.
Eelke

Réponses:

19

En jouant un peu avec pg_buffercache , je pourrais obtenir des réponses à certaines de vos questions.

  1. C'est assez évident, mais les résultats pour (5) montrent également que la réponse est OUI
  2. Je n'ai pas encore mis en place un bon exemple pour cela, pour l'instant c'est plus oui que non :) (Voir ma modification ci-dessous, la réponse est NON .)
  3. Puisque le planificateur est celui qui décide d'utiliser ou non un index, nous pouvons dire OUI , il décide de la mise en cache (mais c'est plus compliqué)
  4. Les détails exacts de la mise en cache pourraient être dérivés du code source, je n'ai pas pu trouver grand-chose sur ce sujet, sauf celui-ci (voir aussi la réponse de l'auteur ). Cependant, je suis à peu près sûr que cela est encore plus compliqué qu'un simple oui ou non. (Encore une fois, à partir de ma modification, vous pouvez vous faire une idée - puisque la taille du cache est limitée, ces index `` sensibles '' se disputent l'espace disponible. S'ils sont trop nombreux, ils se botteront le cache - donc la réponse est plutôt NON . )
  5. Comme une simple requête avec des pg_buffercachespectacles, la réponse est un OUI définitif . Il convient de noter que les données de table temporaires ne sont pas mises en cache ici.

ÉDITER

J'ai trouvé un excellent article de Jeremiah Peschka sur le stockage des tables et des index. Avec des informations à partir de là, je pourrais également répondre (2) . J'ai mis en place un petit test, afin que vous puissiez les vérifier vous-même.

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

Dans l'ensemble, cela montre que les index et les tables peuvent être mis en cache page par page, donc la réponse pour (2) est NON .

Et une dernière pour illustrer les tables temporaires non mises en cache ici:

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
la source
1
+1 Très belle réponse. Il est logique que les tables temporaires vivant dans la RAM ne soient pas mises en cache. Je me demande, cependant, si la mise en cache se produit dès qu'une table temporaire se répand sur le disque (faute de suffisamment temp_buffers) - pour la table entière ou juste la partie sur le disque. Je m'attendrais à ce dernier. Cela pourrait être un test intéressant ..
Erwin Brandstetter
9

Les pages d'index sont récupérées lorsqu'une requête décide qu'elles seront utiles pour réduire la quantité de données de table nécessaires pour répondre à une requête. Seuls les blocs de l'index parcourus pour y parvenir sont lus. Oui, ils vont dans le même pool shared_buffers où les données de la table sont stockées. Les deux sont également pris en charge par le cache du système d'exploitation en tant que deuxième couche de mise en cache.

Vous pouvez facilement avoir 0,1% d'un index en mémoire ou 100% de celui-ci. L'idée que la plupart des "index" sensibles seront dans le cache tout le temps "échoue lorsque vous avez des requêtes qui ne touchent qu'un sous-ensemble d'une table. Un exemple courant est si vous avez des données temporelles. Souvent, ceux-ci naviguent généralement vers la fin récente du tableau, rarement en regardant l'histoire ancienne. Vous y trouverez peut-être tous les blocs d'index nécessaires pour naviguer vers et autour de la fin récente en mémoire, tandis que très peu nécessaires pour naviguer dans les enregistrements précédents sont là.

Les parties compliquées de l'implémentation ne sont pas la façon dont les blocs entrent dans le cache de tampon. Ce sont les règles concernant leur départ. Mon exposé sur le cache de tampon PostgreSQL et les exemples de requêtes inclus peuvent vous aider à comprendre ce qui s'y passe et à voir ce qui s'accumule vraiment sur un serveur de production. Cela peut être surprenant. Il y a beaucoup plus sur tous ces sujets dans mon livre PostgreSQL 9.0 High Performance .

Les index partiels peuvent être utiles car ils réduisent la taille de l'index et sont donc à la fois plus rapides à naviguer et laissent plus de RAM pour la mise en cache d'autres choses. Si votre navigation dans l'index est telle que les parties que vous touchez sont toujours en RAM, de toute façon, cela n'achètera peut-être pas une réelle amélioration.

Greg Smith
la source