Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Il rapporto presenta alcuni approcci che consentono monitorare le prestazioni delle query SQL quando ce ne sono milioni al giornoe ci sono centinaia di server PostgreSQL monitorati.

Quali soluzioni tecniche ci consentono di elaborare in modo efficiente un tale volume di informazioni e in che modo ciò semplifica la vita di un normale sviluppatore?


Chi è interessato? analisi di problemi specifici e varie tecniche di ottimizzazione Puoi anche eseguire query SQL e risolvere i tipici problemi DBA in PostgreSQL leggere una serie di articoli su questo argomento.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)
Mi chiamo Kirill Borovikov e lo rappresento Azienda tensore. Nello specifico, sono specializzato nel lavoro con i database nella nostra azienda.

Oggi ti racconto come ottimizziamo le query, quando non è necessario “smontare” le prestazioni di una singola query, ma risolvere il problema in massa. Quando ci sono milioni di richieste e devi trovarne alcune approcci alla soluzione questo grosso problema.

In generale, Tensor lo è per un milione di nostri clienti VLSI è la nostra applicazione: social network aziendale, soluzioni per la videocomunicazione, per il flusso di documenti interni ed esterni, sistemi contabili per contabilità e magazzini,... Cioè, una tale "mega-combinazione" per la gestione aziendale integrata, in cui esistono più di 100 diversi progetti interni.

Per garantire che tutti funzionino e si sviluppino normalmente, disponiamo di 10 centri di sviluppo in tutto il Paese e di più 1000 sviluppatori.

Lavoriamo con PostgreSQL dal 2008 e abbiamo accumulato una grande quantità di ciò che elaboriamo - dati dei clienti, dati statistici, analitici, dati da sistemi informativi esterni - più di 400TB. Ci sono circa 250 server solo in produzione e in totale sono circa 1000 i server di database che monitoriamo.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

SQL è un linguaggio dichiarativo. Non descrivi "come" qualcosa dovrebbe funzionare, ma "cosa" vuoi ottenere. Il DBMS sa meglio come creare un JOIN: come collegare le tabelle, quali condizioni imporre, cosa passerà attraverso l'indice, cosa no...

Alcuni DBMS accettano suggerimenti: "No, collega queste due tabelle in questa o quella coda", ma PostgreSQL non può farlo. Questa è la posizione consapevole dei principali sviluppatori: “Preferiamo completare il Query Optimizer piuttosto che consentire agli sviluppatori di utilizzare qualche tipo di suggerimento”.

Ma, nonostante PostgreSQL non consenta all’“esterno” di controllarsi, lo consente perfettamente vedere cosa sta succedendo dentro di luiquando esegui la query e dove si verificano problemi.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

In generale, quali sono i classici problemi che solitamente presenta uno sviluppatore [a un DBA]? “Qui abbiamo soddisfatto la richiesta, e tutto è lento con noi, tutto è sospeso, sta succedendo qualcosa... Qualche tipo di problema!”

I motivi sono quasi sempre gli stessi:

  • algoritmo di query inefficiente
    Sviluppatore: "Ora gli do 10 tabelle in SQL tramite JOIN..." - e si aspetta che le sue condizioni vengano miracolosamente effettivamente "sciolte" e otterrà tutto rapidamente. Ma i miracoli non accadono e qualsiasi sistema con tale variabilità (10 tabelle in un A PARTIRE) dà sempre qualche tipo di errore. [articolo]
  • statistiche obsolete
    Questo punto è molto rilevante in particolare per PostgreSQL, quando hai "versato" un set di dati di grandi dimensioni sul server, fai una richiesta e "sexcanits" il tuo tablet. Perché ieri c'erano 10 record e oggi ce ne sono 10 milioni, ma PostgreSQL non ne è ancora a conoscenza e dobbiamo dirlo. [articolo]
  • "plug" sulle risorse
    Hai installato un database di grandi dimensioni e molto caricato su un server debole che non dispone di disco, memoria o prestazioni del processore sufficienti. E questo è tutto... Da qualche parte esiste un limite prestazionale al di sopra del quale non puoi più saltare.
  • blocco
    Questo è un punto difficile, ma sono più rilevanti per varie query di modifica (INSERT, UPDATE, DELETE): questo è un argomento importante separato.

