PostgreSQL Index Caching

16

Ich habe Schwierigkeiten, Erklärungen zu finden, wie Indizes in PostgreSQL zwischengespeichert werden. Daher möchte ich eine Überprüfung der Realität einiger oder aller dieser Annahmen:

  1. PostgreSQL-Indizes befinden sich wie Zeilen auf der Festplatte, können jedoch zwischengespeichert werden.
  2. Ein Index befindet sich möglicherweise vollständig oder überhaupt nicht im Cache.
  3. Ob es zwischengespeichert wird oder nicht, hängt davon ab, wie oft es verwendet wird (wie vom Abfrageplaner definiert).
  4. Aus diesem Grund werden die meisten "vernünftigen" Indizes die ganze Zeit im Cache sein.
  5. Die Indizes befinden sich im selben Cache (dem buffer cache?) Wie die Zeilen. Daher steht der von einem Index verwendete Cache-Speicher für Zeilen nicht zur Verfügung.


Meine Motivation, dies zu verstehen, ergibt sich aus einer anderen Frage, die ich gestellt habe, wo vorgeschlagen wurde, Teilindizes zu erstellen für Tabellen zu verwenden, auf die ein Großteil der Daten niemals zugreifen wird.

Bevor ich dies unternehme, möchte ich klarstellen, dass die Verwendung eines Teilindex zwei Vorteile bringt:

  1. Wir reduzieren die Größe des Index im Cache, um mehr Platz für die Zeilen im Cache zu schaffen.
  2. Wir reduzieren die Größe des B-Baums, was zu einer schnelleren Antwort auf Anfragen führt.
dukedave
quelle
4
Die Verwendung eines Teilindex ist nicht nur nützlich, wenn auf einen großen Teil der Daten selten zugegriffen wird, sondern auch, wenn bestimmte Werte sehr häufig sind. Wenn ein Wert sehr häufig ist, verwendet der Planer ohnehin einen Tabellenscan anstelle des Index, sodass die Aufnahme des Werts in den Index keinen Zweck erfüllt.
Eelke,

Antworten:

19

Wenn ich ein bisschen mit pg_buffercache spiele , könnte ich Antworten auf einige Ihrer Fragen bekommen.

  1. Dies ist ziemlich offensichtlich, aber die Ergebnisse für (5) zeigen auch, dass die Antwort JA ist
  2. Ich muss noch ein gutes Beispiel dafür aufstellen, denn jetzt ist es mehr Ja als Nein :) (Siehe meine Änderung unten, die Antwort ist NEIN .)
  3. Da der Planer entscheidet, ob er einen Index verwendet oder nicht, können wir JA sagen , er entscheidet über das Zwischenspeichern (dies ist jedoch komplizierter).
  4. Die genauen Details der Zwischenspeicherung können aus dem Quellcode abgeleitet werden. Ich konnte zu diesem Thema nicht allzu viel finden, außer diesem (siehe auch die Antwort des Autors ). Ich bin mir jedoch ziemlich sicher, dass dies wiederum weitaus komplizierter ist als ein einfaches Ja oder Nein. (Aus meiner Bearbeitung können Sie sich wieder eine Vorstellung machen - da die Cachegröße begrenzt ist, konkurrieren diese "vernünftigen" Indizes um den verfügbaren Speicherplatz. Wenn sie zu viele sind, kicken sie sich gegenseitig aus dem Cache - die Antwort lautet also eher NEIN . )
  5. Wie eine einfache Abfrage mit pg_buffercachezeigt, lautet die Antwort definitiv JA . Es ist zu beachten, dass temporäre Tabellendaten hier nicht zwischengespeichert werden.

BEARBEITEN

Ich habe Jeremiah Peschkas großartigen Artikel über Tabellen- und Indexspeicher gefunden. Mit den Informationen von dort könnte ich auch (2) antworten . Ich habe einen kleinen Test erstellt, damit Sie diese selbst überprüfen können.

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

Alles in allem zeigt dies, dass Indizes und Tabellen Seite für Seite zwischengespeichert werden können, daher lautet die Antwort für (2) NEIN .

Und eine letzte, um zu veranschaulichen, dass temporäre Tabellen hier nicht zwischengespeichert sind:

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
quelle
1
+1 Sehr schöne Antwort. Sinnvoll, dass temporäre Tabellen im RAM nicht zwischengespeichert werden. Ich frage mich jedoch, ob das Zwischenspeichern erfolgt, sobald temporäre Tabellen auf die Festplatte verschüttet werden (aus Mangel an ausreichend temp_buffers) - für die gesamte Tabelle oder nur für den Teil auf der Festplatte. Letzteres würde ich erwarten. Könnte ein interessanter Test sein.
Erwin Brandstetter
9

Indexseiten werden abgerufen, wenn eine Abfrage entscheidet, dass sie nützlich sind, um die zum Beantworten einer Abfrage erforderliche Menge an Tabellendaten zu reduzieren. Es werden nur die zu diesem Zweck navigierten Blöcke des Index eingelesen. Ja, sie werden in denselben Pool shared_buffers abgelegt, in dem die Tabellendaten gespeichert sind. Beide werden auch vom Cache des Betriebssystems als zweite Cache-Ebene unterstützt.

Sie können leicht 0,1% eines Index im Speicher haben oder 100% davon. Die Idee, dass die meisten "vernünftigen" Indizes die ganze Zeit im Cache gespeichert sein werden, fällt schwer, wenn Sie Abfragen haben, die nur eine Teilmenge einer Tabelle berühren. Ein häufiges Beispiel ist, wenn Sie zeitorientierte Daten haben. Diese navigieren häufig am letzten Ende der Tabelle und sehen selten die alte Geschichte. Dort finden Sie möglicherweise alle Indexblöcke, die zum Navigieren zu und um das zuletzt verwendete Ende im Speicher erforderlich sind, während sich nur wenige befinden, die zum Navigieren zu früheren Datensätzen erforderlich sind.

Die komplizierten Teile der Implementierung sind nicht, wie Blöcke in den Puffercache gelangen. Es sind die Regeln, wann sie gehen. Mein Vortrag über den PostgreSQL-Puffercache und die darin enthaltenen Beispielabfragen können Ihnen dabei helfen, zu verstehen, was dort vor sich geht und was sich wirklich auf einem Produktionsserver ansammelt. Es kann überraschend sein. Zu all diesen Themen gibt es in meinem PostgreSQL 9.0 High Performance- Buch noch viel mehr.

Teilindizes können hilfreich sein, da sie die Größe des Index verringern und daher schneller navigieren und mehr Arbeitsspeicher für die Zwischenspeicherung anderer Dinge lassen. Wenn Ihre Navigation im Index so ist, dass sich die von Ihnen berührten Teile ohnehin immer im RAM befinden, ist dies möglicherweise keine echte Verbesserung.

Greg Smith
quelle