
Il database di serie temporali (TSDB, time series database) in Prometheus 2 è un ottimo esempio di soluzione ingegneristica, che offre miglioramenti significativi rispetto allo storage v2 in Prometheus 1, sia in termini di velocità di acquisizione dei dati che di esecuzione delle query, nonché di efficienza nell’utilizzo delle risorse. Abbiamo implementato Prometheus 2 in Percona Monitoring and Management (PMM), e ho avuto l'opportunità di esaminare le prestazioni del TSDB di Prometheus 2. In questo articolo, condividerò i risultati di queste osservazioni.
Carico di lavoro medio di Prometheus
Per coloro che sono abituati a lavorare con database di uso generale, il carico di lavoro tipico di Prometheus è piuttosto interessante. La velocità di acquisizione dei dati tende a stabilizzarsi: solitamente, i servizi che monitori inviano un numero simile di metriche e l'infrastruttura cambia relativamente lentamente.
Le richieste di informazioni possono provenire da diverse fonti. Alcune di esse, come gli avvisi, tendono anch'esse a stabilizzarsi su un valore prevedibile. Altre, come le richieste degli utenti, possono causare picchi, anche se non è caratteristico della maggior parte del carico.
Test di carico
Durante il test mi sono concentrato sulla capacità di accumulare dati. Ho distribuito Prometheus 2.3.2, compilato con Go 1.10.1 (come parte di PMM 1.14) su un servizio Linode, utilizzando questo script: . Per generare un carico il più realistico possibile, tramite questo ho avviato diversi nodi MySQL con carico reale (Sysbench TPC-C Test), ciascuno dei quali emulava 10 nodi Linux/MySQL.
Tutti i test seguenti sono stati condotti su un server Linode con otto core virtuali e 32 GB di memoria, dove sono in esecuzione 20 simulazioni di carico per monitorare duecento istanze MySQL. In termini di Prometheus, 800 target, 440 raccolte al secondo, 380.000 campioni al secondo e 1,7 milioni di serie temporali attive.
Design
L'approccio tradizionale delle basi di dati, incluso quello utilizzato da Prometheus 1.x, consiste in . Se non è sufficiente a sostenere il carico, si potrebbero verificare grandi ritardi, e alcune richieste potrebbero non essere soddisfatte. L'uso della memoria in Prometheus 2 è configurabile tramite la chiave storage.tsdb.min-block-duration, che determina quanto a lungo i registri saranno conservati in memoria prima di essere scritti su disco (di default sono 2 ore). La quantità di memoria necessaria dipenderà dal numero di serie temporali, etichette (labels) e dall'intensità della raccolta dati (scrapes) insieme al flusso in ingresso. In termini di spazio su disco, Prometheus tende a utilizzare 3 byte per registrazione (sample). D'altra parte, i requisiti di memoria sono molto più elevati.
Sebbene ci sia la possibilità di configurare la dimensione del blocco, non è consigliato farlo manualmente, quindi ti trovi nella necessità di fornire a Prometheus la quantità di memoria di cui ha bisogno per il tuo carico.
Se la memoria non è sufficiente a gestire il flusso in ingresso delle metriche, Prometheus andrà in crash con out of memory o verrà intervistato dall'OOM killer.
Aggiungere swap per ritardare il momento del crash quando Prometheus esaurisce la memoria non è molto utile, perché l'uso di questa funzione provoca un'esplosione nel consumo di memoria. Credo che sia legato a Go, il suo garbage collector e a come gestisce lo swap.
Un altro approccio interessante è configurare il salvataggio del head block su disco a un orario prestabilito, invece di calcolarlo in base all'avvio del processo.

Come puoi vedere dal grafico, i salvataggi su disco avvengono ogni due ore. Se cambi il parametro min-block-duration a un'ora, questi salvataggi avverranno ogni ora, iniziando dopo mezz'ora.
Se desideri utilizzare questo e altri grafici nella tua installazione di Prometheus, puoi utilizzare questo . È stato progettato per PMM, ma con alcune piccole modifiche si adatta a qualsiasi installazione di Prometheus.
Abbiamo un blocco attivo, chiamato head block, che viene conservato in memoria; i blocchi con dati più vecchi sono accessibili tramite mmap(). Questo elimina la necessità di configurare la cache separatamente, ma significa anche che devi lasciare spazio sufficiente per la cache del sistema operativo se desideri eseguire query su dati più vecchi di quelli contenuti nel head block.
Questo significa anche che il consumo di memoria virtuale da parte di Prometheus apparirà piuttosto elevato, ma non c'è motivo di preoccuparsi.

