Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB

Recentemente vi dicu cumu, cù ricette standard aumentà u rendiment di e dumande di lettura SQL da a basa di dati PostgreSQL. Oghje avemu da parlà di cumu a registrazione pò esse fatta più efficace in a basa di dati senza usà alcuna "torte" in a cunfigurazione - solu organizendu currettamente i flussi di dati.

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB

#1. Seccionamentu

Un articulu nantu à cumu è perchè vale a pena urganizà spartizione applicata "in teoria" hè digià statu, quì avemu da parlà di a pratica di applicà qualchi avvicinamenti in u nostru serviziu di monitoraghju per centinaie di servitori PostgreSQL.

"Cose di i ghjorni passati..."

Inizialmente, cum'è qualsiasi MVP, u nostru prughjettu hà iniziatu sottu una carica abbastanza ligera - u monitoraghju hè statu realizatu solu per i deci servitori più critichi, tutti i tavulini eranu relativamente compacti... Ma u tempu passava, u numeru di ospiti monitorati hè diventatu più è più. , è una volta avemu pruvatu à fà qualcosa cù unu di tavule 1.5TB in taglia, avemu capitu chì ancu s'ellu era pussibule di cuntinuà à campà cusì, era assai inconveniente.

I tempi eranu quasi cum'è l'epica, e diverse versioni di PostgreSQL 9.x eranu pertinenti, cusì tutte e partizioni anu da esse fattu "manualmente" - attraversu l'eredità di a tavola è i triggers routing cù dinamica EXECUTE.

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB
A suluzione risultante hè stata abbastanza universale chì puderia esse tradutta à tutte e tavule:

  • Una tavola parentale "header" vacante hè stata dichjarata, chì descriva tuttu indici è triggers necessarii.
  • U registru da u puntu di vista di u cliente hè statu fattu in a tavola "radica", è internu utilizendu trigger di routing BEFORE INSERT u record hè statu "fisicu" inseritu in a sezione necessaria. S'ellu ùn ci era micca cusì, avemu pigliatu un'eccezzioni è...
  • ... usendu CREATE TABLE ... (LIKE ... INCLUDING ...) hè statu creatu basatu annantu à u mudellu di a tavola parent sezione cù una restrizzione à a data desideratacusì chì quandu i dati sò recuperati, a lettura hè fatta solu in questu.

PG10: primu tentativu

Ma a particione per l'eredità hè storicamente micca stata bè adattata per trattà cun un flussu di scrittura attivu o un gran numaru di partizioni di i zitelli. Per esempiu, pudete ricurdà chì l'algoritmu per selezziunà a rùbbrica necessaria avia cumplessità quadratica, chì funziona cù più di 100 sezioni, voi stessu capisce cumu ...

In PG10 sta situazione hè stata assai ottimizzata cù l'implementazione di supportu partizionamentu nativu. Dunque, avemu immediatamente pruvatu à applicà immediatamente dopu a migrazione di l'almacenamiento, ma...

Cum'è hè risultatu dopu à scavà à traversu u manuale, a tavola partizionata nativamente in questa versione hè:

  • ùn sustene micca e descrizzioni d'indici
  • ùn sustene micca i triggers nantu à questu
  • ùn pò esse u "discendente" di nimu
  • ùn sustene micca INSERT ... ON CONFLICT
  • ùn pò micca generà automaticamente una sezione

Dopu avè ricivutu un colpu dulurosu à a fronte cun un rake, avemu capitu chì ùn saria impussibile di fà senza mudificà l'applicazione, è posponendu più ricerca per sei mesi.

PG10: seconda chance

Allora, avemu cuminciatu à risolve i prublemi chì sò ghjunti unu à unu:

  1. Perchè triggers è ON CONFLICT Avemu trovu chì avemu sempre bisognu di elli quì è quì, cusì avemu fattu una tappa intermedia per travaglià table proxy.
  2. Hè liberatu di "routing" in triggers - vale à dì da EXECUTE.
  3. L'anu cacciatu separatamente tavula mudellu cù tutti l'indicicusì ch'elli ùn sò ancu prisenti in a tavola proxy.

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB
Infine, dopu tuttu questu, avemu spartutu a tavola principale nativu. A creazione di una nova sezione hè sempre lasciata à a cuscenza di l'applicazione.

Dizionari "sega".

Cum'è in ogni sistema analiticu, avemu avutu ancu "fatti" è "tagli" (dizziunari). In u nostru casu, in questa capacità anu agitu, per esempiu, corpu di mudellu dumande lenti simili o u testu di a dumanda stessu.

"Fatti" sò stati seccionati da ghjornu per un bellu pezzu digià, cusì avemu sguassatu tranquillamente e sezioni obsolete, è ùn ci anu micca disturbatu (logs!). Ma ci era un prublema cù i dizziunari...

Per ùn dì chì ci eranu assai, ma apprussimatamente 100 TB di "fatti" anu risultatu in un dizziunariu 2.5TB. Ùn pudete micca sguassate nunda da una tale tavola, ùn pudete micca cumpressà in u tempu adattatu, è scrive à pocu à pocu hè diventatu più lento.