Ottenere un piano

...E per tutto il resto noi bisogno di un piano! Dobbiamo vedere cosa sta succedendo all'interno del server.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Un piano di esecuzione delle query per PostgreSQL è un albero dell'algoritmo di esecuzione delle query nella rappresentazione testuale. È proprio l'algoritmo che, a seguito dell'analisi del pianificatore, è risultato essere il più efficace.

Ogni nodo dell'albero è un'operazione: recupero di dati da una tabella o da un indice, creazione di una bitmap, unione di due tabelle, unione, intersezione o esclusione di selezioni. L'esecuzione di una query implica camminare attraverso i nodi di questo albero.

Per ottenere il piano di query, il modo più semplice è eseguire l'istruzione EXPLAIN. Per ottenere tutti gli attributi reali, ovvero eseguire effettivamente una query sulla base - EXPLAIN (ANALYZE, BUFFERS) SELECT ....

La parte negativa: quando lo esegui, accade "qui e ora", quindi è adatto solo per il debug locale. Se prendi un server molto carico sottoposto a un forte flusso di modifiche dei dati e vedi: “Oh! Qui abbiamo un'esecuzione lentaXia richiesta." Mezz'ora, un'ora fa, mentre eri in esecuzione e ricevevi questa richiesta dai registri, riportandola al server, l'intero set di dati e le statistiche sono cambiati. Lo esegui per eseguire il debug e funziona rapidamente! E non puoi capire perché, perché era lentamente.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Per capire cosa è successo esattamente nel momento in cui la richiesta è stata eseguita sul server, hanno scritto persone intelligenti modulo auto_explain. È presente in quasi tutte le distribuzioni PostgreSQL più comuni e può essere semplicemente attivato nel file di configurazione.

Se si rende conto che alcune richieste funzionano più a lungo del limite indicato, lo fa “istantanea” del piano di questa richiesta e le scrive insieme nel log.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Adesso sembra che vada tutto bene, andiamo al registro e vediamo lì... [testo footcloth]. Ma non possiamo dire nulla al riguardo, a parte il fatto che è un piano eccellente perché ci sono voluti 11 ms per essere eseguito.

Sembra che tutto vada bene, ma nulla è chiaro su cosa sia realmente accaduto. A parte l’ora generale, non vediamo davvero nulla. Perché guardare un simile "agnello" di testo semplice generalmente non è visivo.

Ma anche se non è ovvio, anche se è scomodo, ci sono problemi più fondamentali:

  • Il nodo indica somma delle risorse dell'intero sottoalbero sotto di lui. Cioè, non puoi semplicemente scoprire quanto tempo è stato dedicato a questa particolare scansione dell'indice se sotto di essa è presente qualche condizione nidificata. Dobbiamo guardare dinamicamente per vedere se ci sono "figli" e variabili condizionali, CTE all'interno - e sottrarre tutto questo "nella nostra mente".
  • Secondo punto: l'ora che è indicata sul nodo è tempo di esecuzione del singolo nodo. Se questo nodo è stato eseguito più volte, ad esempio, come risultato di un loop attraverso i record della tabella, il numero di loop (cicli di questo nodo) aumenta nel piano. Ma il tempo di esecuzione atomica rimane lo stesso in termini di piano. Cioè, per capire per quanto tempo questo nodo è stato eseguito in totale, devi moltiplicare una cosa per un'altra, ancora una volta, "nella tua testa".

In tali situazioni, capire "Chi è l'anello più debole?" quasi impossibile. Pertanto, anche gli stessi sviluppatori lo scrivono nel “manuale”. “Comprendere un progetto è un’arte che va appresa, sperimentata…”.

Ma abbiamo 1000 sviluppatori e non puoi trasmettere questa esperienza a ciascuno di loro. Io, tu, lui lo sa, ma qualcuno laggiù non lo sa più. Forse imparerà, o forse no, ma ha bisogno di lavorare adesso - e dove otterrebbe questa esperienza?

