For et par uger siden forklarede jeg det grundlæggende i autovakuum tuning. I slutningen af det indlæg lovede jeg snart at undersøge problemer med støvsugning. Nå, det tog lidt længere tid, end jeg havde planlagt, men nu går vi.
For hurtigt at opsummere, autovacuum
er en baggrundsproces, der rydder op i døde rækker, f.eks. gamle slettede rækkeversioner. Du kan også udføre oprydningen manuelt ved at køre VACUUM
, men autovacuum
gør det automatisk afhængigt af mængden af døde rækker i tabellen på det rigtige tidspunkt - ikke for ofte, men ofte nok til at holde mængden af "skrald" under kontrol.
Generelt set autovacuum
kan ikke køre for ofte - oprydningen udføres kun efter at have nået et antal døde rækker, der akkumuleres i tabellen. Men det kan blive forsinket af forskellige årsager, hvilket resulterer i, at tabeller og indekser bliver større end ønskeligt. Og det er netop emnet for dette indlæg. Så hvad er de almindelige syndere, og hvordan identificerer man dem?
Throttling
Som forklaret i grundlæggende tuning, autovacuum
arbejdere begrænses til kun at udføre en vis mængde arbejde pr. tidsinterval. Standardgrænserne er ret lave – omkring 4 MB/s skrivning, 8 MB/s læsninger. Det er velegnet til små maskiner som Raspberry Pi eller små servere for 10 år siden, men de nuværende maskiner er langt mere kraftfulde (både med hensyn til CPU og I/O) og håndterer meget flere data.
Forestil dig, at du har et par store borde og nogle små. Hvis alle tre autovacuum
arbejdere begynder at rydde op i de store borde, ingen af de små borde bliver støvsuget uanset mængden af døde rækker, de akkumulerer. Det er ikke særlig svært at identificere dette, forudsat at du har tilstrækkelig overvågning. Se efter perioder, hvor alle autovacuum
arbejdere har travlt, mens borde ikke støvsuges på trods af, at de har akkumuleret mange døde rækker.
Alle de nødvendige oplysninger er i pg_stat_activity
(antal autovacuum
arbejdsprocesser) og pg_stat_all_tables
(last_autovacuum
og n_dead_tup
).
Forøgelse af antallet af autovacuum
arbejdere er ikke en løsning, da den samlede mængde arbejde forbliver den samme. Du kan angive spjældgrænser pr. bord, udelukke den pågældende arbejder fra den samlede grænse, men det garanterer stadig ikke, at der vil være ledige arbejdere, når det er nødvendigt.
Den rigtige løsning er at justere reguleringen ved at bruge grænser, der er rimelige med hensyn til hardwarekonfiguration og arbejdsbelastningsmønstre. Nogle grundlæggende reguleringsanbefalinger er nævnt i det forrige indlæg. (Hvis du kan reducere mængden af døde rækker, der genereres i databasen, ville det naturligvis være en ideel løsning.)
Fra dette tidspunkt vil vi antage, at reguleringen ikke er problemet, dvs. at autovacuum
arbejdere ikke er mættede i lange perioder, og at oprydningen udløses på alle borde uden urimelige forsinkelser.
Lange transaktioner
Så hvis bordet støvsuges regelmæssigt, kan det vel ikke akkumulere mange døde rækker, vel? Desværre ikke. Rækkerne er faktisk ikke "fjernbare" umiddelbart efter at de er blevet slettet, men kun når der ikke er nogen transaktioner, der muligvis kan se dem. Den nøjagtige adfærd afhænger af, hvad de andre transaktioner (var) i gang med og serialiseringsniveau, men generelt:
LÆS ENGAGEMENT
- kørende forespørgsler blokerer oprydning
- inaktive transaktioner blokerer kun for oprydning, hvis de udførte en skrivning
- tomgangstransaktioner (uden skrivninger) blokerer ikke for oprydning (men det er alligevel ikke en god praksis at beholde dem)
SERIALISERBAR
- kørende forespørgsler blokerer oprydning
- inaktive transaktioner blokerer for oprydning (selvom de kun læste)
I praksis er det selvfølgelig mere nuanceret, men at forklare alle de forskellige bits ville kræve først at forklare, hvordan XID'er og snapshots fungerer, og det er ikke målet med dette indlæg. Hvad du virkelig bør tage væk fra dette er, at lange transaktioner er en dårlig idé, især hvis disse transaktioner måske har skrevet.
Selvfølgelig er der helt gyldige grunde til, at du muligvis skal beholde transaktioner i lange perioder (f.eks. hvis du skal sikre ACID for alle ændringerne). Men sørg for, at det ikke sker unødigt, f.eks. på grund af et dårligt applikationsdesign.
En noget uventet konsekvens af dette er højt CPU- og I/O-forbrug på grund af autovacuum
kører igen og igen, uden at rense nogen døde rækker (eller blot nogle få af dem). Derfor er bordene stadig kvalificerede til oprydning i næste runde, hvilket forårsager mere skade end gavn.
Hvordan opdager man dette? For det første skal du overvåge langvarige transaktioner, især inaktive. Alt du skal gøre er at læse data fra pg_stat_activity
. Visningsdefinitionen ændrer sig lidt med PostgreSQL-versionen, så du skal muligvis justere denne lidt:
SELECT xact_start, state FROM pg_stat_activity; -- count 'idle' transactions longer than 15 minutes (since BEGIN) SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'idle in transaction' AND (now() - xact_start) > interval '15 minutes' -- count transactions 'idle' for more than 5 minutes SELECT COUNT(*) FROM pg_stat_activity WHERE state = 'idle in transaction' AND (now() - state_change) > interval '5 minutes'
Du kan også blot bruge nogle eksisterende overvågningsplugin, f.eks. check_postgres.pl. Disse inkluderer allerede denne type sundhedstjek. Du skal beslutte, hvad der er en rimelig transaktion/forespørgselsvarighed, som er applikationsspecifik.
Siden PostgreSQL 9.6 kan du også bruge idle_in_transaction_session_timeout
så transaktioner, der har været inaktive for længe, afsluttes automatisk. Tilsvarende er der for lange forespørgsler statement_timeout
.
En anden nyttig ting er VACUUM VERBOSE
som faktisk vil fortælle dig, hvor mange døde rækker der ikke kunne fjernes endnu:
db=# VACUUM verbose z; INFO: vacuuming "public.z" INFO: "z": found 0 removable, 66797 nonremovable row versions in 443 out of 443 pages DETAIL: 12308 dead row versions cannot be removed yet. ...
Det vil ikke fortælle dig, hvilken backend der forhindrer oprydningen, men det er et ret tydeligt tegn på, hvad der sker.
Bemærk: . Du kan ikke nemt få disse oplysninger fra autovacuum
fordi det kun er logget med DEBUG2
som standard (og du vil bestemt ikke køre med det logniveau i produktionen).
Lange forespørgsler på varme standbys
Lad os antage, at borde bliver støvsuget rettidigt, men ikke fjerner døde tupler, hvilket resulterer i bord- og indeksbloat. Du overvåger pg_stat_activity
og der er ingen langvarige transaktioner. Hvad kan problemet være?
Hvis du har en streaming-replika, er der sandsynlighed for, at problemet kan være der. Hvis replikaen bruger hot_standby_feedback=on
, forespørgsler på replikaen fungerer stort set som transaktioner på den primære, inklusive blokering af oprydning. Selvfølgelig, hot_standby_feedback=on
bruges nøjagtigt, når der køres lange forespørgsler (f.eks. analyser og BI-arbejdsbelastninger) på replikaer, for at forhindre annulleringer på grund af replikeringskonflikter.
Desværre bliver du nødt til at vælge – enten behold hot_standby_feedback=on
og acceptere forsinkelser i oprydningen, eller håndtere annullerede forespørgsler. Du kan også bruge max_standby_streaming_delay
for at begrænse virkningen, selvom det ikke forhindrer aflysningerne helt (så du skal stadig prøve forespørgslerne igen).
Faktisk er der en tredje mulighed nu - logisk replikering. I stedet for at bruge fysisk streaming replikering til BI replikaen, kan du kopiere ændringerne ved hjælp af den nye logiske replikering, tilgængelig i PostgreSQL 10. Logisk replikering afslapper koblingen mellem den primære og replikaen, og gør klyngerne for det meste uafhængige (ryddes op uafhængigt, osv.).
Dette løser de to problemer forbundet med fysisk streaming-replikering – forsinket oprydning på primære eller annullerede forespørgsler på BI-replikaen. Til replikaer, der tjener DR-formål, er streaming-replikering dog stadig det rigtige valg. Men disse replikaer kører ikke (eller burde ikke køre) lange forespørgsler.
Bemærk: Mens jeg nævnte, at logisk replikering vil være tilgængelig i PostgreSQL 10, var en betydelig del af infrastrukturen tilgængelig i tidligere udgivelser (især PostgreSQL 9.6). Så du kan muligvis gøre dette selv på ældre udgivelser (det gjorde vi for nogle af vores kunder), men PostgreSQL 10 vil gøre det meget mere praktisk og behageligt.
Problemer med autoanalyze
En detalje, du måske går glip af, er autovacuum
arbejdere udfører faktisk to forskellige opgaver. Først oprydningen (som om du kører VACUUM
), men også indsamling af statistik (som om du kører ANALYZE
). Og begge dele dele er droslet ved hjælp af autovacuum_cost_limit
.
Men der er stor forskel på at håndtere transaktioner. Hver gang VACUUM
del når autovacuum_cost_limit
, frigiver arbejderen øjebliksbilledet og sover et stykke tid. ANALYZE
skal dog køre i et enkelt øjebliksbillede/transaktion, hvilket gør blokere oprydning.
Dette er en elegant måde at skyde dig selv i foden på, især hvis du også gør noget af dette:
- øg
default_statistics_target
at opbygge mere nøjagtig statistik fra større stikprøver - lavere
autovacuum_analyze_scale_factor
at indsamle statistik oftere
Den utilsigtede konsekvens er selvfølgelig, at ANALYZE
vil ske hyppigere, vil tage meget længere tid og vil (i modsætning til VACUUM
). del) forhindre oprydning. Løsningen er normalt ret enkel – sænk ikke autovacuum_analyze_scale_factor
for meget. Kører ANALYZE
hver gang 10 % af tabelændringerne burde være mere end nok i de fleste tilfælde.
n_dead_tup
En sidste ting, jeg gerne vil nævne, er om ændringer i pg_stat_all_tables.n_dead_tup
værdier. Du tror måske, at værdien er en simpel tæller, der øges, når en ny død tupel oprettes, og nedsættes, når den er ryddet op. Men det er faktisk kun et skøn over antallet af døde tupler, opdateret af ANALYZE
. For små borde (mindre end 240 MB) er det egentlig ikke den store forskel, fordi ANALYZE
læser hele tabellen og så er den ret præcis. For store tabeller kan det dog ændre sig en del afhængigt af hvilken undergruppe af tabellen, der bliver samplet. Og sænker autovacuum_vacuum_scale_factor
gør det mere tilfældigt.
Så vær forsigtig, når du ser på n_dead_tup
i et overvågningssystem. Pludselige fald eller stigninger i værdien kan simpelthen skyldes ANALYZE
genberegning af et andet estimat og ikke på grund af faktisk oprydning og/eller nye døde tupler, der dukker op i tabellen.
Oversigt
For at opsummere dette i et par enkle punkter:
autovacuum
kan kun gøre dets arbejde, hvis der ikke er nogen transaktioner, der måske har brug for de døde tupler.- Langevarende forespørgsler blokerer for oprydning. Overvej at bruge
statement_timeout
for at begrænse skaden. - Langevarende transaktion kan blokere for oprydning. Den nøjagtige adfærd afhænger af ting som isolationsniveau eller hvad der skete i transaktionen. Overvåg dem og afslut dem, hvis det er muligt.
- Langevarende forespørgsler på replikaer med
hot_standby_feedback=on
kan også blokere for oprydning. autoanalyze
er også droslet, men i modsætning tilVACUUM
del det holder et enkelt øjebliksbillede (og dermed blokerer oprydning).n_dead_tup
er kun et estimat vedligeholdt afANALYZE
, så forvent nogle udsving (især på store borde).