Un altro aspetto interessante del design è l'utilizzo del WAL (write ahead log). Come indicato dalla documentazione dello storage, Prometheus utilizza il WAL per evitare perdite in caso di crash. I meccanismi specifici per garantire la resilienza dei dati, sfortunatamente, non sono documentati a sufficienza. La versione 2.3.2 di Prometheus scrive il WAL su disco ogni 10 secondi, e questo parametro non è configurabile dall'utente.
Compattazioni
Il TSDB di Prometheus è progettato come uno storage LSM (Log Structured Merge): il blocco head viene periodicamente scritto su disco, mentre il meccanismo di compattazione unisce più blocchi per evitare la scansione di troppi blocchi durante le query. Qui si può vedere il numero di blocchi che ho osservato nel sistema di test dopo un giorno di carico.

Se vuoi saperne di più sullo storage, puoi esaminare il file meta.json, che contiene informazioni sui blocchi esistenti e su come sono stati creati.
{
"ulid": "01CPZDPD1D9R019JS87TPV5MPE",
"minTime": 1536472800000,
"maxTime": 1536494400000,
"stats": {
"numSamples": 8292128378,
"numSeries": 1673622,
"numChunks": 69528220
},
"compaction": {
"level": 2,
"sources": [
"01CPYRY9MS465Y5ETM3SXFBV7X",
"01CPYZT0WRJ1JB1P0DP80VY5KJ",
"01CPZ6NR4Q3PDP3E57HEH760XS"
],
"parents": [
{
"ulid": "01CPYRY9MS465Y5ETM3SXFBV7X",
"minTime": 1536472800000,
"maxTime": 1536480000000
},
{
"ulid": "01CPYZT0WRJ1JB1P0DP80VY5KJ",
"minTime": 1536480000000,
"maxTime": 1536487200000
},
{
"ulid": "01CPZ6NR4Q3PDP3E57HEH760XS",
"minTime": 1536487200000,
"maxTime": 1536494400000
}
]
},
"version": 1
}Le compattamenti in Prometheus sono legati al momento in cui il blocco head viene scritto su disco. Durante questo momento possono essere eseguite diverse operazioni di questo tipo.

A quanto pare, i compattamenti non sono limitati e possono causare grandi picchi nell'I/O del disco durante l'esecuzione.

Picchi di utilizzo della CPU

Ovviamente, questo influisce negativamente sulla velocità del sistema e rappresenta una seria sfida per i database LSM: come effettuare i compattamenti per supportare alte velocità di richiesta senza generare eccessivi overhead?
L'uso della memoria durante i compattamenti appare piuttosto interessante.