Pianificare la visualizzazione

Pertanto, ci siamo resi conto che per affrontare questi problemi abbiamo bisogno buona visualizzazione del piano. [articolo]

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Per prima cosa siamo andati "attraverso il mercato": guardiamo su Internet per vedere cosa esiste.

Ma si è scoperto che ci sono pochissime soluzioni relativamente "vive" che sono più o meno in via di sviluppo - letteralmente, solo una: spiegazione.depesz.com di Hubert Lubaczewski. Quando inserisci nel campo "feed" una rappresentazione testuale del piano, ti mostra una tabella con i dati analizzati:

  • tempo di elaborazione del nodo
  • tempo totale per l'intero sottoalbero
  • numero di record recuperati statisticamente attesi
  • il corpo del nodo stesso

Questo servizio ha anche la possibilità di condividere un archivio di collegamenti. Hai lanciato lì il tuo piano e hai detto: "Ehi, Vasya, ecco un collegamento, c'è qualcosa che non va".

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Ma ci sono anche piccoli problemi.

Innanzitutto un'enorme quantità di “copia-incolla”. Prendi un pezzo di tronco, lo infili lì dentro, e ancora, e ancora.

Secondo, l' nessuna analisi della quantità di dati letti - gli stessi buffer in uscita EXPLAIN (ANALYZE, BUFFERS), non lo vediamo qui. Semplicemente non sa come smontarli, capirli e lavorare con loro. Quando stai leggendo molti dati e ti rendi conto che potresti allocare in modo errato il disco e la cache di memoria, queste informazioni sono molto importanti.

Il terzo punto negativo è lo sviluppo molto debole di questo progetto. I commit sono molto piccoli, va bene se una volta ogni sei mesi, e il codice è in Perl.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Ma questi sono tutti “testi”, potremmo in qualche modo conviverci, ma c'è una cosa che ci ha allontanato molto da questo servizio. Si tratta di errori nell'analisi di Common Table Expression (CTE) e di vari nodi dinamici come InitPlan/SubPlan.

Se credi a questa immagine, il tempo di esecuzione totale di ogni singolo nodo è maggiore del tempo di esecuzione totale dell'intera richiesta. È semplice - il tempo di generazione di questo CTE non è stato sottratto dal nodo Scansione CTE. Pertanto, non conosciamo più la risposta corretta su quanto tempo ha impiegato la scansione CTE stessa.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Poi ci siamo resi conto che era giunto il momento di scrivere il nostro: evviva! Ogni sviluppatore dice: “Ora scriveremo il nostro, sarà semplicissimo!”

Abbiamo preso uno stack tipico dei servizi web: un core basato su Node.js + Express, utilizzato Bootstrap e D3.js per bellissimi diagrammi. E le nostre aspettative sono state pienamente giustificate: abbiamo ricevuto il primo prototipo in 2 settimane:

  • parser del piano personalizzato
    Cioè, ora possiamo analizzare qualsiasi piano tra quelli generati da PostgreSQL.
  • corretta analisi dei nodi dinamici - Scansione CTE, InitPlan, Sottopiano
  • analisi della distribuzione dei buffer - dove le pagine di dati vengono lette dalla memoria, dove dalla cache locale, dove dal disco
  • ho avuto chiarezza
    Per non “scavare” tutto questo nel registro, ma per vedere subito “l’anello debole” nella foto.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Abbiamo ottenuto qualcosa del genere, con l'evidenziazione della sintassi inclusa. Ma di solito i nostri sviluppatori non lavorano più con una rappresentazione completa del piano, ma con una più breve. Dopotutto, abbiamo già analizzato tutti i numeri e li abbiamo lanciati a destra ea sinistra, e nel mezzo abbiamo lasciato solo la prima riga, che tipo di nodo è: CTE Scan, CTE generation o Seq Scan secondo qualche segno.

