Performance is een van de belangrijkste en meest complexe taken bij het beheren van een database. Het kan worden beïnvloed door de configuratie, de hardware of zelfs het ontwerp van het systeem. PostgreSQL is standaard geconfigureerd met het oog op compatibiliteit en stabiliteit, aangezien de prestaties sterk afhankelijk zijn van de hardware en van ons systeem zelf. We kunnen een systeem hebben waarin veel gegevens worden gelezen, maar de informatie verandert niet vaak. Of we kunnen een systeem hebben dat continu schrijft. Om deze reden is het onmogelijk om een standaardconfiguratie te definiëren die voor alle soorten werkbelastingen werkt.
In deze blog zullen we zien hoe men de werklast of query's die worden uitgevoerd, analyseert. We zullen dan enkele basisconfiguratieparameters bekijken om de prestaties van onze PostgreSQL-database te verbeteren. Zoals we al zeiden, zullen we slechts enkele parameters zien. De lijst met PostgreSQL-parameters is uitgebreid, we zouden slechts enkele van de belangrijkste aanstippen. Men kan echter altijd de officiële documentatie raadplegen om zich te verdiepen in de parameters en configuraties die in onze omgeving het belangrijkst of nuttigst lijken.
UITLEG
Een van de eerste stappen die we kunnen nemen om te begrijpen hoe we de prestaties van onze database kunnen verbeteren, is het analyseren van de zoekopdrachten die worden gedaan.
PostgreSQL stelt een queryplan op voor elke query die het ontvangt. Om dit plan te zien, gebruiken we EXPLAIN.
De structuur van een queryplan is een boomstructuur van planknooppunten. De knooppunten in het lagere niveau van de boom zijn scanknooppunten. Ze retourneren onbewerkte rijen van een tafel. Er zijn verschillende soorten scanknooppunten voor verschillende methoden om toegang tot de tabel te krijgen. De EXPLAIN-uitgang heeft een regel voor elk knooppunt in de planboom.
world=# EXPLAIN SELECT * FROM city t1,country t2 WHERE id>100 AND t1.population>700000 AND t2.population<7000000;
QUERY PLAN
--------------------------------------------------------------------------
Nested Loop (cost=0.00..734.81 rows=50662 width=144)
-> Seq Scan on city t1 (cost=0.00..93.19 rows=347 width=31)
Filter: ((id > 100) AND (population > 700000))
-> Materialize (cost=0.00..8.72 rows=146 width=113)
-> Seq Scan on country t2 (cost=0.00..7.99 rows=146 width=113)
Filter: (population < 7000000)
(6 rows)
Deze opdracht laat zien hoe de tabellen in onze query worden gescand. Laten we eens kijken waarmee deze waarden overeenkomen die we kunnen waarnemen in onze EXPLAIN.
- De eerste parameter toont de bewerking die de engine uitvoert op de gegevens in deze stap.
- Geschatte opstartkosten. Dit is de tijd die nodig is voordat de uitvoerfase kan beginnen.
- Geschatte totale kosten. Dit wordt vermeld in de veronderstelling dat het planknooppunt volledig wordt uitgevoerd. In de praktijk kan het zijn dat het bovenliggende knooppunt van een knooppunt niet alle beschikbare rijen leest.
- Geschat aantal rijen dat door dit planknooppunt wordt uitgevoerd. Nogmaals, het knooppunt wordt verondersteld te zijn voltooid.
- Geschatte gemiddelde breedte van rijen die door dit planknooppunt worden uitgevoerd.
Het meest kritieke onderdeel van de weergave zijn de geschatte uitvoeringskosten van het overzicht, wat de planner inschat hoe lang het duurt om het overzicht uit te voeren. Wanneer we vergelijken hoe effectief de ene zoekopdracht is met de andere, zullen we in de praktijk de kostenwaarden ervan vergelijken.
Het is belangrijk om te begrijpen dat de kosten van een knooppunt op het hoogste niveau de kosten van alle onderliggende knooppunten omvatten. Het is ook belangrijk om te beseffen dat de kosten alleen de dingen weerspiegelen waar de planner om geeft. De kosten houden met name geen rekening met de tijd die is besteed aan het verzenden van resultaatrijen naar de klant, wat een belangrijke factor zou kunnen zijn in de werkelijk verstreken tijd; maar de planner negeert het omdat hij het niet kan veranderen door het plan te wijzigen.
De kosten worden gemeten in willekeurige eenheden die worden bepaald door de kostenparameters van de planner. De traditionele praktijk is om de kosten te meten in eenheden van het ophalen van schijfpagina's; dat wil zeggen, seq_page_cost is conventioneel ingesteld op 1.0 en de andere kostenparameters worden relatief daaraan ingesteld.
UITLEG ANALYSE
Met deze optie voert EXPLAIN de query uit en geeft vervolgens het werkelijke aantal rijen en de werkelijke looptijd weer die is verzameld binnen elk planknooppunt, samen met dezelfde schattingen die een gewoon EXPLAIN laat zien.
Laten we een voorbeeld bekijken van het gebruik van deze tool.
world=# EXPLAIN ANALYZE SELECT * FROM city t1,country t2 WHERE id>100 AND t1.population>700000 AND t2.population<7000000;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=0.00..734.81 rows=50662 width=144) (actual time=0.081..22.066 rows=51100 loops=1)
-> Seq Scan on city t1 (cost=0.00..93.19 rows=347 width=31) (actual time=0.069..0.618 rows=350 loops=1)
Filter: ((id > 100) AND (population > 700000))
Rows Removed by Filter: 3729
-> Materialize (cost=0.00..8.72 rows=146 width=113) (actual time=0.000..0.011 rows=146 loops=350)
-> Seq Scan on country t2 (cost=0.00..7.99 rows=146 width=113) (actual time=0.007..0.058 rows=146 loops=1)
Filter: (population < 7000000)
Rows Removed by Filter: 93
Planning time: 0.136 ms
Execution time: 24.627 ms
(10 rows)
Als we de reden niet kunnen vinden waarom onze vragen langer duren dan zou moeten, kunnen we deze blog raadplegen voor meer informatie.
VACUM
Het VACUUM-proces is verantwoordelijk voor verschillende onderhoudstaken binnen de database, waaronder het herstellen van de opslag die wordt ingenomen door dode tupels. Bij de normale werking van PostgreSQL worden tuples die zijn verwijderd of verouderd door een update niet fysiek van hun tabel verwijderd; ze blijven aanwezig totdat een VACUM wordt uitgevoerd. Daarom is het noodzakelijk om het VACUM periodiek te doen, vooral in regelmatig bijgewerkte tabellen.
Als de VACUM te veel tijd of middelen kost, betekent dit dat we het vaker moeten doen, zodat elke operatie minder hoeft te reinigen.
In elk geval kan het nodig zijn om de VACUUM uit te schakelen, bijvoorbeeld bij het laden van gegevens in grote hoeveelheden.
De VACUUM wint eenvoudig ruimte terug en stelt deze beschikbaar voor hergebruik. Deze vorm van het commando kan parallel werken met het normale lezen en schrijven van de tabel, aangezien een exclusief slot niet wordt verkregen. De extra ruimte wordt echter niet teruggegeven aan het besturingssysteem (in de meeste gevallen); het is alleen beschikbaar voor hergebruik binnen dezelfde tabel.
VACUUM FULL herschrijft alle inhoud van de tabel in een nieuw schijfbestand zonder extra ruimte, waardoor de ongebruikte ruimte kan terugkeren naar het besturingssysteem. Dit formulier is veel langzamer en vereist een exclusief slot op elke tafel tijdens het verwerken.
VACUM ANALYSE voert een VACUM en vervolgens een ANALYSE uit voor elke geselecteerde tafel. Dit is een praktische manier om scripts voor routineonderhoud te combineren.
ANALYZE verzamelt statistieken over de inhoud van de tabellen in de database en slaat de resultaten op in pg_statistic. Vervolgens gebruikt de queryplanner deze statistieken om de meest efficiënte uitvoeringsplannen voor query's te helpen bepalen.
Download de whitepaper vandaag PostgreSQL-beheer en -automatisering met ClusterControlLees wat u moet weten om PostgreSQL te implementeren, bewaken, beheren en schalenDownload de whitepaperConfiguratieparameters
Om deze parameters te wijzigen, moeten we het bestand $ PGDATA / postgresql.conf bewerken. We moeten er rekening mee houden dat sommige ervan een herstart van onze database vereisen.
max_connections
Bepaalt het maximale aantal gelijktijdige verbindingen met onze database. Er zijn geheugenbronnen die per client kunnen worden geconfigureerd, daarom kan het maximale aantal clients de maximale hoeveelheid gebruikt geheugen suggereren.
superuser_reserved_connections
Als de limiet van max_connection wordt bereikt, zijn deze verbindingen gereserveerd voor superuser.
shared_buffers
Stelt de hoeveelheid geheugen in die de databaseserver gebruikt voor gedeelde geheugenbuffers. Als u een speciale databaseserver hebt met 1 GB of meer RAM, is een redelijke beginwaarde voor gedeelde_buffers 25% van het systeemgeheugen. Grotere configuraties voor shared_buffers vereisen over het algemeen een overeenkomstige toename van max_wal_size, om het proces van het schrijven van grote hoeveelheden nieuwe of gewijzigde gegevens over een langere periode uit te breiden.
temp_buffers
Stelt het maximum aantal tijdelijke buffers in dat voor elke sessie wordt gebruikt. Dit zijn lokale sessiebuffers die alleen worden gebruikt om toegang te krijgen tot tijdelijke tabellen. Een sessie zal de tijdelijke buffers naar behoefte toewijzen tot de limiet die wordt gegeven door temp_buffers.
work_mem
Specificeert de hoeveelheid geheugen die zal worden gebruikt door de interne bewerkingen van ORDER BY, DISTINCT, JOIN en hash-tabellen voordat naar de tijdelijke bestanden op schijf wordt geschreven. Bij het configureren van deze waarde moeten we er rekening mee houden dat verschillende sessies deze bewerkingen tegelijkertijd uitvoeren en dat elke bewerking zoveel geheugen mag gebruiken als gespecificeerd door deze waarde voordat het begint met het schrijven van gegevens in tijdelijke bestanden.
Deze optie heette sort_mem in oudere versies van PostgreSQL.
maintenance_work_mem
Specificeert de maximale hoeveelheid geheugen die onderhoudsbewerkingen zullen gebruiken, zoals VACUUM, CREATE INDEX en ALTER TABLE ADD FOREIGN KEY. Aangezien slechts één van deze bewerkingen tegelijkertijd door een sessie kan worden uitgevoerd, en een installatie heeft er meestal niet veel tegelijk, kan deze groter zijn dan de work_mem. Grotere configuraties kunnen de prestaties voor VACUUM- en databaseherstel verbeteren.
Wanneer het autovacuüm wordt uitgevoerd, kan aan dit geheugen het aantal keren worden toegewezen waarin de parameter autovacuum_max_workers is geconfigureerd, dus we moeten hier rekening mee houden, of anders de parameter autovacuum_work_mem configureren om dit afzonderlijk te beheren.
fsync
Als fsync is ingeschakeld, zal PostgreSQL proberen ervoor te zorgen dat de updates fysiek naar de schijf worden geschreven. Dit zorgt ervoor dat het databasecluster kan worden hersteld naar een consistente staat na een besturingssysteem- of hardwarecrash.
Hoewel het uitschakelen van fsync over het algemeen de prestaties verbetert, kan het gegevensverlies veroorzaken in het geval van een stroomstoring of een systeemcrash. Daarom is het alleen aan te raden om fsync uit te schakelen als je gemakkelijk je hele database kunt recreëren op basis van externe gegevens.
checkpoint_segments (PostgreSQL <9.5)
Maximum aantal recordbestandssegmenten tussen automatische WAL-controlepunten (elk segment is normaal gesproken 16 megabyte). Door deze parameter te verhogen, kan de hoeveelheid tijd die nodig is om fouten te herstellen toenemen. In een systeem met veel verkeer kan het de prestaties beïnvloeden als het op een zeer lage waarde wordt ingesteld. Het wordt aanbevolen om de waarde van checkpoint_segments te verhogen op systemen met veel gegevensaanpassingen.
Het is ook een goede gewoonte om de WAL-bestanden op een andere schijf dan PGDATA op te slaan. Dit is zowel handig voor het balanceren van het schrijven als voor de beveiliging in geval van hardwarestoring.
Vanaf PostgreSQL 9.5 is de configuratievariabele "checkpoint_segments" verwijderd en vervangen door "max_wal_size" en "min_wal_size"
max_wal_size (PostgreSQL>=9.5)
Maximale grootte die de WAL mag groeien tussen de controlepunten. De grootte van WAL kan in speciale omstandigheden max_wal_size overschrijden. Het verhogen van deze parameter kan de hoeveelheid tijd die nodig is om fouten te herstellen verlengen.
min_wal_size (PostgreSQL>=9.5)
Wanneer het WAL-bestand onder deze waarde wordt gehouden, wordt het gerecycled voor toekomstig gebruik bij een controlepunt, in plaats van te worden verwijderd. Dit kan worden gebruikt om ervoor te zorgen dat er voldoende WAL-ruimte wordt gereserveerd om pieken in het gebruik van WAL op te vangen, bijvoorbeeld bij het uitvoeren van grote batchtaken.
wal_sync_methode
Methode die wordt gebruikt om WAL-updates naar de schijf te forceren. Als fsync is uitgeschakeld, heeft deze instelling geen effect.
wal_buffers
De hoeveelheid gedeeld geheugen die wordt gebruikt voor WAL-gegevens die nog niet naar schijf zijn geschreven. De standaardinstelling is ongeveer 3% van shared_buffers, niet minder dan 64 KB of meer dan de grootte van een WAL-segment (meestal 16 MB). Als u deze waarde instelt op minimaal een paar MB, kunnen de schrijfprestaties op een server met veel gelijktijdige transacties worden verbeterd.
effectieve_cache_size
Deze waarde wordt door de queryplanner gebruikt om rekening te houden met plannen die al dan niet in het geheugen passen. Bij de kostenramingen van het gebruik van een index wordt hiermee rekening gehouden; een hoge waarde maakt het waarschijnlijker dat indexscans worden gebruikt en een lage waarde maakt het waarschijnlijker dat sequentiële scans worden gebruikt. Een redelijke waarde zou 50% van het RAM-geheugen zijn.
default_statistics_target
PostgreSQL verzamelt statistieken van elk van de tabellen in zijn database om te beslissen hoe query's daarop worden uitgevoerd. Standaard verzamelt het niet te veel informatie, en als u geen goede uitvoeringsplannen krijgt, moet u deze waarde verhogen en vervolgens ANALYSE opnieuw in de database uitvoeren (of wachten op de AUTOVACUUM).
synchronous_commit
Geeft aan of de transactie-commit zal wachten tot de WAL-records naar de schijf zijn geschreven voordat de opdracht een "succes"-indicatie naar de client retourneert. De mogelijke waarden zijn:"on", "remote_apply", "remote_write", "local" en "off". De standaardinstelling is "aan". Als het is uitgeschakeld, kan er een vertraging zijn tussen het moment dat de klant terugkeert en het moment waarop de transactie gegarandeerd beveiligd is tegen een serververgrendeling. In tegenstelling tot fsync, leidt het uitschakelen van deze parameter niet tot een risico van inconsistentie in de database:een crash van het besturingssysteem of de database kan leiden tot het verlies van enkele recente transacties die zogenaamd zijn gepleegd, maar de status van de database zal precies hetzelfde zijn alsof die transacties netjes was geannuleerd. Daarom kan het deactiveren van synchronous_commit een handig alternatief zijn wanneer prestaties belangrijker zijn dan de exacte zekerheid over de duurzaamheid van een transactie.
Logboekregistratie
Er zijn verschillende soorten gegevens om te loggen die nuttig kunnen zijn of niet. Laten we er een paar bekijken:
- log_min_error_statement:Stelt het minimale logniveau in.
- log_min_duration_statement:wordt gebruikt om langzame zoekopdrachten in het systeem op te nemen.
- log_line_prefix:houdt zich aan informatie aan het begin van elke logregel.
- log_statement:u kunt kiezen tussen NONE, DDL, MOD, ALL. Het gebruik van "alles" kan prestatieproblemen veroorzaken.
Ontwerp
In veel gevallen kan het ontwerp van onze database de prestaties beïnvloeden. We moeten voorzichtig zijn in ons ontwerp, ons schema normaliseren en overbodige gegevens vermijden. In veel gevallen is het handig om meerdere kleine tafels te hebben in plaats van één grote tafel. Maar zoals we al eerder zeiden, alles hangt af van ons systeem en er is niet één mogelijke oplossing.
We moeten de indexen ook verantwoord gebruiken. We zouden geen indexen moeten maken voor elk veld of elke combinatie van velden, want hoewel we niet de hele tabel hoeven af te leggen, gebruiken we schijfruimte en voegen we overhead toe aan schrijfbewerkingen.
Een ander zeer handig hulpmiddel is het beheer van de verbindingspool. Als we een systeem hebben met veel belasting, kunnen we dit gebruiken om verzadiging van de verbindingen in de database te voorkomen en om ze opnieuw te kunnen gebruiken.
Hardware
Zoals we aan het begin van deze blog vermeldden, is hardware een van de belangrijke factoren die de prestaties van onze database direct beïnvloeden. Laten we een paar punten bekijken om in gedachten te houden.
- Geheugen:hoe meer RAM we hebben, hoe meer geheugengegevens we aankunnen, en dat betekent betere prestaties. De snelheid van schrijven en lezen op schijf is veel langzamer dan in het geheugen, dus hoe meer informatie we in het geheugen kunnen hebben, hoe beter de prestaties zullen zijn.
- CPU:Misschien heeft het weinig zin om dit te zeggen, maar hoe meer CPU we hebben, hoe beter. Qua hardware is het in ieder geval niet het belangrijkste, maar als we een goede CPU kunnen hebben, zal onze verwerkingscapaciteit verbeteren en dat heeft direct invloed op onze database.
- Harde schijf:we hebben verschillende soorten schijven die we kunnen gebruiken, SCSI, SATA, SAS, IDE. We hebben ook solid-state schijven. We moeten kwaliteit / prijs vergelijken, die we moeten gebruiken om de snelheid te vergelijken. Maar het type schijf is niet het enige waarmee u rekening moet houden, we moeten ook zien hoe u ze kunt configureren. Als we goede prestaties willen, kunnen we RAID10 gebruiken, waarbij we de WAL's op een andere schijf buiten de RAID houden. Het wordt niet aanbevolen om RAID5 te gebruiken omdat de prestaties van dit type RAID voor databases niet goed zijn.
Conclusie
Nadat we rekening hebben gehouden met de punten die in deze blog worden genoemd, kunnen we een benchmark uitvoeren om het gedrag van de database te verifiëren.
Het is ook belangrijk om onze database te laten monitoren om te bepalen of we een prestatieprobleem hebben en om dit zo snel mogelijk op te lossen. Voor deze taak zijn er verschillende tools zoals Nagios, ClusterControl of Zabbix, die ons niet alleen in staat stellen om te monitoren, maar met een aantal daarvan kunnen we proactief actie ondernemen voordat het probleem zich voordoet. Met ClusterControl kunnen we, naast monitoring, beheer en verschillende andere hulpprogramma's, aanbevelingen ontvangen over welke acties we kunnen ondernemen bij het ontvangen van prestatiewaarschuwingen. Dit stelt ons in staat om een idee te hebben van hoe mogelijke problemen kunnen worden opgelost.
Deze blog is niet bedoeld als een uitputtende gids voor het verbeteren van de databaseprestaties. Hopelijk geeft het een duidelijker beeld van welke dingen belangrijk kunnen worden en enkele van de basisparameters die kunnen worden geconfigureerd. Aarzel niet om het ons te laten weten als we belangrijke hebben gemist.