Possiamo vedere come, dopo un compattamento, la maggior parte della memoria cambia stato da Cached a Free: significa che informazioni potenzialmente preziose sono state rimosse. È interessante sapere se si sta utilizzando fadvice() o qualche altra tecnica di minimizzazione, oppure se ciò è causato dal fatto che la cache è stata liberata dai blocchi distrutti durante il compattamento?
Ripristino dopo un guasto
Il ripristino dopo un guasto richiede tempo, e questo è comprensibile. Per un flusso in entrata di un milione di record al secondo, ho dovuto attendere circa 25 minuti mentre avveniva il ripristino utilizzando un disco SSD.
level=info ts=2018-09-13T13:38:14.09650965Z caller=main.go:222 msg="Avvio di Prometheus" version="(versione=2.3.2, branch=v2.3.2, revisione=71af5e29e815795e9dd14742ee7725682fa14b7b)"
level=info ts=2018-09-13T13:38:14.096599879Z caller=main.go:223 build_context="(go=go1.10.1, user=Jenkins, date=20180725-08:58:13OURCE)"
level=info ts=2018-09-13T13:38:14.096624109Z caller=main.go:224 host_details="(Linux 4.15.0-32-generic #35-Ubuntu SMP Fri Aug 10 17:58:07 UTC 2018 x86_64 1bee9e9b78cf (nessuno))"
level=info ts=2018-09-13T13:38:14.096641396Z caller=main.go:225 fd_limits="(soft=1048576, hard=1048576)"
level=info ts=2018-09-13T13:38:14.097715256Z caller=web.go:415 component=web msg="Inizia a ricevere connessioni" address=:9090
level=info ts=2018-09-13T13:38:14.097400393Z caller=main.go:533 msg="Avvio di TSDB ..."
level=info ts=2018-09-13T13:38:14.098718401Z caller=repair.go:39 component=tsdb msg="blocco sano trovato" mint=1536530400000 maxt=1536537600000 ulid=01CQ0FW3ME8Q5W2AN5F9CB7R0R
level=info ts=2018-09-13T13:38:14.100315658Z caller=web.go:467 component=web msg="prefisso router" prefix=/prometheus
level=info ts=2018-09-13T13:38:14.101793727Z caller=repair.go:39 component=tsdb msg="blocco sano trovato" mint=1536732000000 maxt=1536753600000 ulid=01CQ78486TNX5QZTBF049PQHSM
level=info ts=2018-09-13T13:38:14.102267346Z caller=repair.go:39 component=tsdb msg="blocco sano trovato" mint=1536537600000 maxt=1536732000000 ulid=01CQ78DE7HSQK0C0F5AZ46YGF0
level=info ts=2018-09-13T13:38:14.102660295Z caller=repair.go:39 component=tsdb msg="blocco sano trovato" mint=1536775200000 maxt=1536782400000 ulid=01CQ7SAT4RM21Y0PT5GNSS146Q
level=info ts=2018-09-13T13:38:14.103075885Z caller=repair.go:39 component=tsdb msg="blocco sano trovato" mint=1536753600000 maxt=1536775200000 ulid=01CQ7SV8WJ3C2W5S3RTAHC2GHB
level=error ts=2018-09-13T14:05:18.208469169Z caller=wal.go:275 component=tsdb msg="Corruzione di WAL rilevata; troncatura" err="checksum CRC32 imprevisto d0465484, volevo 0" file=/opt/prometheus/data/.prom2-data/wal/007357 pos=15504363
level=info ts=2018-09-13T14:05:19.471459777Z caller=main.go:543 msg="TSDB avviato"
level=info ts=2018-09-13T14:05:19.471604598Z caller=main.go:603 msg="Caricamento del file di configurazione" filename=/etc/prometheus.yml
level=info ts=2018-09-13T14:05:19.499156711Z caller=main.go:629 msg="Caricamento del file di configurazione completato" filename=/etc/prometheus.yml
level=info ts=2018-09-13T14:05:19.499228186Z caller=main.go:502 msg="Il server è pronto per ricevere richieste web."Il principale problema del processo di recupero è l'alto consumo di memoria. Anche se in situazioni normali il server può funzionare stabilmente con la stessa quantità di memoria, in caso di arresto potrebbe non riavviarsi a causa di OOM. L'unica soluzione che ho trovato è disattivare la raccolta dei dati, riavviare il server, permettergli di riprendersi e poi riaccendere la raccolta.
Riscaldamento
Un altro comportamento da considerare durante il riscaldamento è il rapporto tra bassa performance e alto consumo di risorse subito dopo l'avvio. In alcuni, ma non tutti, gli avvii ho osservato un carico significativo su CPU e memoria.


Le cadute nell'uso della memoria indicano che Prometheus non riesce a configurare tutte le raccolte all'avvio, e alcune informazioni vanno perse.
Non ho determinato le cause esatte dell'alto carico su CPU e memoria. Sospetto che sia legato alla creazione di nuove serie temporali nel blocco head a una frequenza elevata.
Variazioni del carico sulla CPU
Oltre alle anomalie che creano un carico abbastanza elevato su I/O, ho notato forti picchi di carico della CPU ogni due minuti. I picchi durano più a lungo con un alto flusso in entrata e sembrano essere causati dal garbage collector di Go, almeno alcuni core sono completamente impegnati.


Questi picchi non sono affatto trascurabili. Sembra che quando si verificano, il punto di accesso interno e le metriche di Prometheus diventino inaccessibili, causando interruzioni nei dati in quegli stessi intervalli di tempo.

Si può anche notare che l'esportatore di Prometheus si blocca per un secondo.

Possiamo notare correlazioni con la pulizia della memoria (GC).

Conclusione
Il TSDB in Prometheus 2 funziona rapidamente, in grado di gestire milioni di serie temporali e contemporaneamente migliaia di scritture al secondo, utilizzando hardware piuttosto modesto. L'utilizzo della CPU e dell'I/O del disco è anch'esso impressionante. Il mio esempio mostrava fino a 200.000 metriche al secondo su un singolo core utilizzato.
Quando si pianifica un'espansione, è importante considerare un volume adeguato di memoria, e questa deve essere memoria reale. La quantità di memoria utilizzata che ho osservato era di circa 5 GB per 100.000 record al secondo nel flusso in ingresso, portando a un totale, con la cache del sistema operativo, a circa 8 GB di memoria occupata.
Naturalmente, c'è ancora molto lavoro da fare per gestire i picchi di CPU e di I/O del disco, e non sorprende, considerando quanto sia ancora giovane TSDB Prometheus 2 rispetto a InnoDB, TokuDB, RocksDB, WiredTiger, ma tutti avevano problemi simili all'inizio del loro ciclo di vita.
Fonte: habr.com