Questa è la rappresentazione abbreviata che chiamiamo modello di piano.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Cos'altro sarebbe conveniente? Sarebbe conveniente vedere quale parte del nostro tempo totale è assegnata a quale nodo e semplicemente "attaccarlo" di lato Grafico a torta.

Indichiamo il nodo e vediamo: si scopre che Seq Scan ha impiegato meno di un quarto del tempo totale e i restanti 3/4 sono stati presi da CTE Scan. Orrore! Questa è una piccola nota sulla "velocità di fuoco" di CTE Scan se li utilizzi attivamente nelle tue query. Non sono molto veloci: sono inferiori anche alla normale scansione dei tavoli. [articolo] [articolo]

Ma di solito tali diagrammi sono più interessanti, più complessi, quando indichiamo immediatamente un segmento e vediamo, ad esempio, che più della metà delle volte alcuni Seq Scan "mangiano". Inoltre, all'interno c'era una sorta di filtro, in base ad esso venivano scartati molti record... Puoi lanciare direttamente questa immagine allo sviluppatore e dire: “Vasya, qui va tutto male per te! Scoprilo, guarda: qualcosa non va!”

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Naturalmente c'erano dei "rastrelli" coinvolti.

La prima cosa che abbiamo riscontrato è stato il problema dell'arrotondamento. Il tempo di ogni singolo nodo del piano è indicato con una precisione di 1 μs. E quando il numero di cicli del nodo supera, ad esempio, 1000 - dopo l'esecuzione PostgreSQL ha diviso "con precisione", quindi quando ricalcoliamo otteniamo il tempo totale "tra 0.95 ms e 1.05 ms". Quando il conteggio arriva ai microsecondi va bene, ma quando sono già [milli]secondi bisogna tenere conto di questa informazione quando si “slegano” le risorse ai nodi del piano “chi ha consumato quanto”.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Il secondo punto, più complesso, è la distribuzione delle risorse (quei buffer) tra i nodi dinamici. Questo ci è costato le prime 2 settimane del prototipo più altre 4 settimane.

È abbastanza facile riscontrare questo tipo di problema: eseguiamo un CTE e presumibilmente leggiamo qualcosa al suo interno. In effetti, PostgreSQL è “intelligente” e non leggerà nulla direttamente lì. Quindi prendiamo da esso il primo record e ad esso il centouno dallo stesso CTE.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Osserviamo il piano e capiamo: è strano, abbiamo 3 buffer (pagine di dati) "consumati" in Seq Scan, 1 in più nella scansione CTE e altri 2 nella seconda scansione CTE. Cioè se ci limitiamo a sommare il tutto otterremo 6, ma dal tablet ne leggiamo solo 3! CTE Scan non legge nulla da nessuna parte, ma lavora direttamente con la memoria del processo. Cioè, qui c'è chiaramente qualcosa che non va!

In effetti, si scopre che qui ci sono tutte quelle 3 pagine di dati che sono state richieste a Seq Scan, prima 1 ha chiesto la 1a scansione CTE, poi la 2a e gli sono state lette altre 2. Cioè, un totale di Sono state lette 3 pagine, non 6.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

E questa immagine ci ha portato alla comprensione che l'esecuzione di un piano non è più un albero, ma semplicemente una sorta di grafico aciclico. E abbiamo ottenuto un diagramma come questo, in modo da capire "cosa è venuto da dove in primo luogo". Cioè, qui abbiamo creato un CTE da pg_class e lo abbiamo richiesto due volte, e quasi tutto il nostro tempo è stato trascorso sul ramo quando lo abbiamo richiesto la seconda volta. È chiaro che leggere la 2a voce è molto più costoso che leggere semplicemente la 101a voce dal tablet.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Abbiamo espirato per un po'. Dissero: “Ora, Neo, conosci il kung fu! Ora la nostra esperienza è direttamente sul tuo schermo. Ora puoi usarlo." [articolo]

Consolidamento dei registri

I nostri 1000 sviluppatori hanno tirato un sospiro di sollievo. Ma abbiamo capito che abbiamo solo centinaia di server “da combattimento”, e tutto questo “copia-incolla” da parte degli sviluppatori non è affatto conveniente. Ci siamo resi conto che dovevamo ritirarlo noi stessi.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