Cum'è un dizziunariu... in ellu, ogni entrata deve esse presentata una sola volta... è questu hè currettu, ma !.. Nimu ùn ci impedisce di avè. un dizziunariu separatu per ogni ghjornu! Iè, questu porta una certa redundanza, ma permette:

  • scrive / leghje più veloce per via di a dimensione di a sezione più chjuca
  • cunsuma menu memoria travagliendu cù indici più compacti
  • almacenà menu dati per via di a capacità di sguassà rapidamente obsoleti

In u risultatu di tuttu u cumplessu di misure A carica di CPU hè diminuita di ~ 30%, a carica di u discu di ~ 50%:

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB
À u listessu tempu, avemu cuntinuatu à scrive esattamente a stessa cosa in a basa di dati, solu cù menu carica.

#2. Evoluzione di basa di dati è refactoring

Allora avemu stabilitu nantu à ciò chì avemu ogni ghjornu hà a so sezione cù dati. In fatti, CHECK (dt = '2018-10-12'::date) - è ci hè una chjave di partizione è a cundizione per un registru per fallu in una sezione specifica.

Siccomu tutti i rapporti in u nostru serviziu sò custruiti in u cuntestu di una data specifica, l'indici per elli da "tempi non partizionati" sò stati tutti i tipi. (Server, Data, mudellu di pianu), (Server, Data, Pianu node), (Data, Classe d'errore, Server)...

Ma avà campanu in ogni rùbbrica e vostre copie ogni tali indice... È in ogni rùbbrica data hè una constante... Ci hè chì avà simu in ogni tali indici simpricimenti entre in una constante cum'è unu di i campi, chì aumenta u so voluminu è u tempu di ricerca per ellu, ma ùn porta nisun risultatu. Anu lasciatu u rake per sè stessu, oops...

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB
A direzzione di ottimisazione hè evidenti - simplice sguassate u campu di data da tutti l'indici nantu à i tavulini divisi. Dati i nostri volumi, u guadagnu hè di circa 1 TB / settimana!

Avà nutemu chì stu terabyte avia ancu esse arregistratu in qualchì modu. Hè, ancu noi u discu deve avà carica menu! Questa stampa mostra chjaramente l'effettu ottenutu da a pulizia, à quale avemu dedicatu una settimana:

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB

#3. "Sparendu" a carica di punta

Unu di i grandi prublemi di sistemi caricati hè sincronizzazione redundante alcune operazioni chì ùn ne necessitanu micca. Certe volte "perchè ùn anu micca nutatu", à volte "era più faciule cusì", ma prima o dopu avete da sbarazzà.

Facemu zoom in a stampa precedente è vede chì avemu un discu "pumps" sottu a carica cù doppia amplitude trà campioni adiacenti, chì chjaramente "statisticamente" ùn deve micca accade cù un tali numeru di operazioni:

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB

Questu hè abbastanza faciule da ottene. Avemu digià principiatu u monitoraghju quasi 1000 servitori, ognunu hè trattatu da un filu lògicu separatu, è ogni filu resetta l'infurmazioni accumulate per esse mandatu à a basa di dati à una certa freccia, qualcosa cum'è questu:

setInterval(sendToDB, interval)

U prublema quì hè precisamente in u fattu chì tutti i fili cumincianu à circa à u listessu tempu, cusì i so tempi di mandatu quasi sempre coincidenu "à u puntu". Oops #2...

Fortunatamente, questu hè abbastanza faciule da riparà, aghjunghjendu un run-up "aleatoriu". per tempu:

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

#4. Cachemu ciò chì avemu bisognu

U terzu prublema tradiziunale highload hè senza cache induve ellu hè pudia esse.

Per esempiu, avemu permessu di analizà in termini di nodi di pianu (tutti questi Seq Scan on users), ma immediatamente pensate chì sò, per a maiò parte, i stessi - si scurdanu.

No, sicuru, nunda hè scrittu à a basa di dati di novu, questu taglia u trigger cù INSERT ... ON CONFLICT DO NOTHING. Ma sta dati righjunghji ancu a basa di dati, è ùn hè micca necessariu lettura per verificà u cunflittu avè da fà. Oops #3...

A diferenza in u numeru di registri mandati à a basa di dati prima / dopu chì a caching hè attivata hè ovvia:

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB

È questu hè a caduta accumpagnata di a carica di almacenamento:

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB

Tuttu

"Terabyte-per-day" sona solu paura. Se fate tuttu bè, allora questu hè ghjustu 2^40 bytes / 86400 seconde = ~12.5 MB/schì ancu i viti IDE di u desktop tenutu. 🙂

Ma in seriu, ancu cù un "skew" di deci volte di a carica durante u ghjornu, pudete facilmente scuntrà e capacità di i SSD muderni.

Scrivemu in PostgreSQL in sublight: 1 host, 1 day, 1TB

Source: www.habr.com

Add a comment