We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB

Onlangs vertelde ik je hoe, aan de hand van standaardrecepten verhoog de prestaties van SQL-leesquery's uit de PostgreSQL-database. Vandaag zullen we praten over hoe opnemen kan efficiënter in de database zonder enige “wendingen” in de configuratie te gebruiken - eenvoudigweg door de gegevensstromen correct te organiseren.

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB

#1. Secties

Een artikel over hoe en waarom het de moeite waard is om te organiseren toegepaste partitie “in theorie” al is geweest, zullen we hier praten over de praktijk van het toepassen van enkele benaderingen binnen onze monitoringservice voor honderden PostgreSQL-servers.

"Dingen van vervlogen tijden..."

Aanvankelijk begon ons project, zoals elke MVP, onder een vrij lichte belasting - de monitoring werd alleen uitgevoerd voor de tien meest kritische servers, alle tabellen waren relatief compact... Maar naarmate de tijd verstreek, werd het aantal bewaakte hosts steeds groter , en opnieuw probeerden we iets met een van te doen tafels van 1.5 TB grootbeseften we dat het weliswaar mogelijk was om zo te blijven leven, maar dat het erg lastig was.

Het waren bijna epische tijden, verschillende versies van PostgreSQL 9.x waren relevant, dus alle partities moesten “handmatig” worden gedaan - via tabelovererving en triggers routering met dynamiek EXECUTE.

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB
De resulterende oplossing bleek universeel genoeg te zijn om naar alle tabellen te kunnen worden vertaald:

  • Er werd een lege bovenliggende tabel met de koptekst gedeclareerd, waarin alles werd beschreven noodzakelijke indexen en triggers.
  • Het record vanuit het perspectief van de klant werd gemaakt in de "root" -tabel en werd intern gebruikt routeringstrigger BEFORE INSERT het record werd “fysiek” in de vereiste sectie ingevoegd. Als zoiets nog niet bestond, hebben we een uitzondering gemaakt en...
  • … door het gebruiken van CREATE TABLE ... (LIKE ... INCLUDING ...) is gemaakt op basis van de sjabloon van de bovenliggende tabel sectie met een beperking op de gewenste datumzodat wanneer gegevens worden opgehaald, er alleen daarin wordt gelezen.

PG10: eerste poging

Maar het partitioneren via overerving is historisch gezien niet erg geschikt geweest voor het omgaan met een actieve schrijfstroom of een groot aantal onderliggende partities. U kunt zich bijvoorbeeld herinneren dat het algoritme voor het selecteren van de vereiste sectie had kwadratische complexiteit, dat het werkt met 100+ secties, je begrijpt zelf hoe...

In PG10 werd deze situatie sterk geoptimaliseerd door het implementeren van ondersteuning inheemse partitie. Daarom hebben we het meteen geprobeerd toe te passen na het migreren van de opslag, maar...

Zoals bleek na het doornemen van de handleiding, is de native gepartitioneerde tabel in deze versie:

  • ondersteunt geen indexbeschrijvingen
  • ondersteunt geen triggers erop
  • kan niemands “afstammeling” zijn
  • niet ondersteunen INSERT ... ON CONFLICT
  • kan niet automatisch een sectie genereren

Nadat we met een hark een pijnlijke klap op het voorhoofd hadden gekregen, realiseerden we ons dat het onmogelijk zou zijn om te doen zonder de applicatie aan te passen, en stelden we verder onderzoek zes maanden uit.

PG10: tweede kans

Dus begonnen we de problemen die zich voordeden één voor één op te lossen:

  1. Omdat triggers en ON CONFLICT Omdat we merkten dat we ze hier en daar nog nodig hadden, hebben we een tussenstap gemaakt om ze uit te werken proxy-tabel.
  2. Weg met "routing" in triggers - dat wil zeggen, van EXECUTE.
  3. Ze hebben het er apart uitgehaald sjabloontabel met alle indexenzodat ze niet eens aanwezig zijn in de proxytabel.

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB
Tenslotte hebben we, na dit alles, de hoofdtabel native gepartitioneerd. Het creëren van een nieuwe sectie wordt nog steeds aan het geweten van de applicatie overgelaten.

Woordenboeken ‘zagen’

Zoals bij elk analytisch systeem hadden we dat ook "feiten" en "bezuinigingen" (woordenboeken). In ons geval handelden zij in deze hoedanigheid bijvoorbeeld sjabloonlichaam soortgelijke langzame zoekopdrachten of de tekst van de zoekopdracht zelf.

'Feiten' werden al lange tijd per dag onderverdeeld, dus hebben we rustig verouderde secties verwijderd, en we hebben er geen last van gehad (logs!). Maar er was een probleem met woordenboeken...

Om niet te zeggen dat het er veel waren, maar ongeveer 100 TB aan ‘feiten’ resulteerde in een woordenboek van 2.5 TB. Je kunt niet gemakkelijk iets uit zo'n tabel verwijderen, je kunt het niet binnen de juiste tijd comprimeren, en het schrijven ernaar werd geleidelijk langzamer.