In generale, esiste un modulo standard in grado di raccogliere statistiche, tuttavia deve essere attivato anche nella configurazione: questo modulo pg_stat_statements. Ma non era adatto a noi.

Innanzitutto, assegna le stesse query utilizzando schemi diversi all'interno dello stesso database ID query diversi. Cioè, se lo fai prima SET search_path = '01'; SELECT * FROM user LIMIT 1;e poi SET search_path = '02'; e la stessa richiesta, le statistiche di questo modulo avranno record diversi e non sarò in grado di raccogliere statistiche generali specificamente nel contesto di questo profilo di richiesta, senza tenere conto degli schemi.

Il secondo punto che ci ha impedito di utilizzarlo è mancanza di piani. Cioè non esiste un piano, esiste solo la richiesta stessa. Vediamo cosa stava rallentando, ma non capiamo il perché. E qui torniamo al problema di un set di dati in rapida evoluzione.

E l'ultimo momento - mancanza di "fatti". Cioè, non puoi affrontare un'istanza specifica di esecuzione della query: non ce n'è, ci sono solo statistiche aggregate. Sebbene sia possibile lavorare con questo, è semplicemente molto difficile.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Pertanto, abbiamo deciso di combattere il copia-incolla e abbiamo iniziato a scrivere коллектор.

Il raccoglitore si connette tramite SSH, stabilisce una connessione sicura al server con il database utilizzando un certificato e tail -F "si aggrappa" ad esso nel file di registro. Quindi in questa sessione otteniamo un "mirror" completo dell'intero file di registro, che il server genera. Il carico sul server stesso è minimo, perché lì non analizziamo nulla, ci limitiamo a rispecchiare il traffico.

Dato che avevamo già iniziato a scrivere l'interfaccia in Node.js, abbiamo continuato a scrivervi il collector. E questa tecnologia si è giustificata, perché è molto comodo utilizzare JavaScript per lavorare con dati di testo con formattazione debole, che è il registro. E la stessa infrastruttura Node.js come piattaforma backend ti consente di lavorare facilmente e comodamente con le connessioni di rete e in effetti con qualsiasi flusso di dati.

Di conseguenza, “allunghiamo” due connessioni: la prima per “ascoltare” il registro stesso e portarlo a noi stessi, e la seconda per chiedere periodicamente alla base. "Ma il registro mostra che il segno con oid 123 è bloccato", ma questo non significa nulla per lo sviluppatore, e sarebbe carino chiedere al database: "Che cos'è OID = 123 comunque?" E così periodicamente chiediamo alla base cosa ancora non sappiamo di noi stessi.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

"C'è solo una cosa di cui non hai tenuto conto, c'è una specie di api simili a elefanti!..." Abbiamo iniziato a sviluppare questo sistema quando volevamo monitorare 10 server. Quello più critico a nostro avviso, dove sono emersi alcuni problemi difficili da affrontare. Ma durante il primo trimestre ne abbiamo ricevuti cento per il monitoraggio, perché il sistema funzionava, tutti lo volevano, tutti erano a proprio agio.

Tutto questo deve essere sommato, il flusso di dati è ampio e attivo. In effetti, ciò che monitoriamo, ciò di cui possiamo occuparci, è ciò che utilizziamo. Utilizziamo anche PostgreSQL come archivio dati. E niente è più veloce dell'operatore nel “versare” i dati al suo interno COPY Non ancora.

Ma il semplice “trasferimento” di dati non è realmente la nostra tecnologia. Perché se hai circa 50 richieste al secondo su un centinaio di server, questo genererà 100-150 GB di log al giorno. Pertanto, abbiamo dovuto “tagliare” attentamente la base.

In primo luogo, l'abbiamo fatto partizionamento per giorno, perché, in generale, nessuno è interessato alla correlazione tra i giorni. Che differenza fa quello che avevi ieri, se stasera lanciassi una nuova versione dell'applicazione - e già alcune nuove statistiche.

