Das Erhöhen von work_mem und shared_buffers unter Postgres 9.2 verlangsamt Abfragen erheblich

39

Ich habe eine PostgreSQL 9.2-Instanz, die auf einem RHEL 6.3-Computer mit 8 Kernen und 16 GB RAM ausgeführt wird. Der Server ist dieser Datenbank zugeordnet. Angesichts der Tatsache, dass die Standardeinstellung für postgresql.conf in Bezug auf die Speichereinstellungen recht konservativ ist, hielt ich es für eine gute Idee, Postgres mehr Speicher zur Verfügung zu stellen. Zu meiner Überraschung hat das Befolgen von Ratschlägen auf wiki.postgresql.org/wiki/Tuning_Your_PostgreSQL_Server praktisch jede Abfrage, die ich ausführe, erheblich verlangsamt, was jedoch bei komplexeren Abfragen offensichtlich auffälliger ist.

Ich habe auch versucht, pgtune auszuführen, was die folgende Empfehlung mit mehr Parametern ergab, aber das hat nichts geändert. Es werden Shared_Buffer von 1/4 der RAM-Größe vorgeschlagen, was anscheinend mit anderen Empfehlungen (insbesondere in PG-Wiki) übereinstimmt.

default_statistics_target = 50
maintenance_work_mem = 960MB
constraint_exclusion = on
checkpoint_completion_target = 0.9
effective_cache_size = 11GB
work_mem = 96MB
wal_buffers = 8MB
checkpoint_segments = 16
shared_buffers = 3840MB
max_connections = 80

Ich habe versucht, die gesamte Datenbank nach dem Ändern der Einstellungen neu zu indizieren (mithilfe von reindex database), aber das hat auch nicht geholfen. Ich habe mit shared_buffers und work_mem rumgespielt. Wenn Sie die sehr konservativen Standardwerte (128 KB / 1 MB) schrittweise ändern, sinkt die Leistung allmählich.

Ich habe EXPLAIN (ANALYZE,BUFFERS)ein paar Fragen beantwortet und der Schuldige scheint zu sein, dass Hash Join bedeutend langsamer ist. Mir ist nicht klar warum.

Um ein konkretes Beispiel zu nennen: Ich habe die folgende Abfrage. Es wird in der Standardkonfiguration in ~ 2100 ms und in der Konfiguration mit erhöhten Puffergrößen in ~ 3300 ms ausgeführt:

select count(*) from contest c
left outer join contestparticipant cp on c.id=cp.contestId
left outer join teammember tm on tm.contestparticipantid=cp.id
left outer join staffmember sm on cp.id=sm.contestparticipantid
left outer join person p on p.id=cp.personid
left outer join personinfo pi on pi.id=cp.personinfoid
where pi.lastname like '%b%' or pi.firstname like '%a%';

EXPLAIN (ANALYZE,BUFFERS) für die Abfrage oben:

Die Frage ist, warum ich eine verringerte Leistung beobachte, wenn ich die Puffergröße erhöhe. Der Maschine geht definitiv nicht der Speicher aus. Zuweisung, wenn Shared Memory im Betriebssystem ( shmmaxund shmall) auf sehr große Werte gesetzt ist, sollte dies kein Problem sein. Ich bekomme auch keine Fehler im Postgres-Log. Ich verwende Autovacuum in der Standardkonfiguration, aber ich erwarte nicht, dass das irgendetwas damit zu tun hat. Alle Abfragen wurden im Abstand von wenigen Sekunden auf demselben Computer ausgeführt, nur mit geänderter Konfiguration (und neu gestartetem PG).

Edit: Ich fand gerade eine besonders interessante Tatsache: Wenn ich den gleichen Test auf meinem iMac Mitte 2010 (OSX 10.7.5) auch mit Postgres 9.2.1 und 16 GB RAM durchführe, spüre ich keine Verlangsamung. Speziell:

set work_mem='1MB';
select ...; // running time is ~1800 ms
set work_mem='96MB';
select ...' // running time is ~1500 ms

Wenn ich genau die gleiche Abfrage (die oben genannte) mit genau den gleichen Daten auf dem Server durchführe, erhalte ich 2100 ms mit work_mem = 1 MB und 3200 ms mit 96 MB.

Der Mac verfügt über eine SSD, ist also verständlicherweise schneller, zeigt aber ein Verhalten, das ich erwarten würde.

Siehe auch die nachfolgende Diskussion zu pgsql-performance .