Net als in een woordenboek... daarin zou elk item precies één keer moeten worden weergegeven... en dat klopt, maar!.. Niemand houdt ons tegen om voor elke dag een apart woordenboek! Ja, dit brengt een zekere redundantie met zich mee, maar het maakt het volgende mogelijk:

  • schrijf/lees sneller vanwege de kleinere sectiegrootte
  • verbruiken minder geheugen door met compactere indexen te werken
  • minder gegevens opslaan vanwege de mogelijkheid om verouderde bestanden snel te verwijderen

Als gevolg van het hele complex aan maatregelen CPU-belasting verlaagd met ~30%, schijfbelasting met ~50%:

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB
Tegelijkertijd bleven we precies hetzelfde in de database schrijven, alleen met minder belasting.

#2. Database-evolutie en refactoring

Dus we hebben ons gebaseerd op wat we hebben elke dag heeft zijn eigen sectie met gegevens. Eigenlijk, CHECK (dt = '2018-10-12'::date) – en er is een partitiesleutel en de voorwaarde dat een record in een specifieke sectie valt.

Omdat alle rapporten in onze service zijn gebouwd in de context van een specifieke datum, zijn de indexen daarvoor sinds “niet-gepartitioneerde tijden” van alle typen geweest (Server, datum, Plansjabloon), (Server, datum, Planknooppunt), (datum, Foutklasse, Server), ...

Maar nu wonen ze op elke sectie uw kopieën elke dergelijke index... En binnen elke sectie datum is een constante... Het blijkt dat we nu in elk van deze indexen zitten voer gewoon een constante in als een van de velden, wat zowel het volume als de zoektijd ervoor vergroot, maar geen resultaat oplevert. Ze hebben de hark aan zichzelf overgelaten, oeps...

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB
De richting van optimalisatie is duidelijk - eenvoudig verwijder het datumveld uit alle indexen op gepartitioneerde tabellen. Gezien onze volumes is de winst ongeveer 1TB/week!

Laten we er nu rekening mee houden dat deze terabyte op de een of andere manier nog steeds geregistreerd moest worden. Dat wil zeggen: wij ook de schijf zou nu minder moeten laden! Deze foto laat duidelijk het effect zien dat wordt verkregen door de schoonmaak, waaraan we een week hebben besteed:

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB

#3. Het “spreiden” van de piekbelasting

Een van de grote problemen van geladen systemen is redundante synchronisatie sommige bewerkingen waarvoor dit niet nodig is. Soms “omdat ze het niet merkten”, soms “zo was het makkelijker”, maar vroeg of laat moet je er vanaf.

Laten we inzoomen op de vorige afbeelding en zien dat we een schijf hebben “pompt” onder de belasting met dubbele amplitude tussen aangrenzende monsters, wat duidelijk “statistisch” niet zou mogen gebeuren met een dergelijk aantal bewerkingen:

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB

Dit is vrij eenvoudig te bereiken. We zijn al begonnen met monitoren bijna 1000 servers, elk wordt verwerkt door een afzonderlijke logische thread, en elke thread reset de verzamelde informatie die met een bepaalde frequentie naar de database moet worden verzonden, ongeveer als volgt:

setInterval(sendToDB, interval)

Het probleem ligt hier juist in het feit dat alle threads beginnen ongeveer op hetzelfde tijdstip, dus hun verzendtijden vallen bijna altijd ‘to the point’ samen. Oeps #2...

Gelukkig is dit vrij eenvoudig op te lossen, het toevoegen van een “willekeurige” aanloop tegen de tijd:

setInterval(sendToDB, interval * (1 + 0.1 * (Math.random() - 0.5)))

#4. We cachen wat we nodig hebben

Het derde traditionele probleem met hoge belasting is geen cache waar hij is kon .

We hebben het bijvoorbeeld mogelijk gemaakt om te analyseren in termen van planknooppunten (al deze Seq Scan on users), maar denken meteen dat ze voor het grootste deel hetzelfde zijn - ze zijn het vergeten.

Nee, er wordt natuurlijk niets meer naar de database geschreven, hiermee wordt de trigger afgebroken INSERT ... ON CONFLICT DO NOTHING. Maar deze gegevens bereiken nog steeds de database en dat is niet nodig lezen om te controleren op conflicten moeten doen. Oeps #3...

Het verschil in het aantal records dat naar de database wordt verzonden vóór/nadat caching is ingeschakeld, is duidelijk:

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB

En dit is de bijbehorende daling van de opslagbelasting:

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB

In totaal

“Terabyte per dag” klinkt gewoon eng. Als je alles goed doet, dan is dit gewoon 2^40 bytes / 86400 seconden = ~12.5 MB/sdat zelfs desktop IDE-schroeven vasthielden. 🙂

Maar serieus, zelfs met een tienvoudige "scheefheid" van de belasting gedurende de dag kun je gemakkelijk voldoen aan de mogelijkheden van moderne SSD's.

We schrijven in PostgreSQL over sublight: 1 host, 1 dag, 1TB

Bron: www.habr.com

Voeg een reactie