In secondo luogo, abbiamo imparato (siamo stati costretti) molto, molto veloce da scrivere utilizzando COPY. Cioè, non solo COPYperché è più veloce di INSERT, e ancora più veloce.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Il terzo punto: dovevo farlo rispettivamente i trigger di abbandono e le chiavi esterne. Cioè, non abbiamo alcuna integrità referenziale. Perché se hai una tabella che ha una coppia di FK e dici nella struttura del database che "ecco un record di log a cui FK fa riferimento, ad esempio, a un gruppo di record", allora quando lo inserisci, PostgreSQL non gli resta altro che come prenderlo e farlo onestamente SELECT 1 FROM master_fk1_table WHERE ... con l'identificatore che stai tentando di inserire - solo per verificare che questo record sia presente lì, che tu non “interrompa” questa Foreign Key con il tuo inserimento.

Invece di un record nella tabella di destinazione e nei relativi indici, otteniamo il vantaggio aggiuntivo di leggere da tutte le tabelle a cui fa riferimento. Ma non ne abbiamo affatto bisogno: il nostro compito è registrare il più possibile e il più rapidamente possibile con il minor carico. Quindi FK - giù!

Il punto successivo è l'aggregazione e l'hashing. Inizialmente, li abbiamo implementati nel database - dopo tutto, è conveniente farlo immediatamente, quando arriva un record, farlo in una sorta di tablet "più uno" proprio nel grilletto. Bene, è conveniente, ma la stessa cosa brutta: inserisci un record, ma sei costretto a leggere e scrivere qualcos'altro da un'altra tabella. Inoltre, non solo leggi e scrivi, ma lo fai anche ogni volta.

Ora immagina di avere una tabella in cui conti semplicemente il numero di richieste che sono passate attraverso un host specifico: +1, +1, +1, ..., +1. E tu, in linea di principio, non ne hai bisogno: tutto è possibile somma in memoria sul raccoglitore e inviarlo al database in una volta sola +10.

Sì, in caso di problemi, la tua integrità logica potrebbe "andare in pezzi", ma questo è un caso quasi irrealistico - perché hai un server normale, ha una batteria nel controller, hai un registro delle transazioni, un registro sul file system... In generale, no, ne vale la pena. La perdita di produttività che si ottiene dall'esecuzione di trigger/FK non vale la spesa sostenuta.

È lo stesso con l'hashing. Una determinata richiesta ti arriva, calcoli un determinato identificatore da esso nel database, lo scrivi nel database e poi lo dici a tutti. Va tutto bene finché, al momento della registrazione, una seconda persona viene da te che vuole registrare la stessa cosa - e vieni bloccato, e questo è già un male. Pertanto, se è possibile trasferire la generazione di alcuni ID al client (relativi al database), è meglio farlo.

Per noi è stato semplicemente perfetto utilizzare MD5 dal testo: richiesta, piano, modello,... Lo calcoliamo dal lato del collezionista e "versiamo" l'ID già pronto nel database. La lunghezza di MD5 e il partizionamento giornaliero ci permettono di non preoccuparci di possibili collisioni.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Ma per poter registrare tutto questo velocemente, abbiamo dovuto modificare la procedura di registrazione stessa.

Come scrivi solitamente i dati? Abbiamo una sorta di set di dati, lo dividiamo in più tabelle e poi lo COPIIAMO: prima nella prima, poi nella seconda, nella terza... È scomodo, perché sembra che stiamo scrivendo un flusso di dati in tre passaggi in sequenza. Sgradevole. Si può fare più velocemente? Potere!

Per fare ciò è sufficiente scomporre questi flussi in parallelo tra loro. Si scopre che abbiamo errori, richieste, modelli, blocchi, ... che volano in thread separati e scriviamo tutto in parallelo. Basta per questo mantenere un canale COPY costantemente aperto per ogni singola tabella di destinazione.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Cioè, al collezionista c'è sempre un ruscello, in cui posso scrivere i dati di cui ho bisogno. Ma affinché il database veda questi dati e qualcuno non rimanga bloccato in attesa che questi dati vengano scritti, La COPIA deve essere interrotta a determinati intervalli. Per noi, il periodo più efficace è stato di circa 100 ms: lo chiudiamo e lo riapriamo immediatamente sullo stesso tavolo. E se non ne abbiamo abbastanza di un flusso durante alcuni picchi, allora raccogliamo fino a un certo limite.