Petr Praus
quelle
1
Es sieht so aus, als ob im langsameren Fall jeder Schritt konsequent langsamer ist. Sind andere Einstellungen gleich geblieben?
Dezso
1
Es lohnt sich wahrscheinlich, dies in einem spezielleren Forum zu erfragen, anstatt dem allgemeinen, das dies ist. In diesem Fall empfehle ich die pgsql-allgemeine Mailingliste archives.postgresql.org/pgsql-general
Colin 't Hart
1
Oh, und melde dich und beantworte bitte deine eigene Frage, wenn du die Antwort findest! (Dies ist erlaubt, sogar empfohlen).
Colin 't Hart
1
Ich frage mich, wie ähnlich Postgres Oracle in dieser Hinsicht ist: Ich erinnere mich an einen Kurs von Jonathan Lewis (Oracle Guru), in dem er demonstrierte, dass das Zuweisen von mehr Speicher für Sortierungen sie manchmal langsamer machte. Ich vergesse die Details, aber es war etwas damit zu tun, dass Oracle partielle Sortierungen durchführt und sie dann in den temporären Speicher schreibt und sie später kombiniert. Irgendwie hat mehr Speicher diesen Prozess langsamer gemacht.
Colin 't Hart
2
Die Frage wurde nun auf pgsql-performance veröffentlicht: archives.postgresql.org/pgsql-performance/2012-11/msg00004.php
Petr Praus

Antworten:

28

Denken Sie zunächst daran, dass work_mem pro Operation ausgeführt wird und daher sehr schnell zu viel wird. Im Allgemeinen würde ich work_mem so lange in Ruhe lassen, bis Sie es brauchen, wenn Sie keine Probleme damit haben, langsam zu sein.

Wenn ich mir Ihre Abfragepläne ansehe, fällt mir auf, dass die Puffertreffer bei beiden Plänen sehr unterschiedlich sind und dass sogar die sequentiellen Scans langsamer sind. Ich vermute, dass das Problem mit Read-Ahead-Caching und weniger Platz dafür zu tun hat. Dies bedeutet, dass Sie den Speicher für die Wiederverwendung von Indizes und gegen das Lesen von Tabellen auf der Festplatte vorbelasten.


Nach meinem Verständnis sucht PostgreSQL nach einer Seite im Cache, bevor es sie von der Festplatte liest, da es nicht genau weiß, ob der Cache des Betriebssystems diese Seite enthält. Da die Seiten dann im Cache verbleiben und dieser Cache langsamer als der Cache des Betriebssystems ist, werden die Arten von Abfragen, die schnell sind, gegenüber den Arten, die langsam sind, geändert. Abgesehen von work_mem-Problemen sieht es so aus, als ob alle Ihre Abfrageinformationen aus dem Cache stammen, es ist jedoch eine Frage des Cache.

work_mem : Wie viel Speicher können wir für eine Sortier- oder verwandte Verknüpfungsoperation reservieren ? Dies erfolgt pro Operation, nicht pro Anweisung oder pro Back-End, sodass eine einzelne komplexe Abfrage ein Vielfaches dieses Speicherplatzes beanspruchen kann. Es ist nicht klar, ob Sie diese Grenze erreichen, aber es lohnt sich, darauf hinzuweisen und sich dessen bewusst zu sein. Wenn Sie dies zu weit erhöhen, verlieren Sie Speicher, der möglicherweise für den Lesecache und die gemeinsam genutzten Puffer verfügbar ist.

shared_buffers : Wie viel Speicher muss der tatsächlichen PostgreSQL-Seitenwarteschlange zugewiesen werden ? Im Idealfall verbleibt der interessante Satz Ihrer Datenbank im Arbeitsspeicher, der hier und in den Lesepuffern zwischengespeichert ist. Dadurch wird jedoch sichergestellt, dass die am häufigsten verwendeten Informationen in allen Backends zwischengespeichert und nicht auf die Festplatte geschrieben werden. Unter Linux ist dieser Cache erheblich langsamer als der Festplatten-Cache des Betriebssystems, bietet jedoch die Garantie, dass der Festplatten-Cache des Betriebssystems nicht vorhanden ist und für PostgreSQL transparent ist. Dies ist ziemlich klar, wo Ihr Problem ist.

