Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB

Nyligen berättade jag hur, med hjälp av standardrecept öka prestandan för SQL-läsfrågor från PostgreSQL-databasen. Idag ska vi prata om hur inspelning kan göras mer effektivt i databasen utan att använda några "vridningar" i konfigurationen - helt enkelt genom att korrekt organisera dataflödena.

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB

#1. Sektionering

En artikel om hur och varför det är värt att organisera tillämpad partitionering "i teorin" har redan varit, här kommer vi att prata om praxis att tillämpa några tillvägagångssätt inom vår övervakningstjänst för hundratals PostgreSQL-servrar.

"Saker från svunna dagar..."

Inledningsvis, som alla MVP, startade vårt projekt under en ganska lätt belastning - övervakning utfördes endast för de tio mest kritiska servrarna, alla tabeller var relativt kompakta... Men allt eftersom tiden gick blev antalet övervakade värdar fler och fler , och återigen försökte vi göra något med en av bord 1.5TB i storlek, insåg vi att även om det var möjligt att fortsätta leva så här, så var det väldigt obekvämt.

Tiderna var nästan som episka tider, olika versioner av PostgreSQL 9.x var relevanta, så all partitionering måste göras "manuellt" - genom bordsarv och triggers routing med dynamik EXECUTE.

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB
Den resulterande lösningen visade sig vara universell nog att den kunde översättas till alla tabeller:

  • En tom "header" överordnad tabell deklarerades, som beskrev allt nödvändiga index och triggers.
  • Rekordet från klientens synvinkel gjordes i "root"-tabellen och internt med hjälp av routing trigger BEFORE INSERT posten infogades "fysiskt" i den obligatoriska delen. Om det inte fanns något sådant ännu, fick vi ett undantag och...
  • … genom att använda CREATE TABLE ... (LIKE ... INCLUDING ...) skapades baserat på mallen för den överordnade tabellen avsnitt med en begränsning av önskat datumså att när data hämtas utförs läsning endast i den.

PG10: första försöket

Men partitionering genom arv har historiskt sett inte varit väl lämpad för att hantera en aktiv skrivström eller ett stort antal underordnade partitioner. Till exempel kan du komma ihåg att algoritmen för att välja önskad sektion hade kvadratisk komplexitet, att det fungerar med 100+ avsnitt förstår du själv hur...

I PG10 optimerades denna situation avsevärt genom att implementera support inbyggd partitionering. Därför försökte vi omedelbart använda det direkt efter migreringen av lagringen, men...

Som det visade sig efter att ha grävt igenom manualen är den inbyggda partitionerade tabellen i den här versionen:

  • stöder inte indexbeskrivningar
  • stöder inte triggers på den
  • kan inte vara någons "ättling"
  • stöder inte INSERT ... ON CONFLICT
  • kan inte generera ett avsnitt automatiskt

Efter att ha fått ett smärtsamt slag i pannan med en kratta insåg vi att det skulle vara omöjligt att göra utan att modifiera ansökan, och sköt upp ytterligare forskning i sex månader.

PG10: andra chansen

Så vi började lösa problemen som uppstod ett efter ett:

  1. Eftersom utlöser och ON CONFLICT Vi upptäckte att vi fortfarande behövde dem här och där, så vi gjorde ett mellansteg för att reda ut dem proxytabell.
  2. Blev av med "routing" i triggers - det vill säga från EXECUTE.
  3. De tog ut den separat malltabell med alla indexså att de inte ens finns i proxytabellen.

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB
Slutligen, efter allt detta, partitionerade vi huvudtabellen inbyggt. Skapandet av en ny sektion lämnas fortfarande till ansökans samvete.

"Såga" ordböcker

Som i alla analytiska system hade vi också "fakta" och "nedskärningar" (ordböcker). I vårt fall handlade de i denna egenskap t.ex. mallkropp liknande långsamma frågor eller texten i själva frågan.

"Fakta" sektionerades efter dag under lång tid redan, så vi tog lugnt bort föråldrade avsnitt, och de störde oss inte (loggar!). Men det var ett problem med ordböcker...

För att inte säga att det var många, men ungefär 100 TB "fakta" resulterade i en 2.5 TB ordbok. Du kan inte bekvämt ta bort någonting från en sådan tabell, du kan inte komprimera den i tillräcklig tid, och att skriva till den blev gradvis långsammare.