Inoltre, abbiamo scoperto che per un simile profilo di carico, qualsiasi aggregazione, quando i record vengono raccolti in batch, è dannosa. Il male classico lo è INSERT ... VALUES e altri 1000 record. Perché a quel punto hai un picco di scrittura sul supporto e tutti gli altri che tentano di scrivere qualcosa sul disco saranno in attesa.

Per eliminare tali anomalie, semplicemente non aggregare nulla, non bufferizzare affatto. E se si verifica il buffering su disco (fortunatamente, l'API Stream in Node.js ti consente di scoprirlo), rimanda questa connessione. Quando ricevi un evento che è di nuovo gratuito, scrivilo dalla coda accumulata. E mentre è occupato, prendi il prossimo libero dalla piscina e scrivigli.

Prima di introdurre questo approccio alla registrazione dei dati, avevamo circa 4K di operazioni di scrittura e in questo modo abbiamo ridotto il carico di 4 volte. Ora sono cresciuti di altre 6 volte grazie ai nuovi database monitorati - fino a 100 MB/s. E ora archiviamo i registri degli ultimi 3 mesi in un volume di circa 10-15 TB, sperando che in soli tre mesi qualsiasi sviluppatore sarà in grado di risolvere qualsiasi problema.

Comprendiamo i problemi

Ma raccogliere semplicemente tutti questi dati è positivo, utile, rilevante, ma non sufficiente: deve essere compreso. Perché questi sono milioni di piani diversi al giorno.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Ma milioni sono ingestibili, bisogna prima fare “più piccoli”. E, prima di tutto, devi decidere come organizzare questa cosa “più piccola”.

Abbiamo individuato tre punti chiave:

  • che inviato questa richiesta
    Cioè da quale applicazione è “arrivato”: interfaccia web, backend, sistema di pagamento o altro.
  • dove è successo
    Su quale server specifico? Perché se hai diversi server sotto un'unica applicazione e all'improvviso uno "impazzisce" (perché "il disco è marcio", "memoria trapelata", qualche altro problema), allora devi affrontare in modo specifico il server.
  • come il problema si è manifestato in un modo o nell'altro

Per capire "chi" ci ha inviato una richiesta, utilizziamo uno strumento standard, ovvero l'impostazione di una variabile di sessione: SET application_name = '{bl-host}:{bl-method}'; — inviamo il nome dell'host della logica aziendale da cui proviene la richiesta e il nome del metodo o dell'applicazione che l'ha avviata.

Dopo aver passato il "proprietario" della richiesta, questa deve essere inviata al log - per questo configuriamo la variabile log_line_prefix = ' %m [%p:%v] [%d] %r %a'. Per chi è interessato, forse guarda nel manualeChe cosa significa tutto questo. Si scopre che vediamo nel registro:

  • tempo
  • identificatori di processi e transazioni
  • nome del database
  • IP della persona che ha inviato questa richiesta
  • e nome del metodo

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Poi ci siamo resi conto che non è molto interessante osservare la correlazione di una richiesta tra server diversi. Non capita spesso di avere una situazione in cui un'applicazione fallisce allo stesso modo qua e là. Ma anche se è lo stesso, guarda uno qualsiasi di questi server.

Quindi ecco il taglio "un server - un giorno" si è rivelato sufficiente per noi per qualsiasi analisi.

La prima sezione analitica è la stessa "campione" - una forma abbreviata di presentazione del piano, sgombrata da ogni indicatore numerico. Il secondo taglio è l'applicazione o il metodo, mentre il terzo taglio è il nodo specifico del piano che ci ha causato problemi.

Quando siamo passati da istanze specifiche ai modelli, abbiamo ottenuto due vantaggi contemporaneamente:

  • riduzione multipla del numero di oggetti da analizzare
    Dobbiamo analizzare il problema non più attraverso migliaia di domande o piani, ma attraverso decine di modelli.
  • sequenza temporale
    Cioè, riassumendo i “fatti” all'interno di una determinata sezione, è possibile visualizzarne l'aspetto durante la giornata. E qui puoi capire che se hai qualche tipo di schema che si verifica, ad esempio, una volta all'ora, ma dovrebbe accadere una volta al giorno, dovresti pensare a cosa è andato storto - chi lo ha causato e perché, forse dovrebbe essere qui non dovrebbe. Questo è un altro metodo di analisi non numerico, puramente visivo.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

I restanti metodi si basano sugli indicatori che estraiamo dal piano: quante volte si è verificato un simile schema, il tempo totale e medio, quanti dati sono stati letti dal disco e quanti dalla memoria...

Perché, ad esempio, arrivi alla pagina di analisi dell'host, guarda: qualcosa sta iniziando a leggere troppo sul disco. Il disco sul server non può gestirlo: chi legge da esso?

E puoi ordinare in base a qualsiasi colonna e decidere di cosa ti occuperai in questo momento: il carico sul processore o sul disco, o il numero totale di richieste... L'abbiamo ordinato, abbiamo esaminato quelle "migliori", risolto e ha lanciato una nuova versione dell'applicazione.
[videolezione]

E immediatamente puoi vedere diverse applicazioni fornite con lo stesso modello da una richiesta simile SELECT * FROM users WHERE login = 'Vasya'. Frontend, backend, elaborazione... E ti chiedi perché l'elaborazione leggerebbe l'utente se non interagisce con lui.

Il modo opposto è vedere subito dall'applicazione cosa fa. Ad esempio, il frontend è questo, questo, questo e questo una volta all'ora (la sequenza temporale aiuta). E subito sorge la domanda: sembra che non sia compito del frontend fare qualcosa una volta ogni ora...

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Dopo qualche tempo, ci siamo resi conto che ci mancava l'aggregazione statistiche per nodi del piano. Abbiamo isolato dai piani solo quei nodi che fanno qualcosa con i dati delle tabelle stesse (leggerli/scriverli per indice o meno). Rispetto all'immagine precedente infatti viene aggiunto solo un aspetto: quanti record ci ha portato questo nodo?e quante sono state scartate (Righe rimosse dal filtro).

Non hai un indice adatto sulla piastra, gli fai una richiesta, vola oltre l'indice, cade in Seq Scan... hai filtrato tutti i record tranne uno. Perché hai bisogno di 100 milioni di record filtrati al giorno? Non è meglio raggruppare l'indice?

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Dopo aver analizzato tutti i piani nodo per nodo, ci siamo resi conto che ci sono alcune strutture tipiche nei piani che molto probabilmente sembrano sospette. E sarebbe bello dire allo sviluppatore: "Amico, qui leggi prima per indice, poi ordini e poi tagli" - di regola, c'è un record.

Tutti coloro che hanno scritto query hanno probabilmente riscontrato questo schema: "Dammi l'ultimo ordine per Vasya, la sua data." E se non hai un indice per data, o non c'è alcuna data nell'indice che hai utilizzato, allora lo farai calpestare esattamente lo stesso “rastrello”.

Ma sappiamo che questo è un "rastrello", quindi perché non dire immediatamente allo sviluppatore cosa dovrebbe fare. Di conseguenza, aprendo ora un piano, il nostro sviluppatore vede immediatamente una bellissima immagine con suggerimenti, in cui gli dicono immediatamente: "Hai problemi qua e là, ma vengono risolti in un modo e nell'altro".

Di conseguenza, la quantità di esperienza necessaria per risolvere i problemi all’inizio e ora è diminuita in modo significativo. Questo è il tipo di strumento che abbiamo.

Ottimizzazione in blocco delle query PostgreSQL. Kirill Borovikov (Tensore)

Fonte: habr.com

Aggiungi un commento