Wenn wir also eine Anfrage haben, überprüfen wir zuerst die gemeinsam genutzten Puffer, da PostgreSQL über umfassende Kenntnisse dieses Caches verfügt, und suchen nach den Seiten. Wenn sie nicht vorhanden sind, bitten wir das Betriebssystem, sie aus der Datei zu öffnen. Wenn das Betriebssystem das Ergebnis zwischengespeichert hat, gibt es die zwischengespeicherte Kopie zurück (dies ist schneller als die gemeinsam genutzten Puffer, aber Pg kann nicht feststellen, ob sie zwischengespeichert oder aktiviert ist Datenträger, und Datenträger ist viel langsamer, so dass PostgreSQL diese Chance normalerweise nicht wahrnimmt). Beachten Sie, dass dies auch Auswirkungen auf den zufälligen oder den sequentiellen Seitenzugriff hat. So können Sie mit niedrigeren Einstellungen für shared_buffers eine bessere Leistung erzielen.

Ich gehe davon aus, dass Sie in Umgebungen mit hoher Parallelität und größeren Einstellungen für den gemeinsamen Puffer wahrscheinlich eine bessere oder zumindest konsistentere Leistung erzielen. Denken Sie auch daran, dass PostgreSQL diesen Speicher belegt und speichert. Wenn also andere Dinge auf dem System ausgeführt werden, enthalten die Lesepuffer Dateien, die von anderen Prozessen gelesen wurden. Es ist ein sehr großes und komplexes Thema. Größere gemeinsamen Puffer Einstellungen bieten eine bessere Garantien der Leistung kann aber weniger Leistung in einigen Fällen liefern.

Chris Travers
quelle
10

Abgesehen von dem scheinbar paradoxen Effekt, dass eine Erhöhung work_memdie Leistung verringert ( @Chris könnte eine Erklärung haben), können Sie Ihre Funktion auf mindestens zwei Arten verbessern.

  • Schreibe zwei Fälschungen LEFT JOINmit JOIN. Dies könnte den Abfrageplaner verwirren und zu minderwertigen Plänen führen.

SELECT count(*) AS ct
FROM   contest            c
JOIN   contestparticipant cp ON cp.contestId = c.id
JOIN   personinfo         pi ON pi.id = cp.personinfoid
LEFT   JOIN teammember    tm ON tm.contestparticipantid = cp.id
LEFT   JOIN staffmember   sm ON sm.contestparticipantid = cp.id
LEFT   JOIN person        p  ON p.id = cp.personid
WHERE (pi.firstname LIKE '%a%'
OR     pi.lastname  LIKE '%b%')
  • Angenommen, Ihre tatsächlichen Suchmuster sind selektiver, verwenden Sie Trigrammindizes für pi.firstnameund pi.lastname, um nicht verankerte LIKESuchvorgänge zu unterstützen. (Kürzere Muster wie '%a%'werden ebenfalls unterstützt, aber ein Index hilft wahrscheinlich nicht bei nicht selektiven Prädikaten.):

CREATE INDEX personinfo_firstname_gin_idx ON personinfo USING gin (firstname gin_trgm_ops);
CREATE INDEX personinfo_lastname_gin_idx  ON personinfo USING gin (lastname gin_trgm_ops);

Oder ein mehrspaltiger Index:

CREATE INDEX personinfo_name_gin_idx ON personinfo USING gin (firstname gin_trgm_ops, lastname gin_trgm_ops);

Sollte Ihre Anfrage etwas schneller machen. Dazu müssen Sie das Zusatzmodul pg_trgm installieren . Details unter diesen verwandten Fragen:


Haben Sie auch versucht, die Einstellungen work_mem lokal vorzunehmen - nur für die aktuelle Transaktion ?

SET LOCAL work_mem = '96MB';

Dies verhindert, dass gleichzeitige Transaktionen auch mehr RAM verbrauchen und sich gegenseitig hungern.

Erwin Brandstetter
quelle
3
Ich möchte Erwins Vorschlag von work_mem zustimmen. Da work_mem die Art der Abfragen ändert, die schneller sind, müssen Sie sie möglicherweise für einige Abfragen ändern. Niedrige Arbeitsspeicherebenen eignen sich am besten für Abfragen, die eine kleine Anzahl von Datensätzen auf komplexe Weise sortieren / verknüpfen (dh viele Verknüpfungen), während hohe Arbeitsspeicherebenen für Abfragen geeignet sind, die einige Sortierungen aufweisen, aber eine große Anzahl von Zeilen gleichzeitig sortieren oder verknüpfen .
Chris Travers
Ich habe die Abfrage in der Zwischenzeit verbessert (die Frage stammt aus dem letzten Oktober), aber danke :) Bei dieser Frage geht es mehr um den unerwarteten Effekt als um die bestimmte Abfrage. Die Abfrage dient hauptsächlich dazu, den Effekt zu demonstrieren. Danke für den Hinweis auf den Index, ich werde das versuchen!
Petr Praus