Som en ordbok... i den ska varje post presenteras exakt en gång... och det är korrekt, men!.. Ingen hindrar oss från att ha en separat ordbok för varje dag! Ja, detta ger en viss redundans, men det tillåter:

  • skriva/läsa snabbare på grund av mindre sektionsstorlek
  • förbrukar mindre minne genom att arbeta med mer kompakta index
  • lagra mindre data på grund av möjligheten att snabbt ta bort föråldrade

Som ett resultat av hela komplexet av åtgärder CPU-belastningen minskade med ~30%, diskbelastningen med ~50%:

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB
Samtidigt fortsatte vi att skriva exakt samma sak i databasen, bara med mindre belastning.

#2. Databasutveckling och refaktorering

Så vi bestämde oss för vad vi har varje dag har sitt eget avsnitt med data. Faktiskt, CHECK (dt = '2018-10-12'::date) — och det finns en partitioneringsnyckel och villkoret för att en post ska falla in i en specifik sektion.

Eftersom alla rapporter i vår tjänst är byggda i samband med ett specifikt datum, har indexen för dem sedan "icke-partitionerade tider" varit alla typer (Server, Datum, planmall), (Server, Datum, plannod), (Datum, Felklass, Server), ...

Men nu bor de på varje avsnitt dina kopior varje sådant index... Och inom varje avsnitt datum är en konstant... Det visar sig att nu är vi i varje sådant index ange bara en konstant som ett av fälten, vilket ökar både dess volym och söktiden för det, men inte ger något resultat. De lämnade rakan åt sig själva, oj...

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB
Riktningen för optimeringen är uppenbar - enkel ta bort datumfältet från alla index på partitionerade tabeller. Med tanke på våra volymer är vinsten ca 1TB/vecka!

Låt oss nu notera att denna terabyte fortfarande måste spelas in på något sätt. Det vill säga vi också disken bör nu ladda mindre! Den här bilden visar tydligt effekten av rengöringen, som vi ägnade en vecka åt:

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB

#3. "Spridar" toppbelastningen

Ett av de stora problemen med laddade system är redundant synkronisering vissa operationer som inte kräver det. Ibland "för att de inte märkte det", ibland "det var lättare så", men förr eller senare måste man bli av med det.

Låt oss zooma in på föregående bild och se att vi har en disk "pumpar" under belastningen med dubbel amplitud mellan angränsande prover, vilket helt klart "statistiskt" inte borde ske med ett sådant antal operationer:

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB

Detta är ganska lätt att uppnå. Vi har redan börjat bevaka nästan 1000 servrar, var och en bearbetas av en separat logisk tråd, och varje tråd återställer den ackumulerade informationen som ska skickas till databasen med en viss frekvens, ungefär så här:

setInterval(sendToDB, interval)

Problemet här ligger just i det faktum att alla trådar startar ungefär samtidigt, så deras sändningstider sammanfaller nästan alltid "till punkten". Oj #2...

Lyckligtvis är detta ganska enkelt att fixa, lägga till en "slumpmässig" upptakt efter tid:

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

#4. Vi cachar det vi behöver

Det tredje traditionella högbelastningsproblemet är ingen cache där han är skulle kunna att vara.

Till exempel gjorde vi det möjligt att analysera i termer av plannoder (alla dessa Seq Scan on users), men tror genast att de är, för det mesta, likadana - de glömde.

Nej, självklart, inget skrivs till databasen igen, detta stänger av triggern med INSERT ... ON CONFLICT DO NOTHING. Men dessa data når fortfarande databasen, och det är onödigt läser för att kontrollera konflikter måste göra. Oj #3...

Skillnaden i antalet poster som skickas till databasen innan/efter cachning är aktiverad är uppenbar:

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB

Och detta är den medföljande minskningen av lagringsbelastningen:

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB

Totalt

"Terabyte-per-dag" låter bara skrämmande. Om du gör allt rätt, då är detta bara 2^40 byte / 86400 sekunder = ~12.5 MB/ssom även stationära IDE-skruvar höll. 🙂

Men seriöst, även med en tiofaldig "skev" av belastningen under dagen, kan du enkelt möta kapaciteten hos moderna SSD:er.

Skriver i PostgreSQL på sublight: 1 värd, 1 dag, 1TB

Källa: will.com

Lägg en kommentar