Problema molto lento di Sidekiq con coda grande dovuto a un numero enorme di notifiche utente non lette

Grazie @Falco

Sono principalmente bloccato sul capire come le prestazioni possano oscillare tra il completamento di ~11 milioni e ~300.000 lavori al giorno nell’arco di ~1 settimana con la stessa configurazione. Una differenza di velocità di ~35 volte in termini di lavori al secondo.

Per quanto riguarda l’utilizzo della CPU, è tornato al ~15-20%, che è la norma. L’elaborazione dei lavori avviene alla stessa velocità (lenta).

Solo per chiarire/confermare, intendevo assegnare (non aggiungere) alcuni sidekiq esclusivamente per elaborare la coda a bassa priorità, poiché sembrava che i task a bassa priorità potessero essere elaborati a una velocità molto maggiore e forse non subiscano gli stessi colli di bottiglia. Stavo ipotizzando che questo potesse spiegare perché il numero di lavori al secondo possa variare così drasticamente (cioè task ‘facili’ a bassa priorità bloccati dietro il backlog della coda predefinita).

Per chiarire: pensi che le prestazioni di PostgreSQL stiano causando il completamento lento dei lavori o solo l’evento di elevato utilizzo della CPU notato ieri (che ora è tornato alla normalità)?

Tutto questo è su SSD, vero?

Sì, corretto @Stephen - NVMe SSD RAID 1.

Aggiornamento: Ho provato a eliminare più volte la coda a priorità bassa e quella predefinita, ma senza alcun impatto sulla velocità, poiché la coda predefinita ricresce immediatamente. Ho quindi provato a eliminare la coda predefinita e ad attivare la modalità sola lettura. Questo ha fatto schizzare in alto drasticamente il numero di job al secondo, smaltendo la coda a priorità bassa a una velocità impressionante (circa 100 volte i job al secondo).

Modifica: Sembra che anche con una coda a priorità bassa molto grande, la velocità di elaborazione rimanga comunque lenta. Se imposto Discourse in modalità sola lettura, svuoto entrambe le code a priorità bassa e predefinita. L’elaborazione dei job successiva sembra rimanere super veloce, svuotando le attività programmate e le code fino a quando non disattivo la modalità sola lettura. :yuno:

Il mio prossimo passo sarebbe capire esattamente quale processo sta causando il problema, entrando nell’applicazione Discourse ed eseguendo htop o top per visualizzare i processi con il maggior utilizzo della CPU.

Sembra proprio che il collo di bottiglia sia PostgreSQL. Potresti configurare Prometheus per monitorarne le prestazioni e verificare che abbia accesso a memoria RAM sufficiente.

Grazie per il tuo contributo @pfaffman :slight_smile: Penso che db_shared_buffers e db_work_mem in app.yml siano gli unici controlli per l’accesso alla RAM di PostgreSQL, giusto?

Ho fatto alcune prove sia aumentando che diminuendo i valori. Le impostazioni attuali in app.yml sono:
db_shared_buffers: “32768MB”
db_work_mem: “128MB”

Con una RAM totale del sistema di 128 GB.

Ho anche provato a modificare max_connections in /var/discourse/shared/standalone/postgres_data/postgresql.conf e poi a ricostruire Discourse. Ho provato valori superiori al predefinito (100), da 200 a 500. Attualmente è impostato a 300. Non sono sicuro che modificarlo lì stia effettivamente cambiando il valore massimo delle connessioni.

Vedo queste impostazioni in /var/discourse/templates/postgres.template.yml:

db_synchronous_commit: “off”
db_shared_buffers: “256MB”
db_work_mem: “10MB”
db_default_text_search_config: “pg_catalog.english”
db_name: discourse
db_user: discourse
db_wal_level: minimal
db_max_wal_senders: 0
db_checkpoint_segments: 6
db_logging_collector: off
db_log_min_duration_statement: 100

Grazie @bartv, seguendo il tuo consiglio ho monitorato dall’interno dell’app Discourse tramite top. Ho notato molti processi postmaster eseguiti dall’utente postgres, con un utilizzo della CPU variabile. Gli screenshot rappresentano periodi di tempo prolungati con statistiche di utilizzo simili.

Utilizzo di circa il 95% dei 32 core:

Utilizzo di circa il 20%, con un utilizzo della CPU più basso per postmaster.

Utilizzo di circa il 6% della CPU, mentre la modalità sola lettura era attiva.

Quanto è grande il tuo database? Quanti utenti hai? Quanti nuovi post al giorno?

La prima cosa da fare è eseguire VACUUM ANALYZE; dalla console di postgres.

L’esecuzione potrebbe richiedere del tempo; potresti voler fermare temporaneamente sidekiq per alleggerire il carico mentre lavora.

Se questo non aiuta, dovremmo abilitare pg_stat_statements e poi verificare quali query stanno utilizzando una quantità enorme di CPU.

@pfaffman

  • La cartella /var/discourse/shared/standalone/postgres_data è di 170 GB
  • 61,7k utenti attivi negli ultimi 30 giorni (non sono sicuro del totale assoluto)
  • Circa 50k-80k nuovi post al giorno

Oh. Questo non è banale.

Dovresti informarti sull’ottimizzazione di PostgreSQL. Quel livello di prestazioni va un po’ oltre il tipico self-hosting che si vede qui.

Proverei a dedicare a PostgreSQL circa 3/4 della RAM. Di certo dividerei i container in uno per i dati e uno per il web. Tuttavia, potresti aver bisogno di una configurazione di PostgreSQL più complessa per ottenere le prestazioni di cui hai bisogno.

EDIT: Ma non ho esperienza con database di dimensioni molto maggiori, quindi vedi sotto! :wink:

Gestiamo database molto più grandi con molta meno RAM e senza un utilizzo della CPU quasi così elevato.

Le informazioni di pg_stat_statements saranno probabilmente in grado di dirci cosa non va.

Grazie mille per l’aiuto, ragazzi.

Ho provato a eseguire VACUUM ANALYZE; - purtroppo senza successo. Di seguito i comandi utilizzati, per riferimento:

cd /var/discourse/
./launcher enter app
sudo -u postgres psql
\c discourse
VACUUM ANALYZE;

Ho tentato di abilitare pg_stat_statements; i passaggi eseguiti sono riportati di seguito:

Ho aggiunto/modificato le righe qui: /var/discourse/shared/standalone/postgres_data/postgresql.conf

shared_preload_libraries = ‘pg_stat_statements’ # (richiede riavvio)
pg_stat_statements.max = 10000
pg_stat_statements.track = all
pg_stat_statements.track_utility = on
pg_stat_statements.save = off

Poi ho ricostruito Discourse ed eseguito:

./launcher enter app
sudo -u postgres psql
\c discourse
CREATE EXTENSION pg_stat_statements;

Ho provato a eseguire delle query, ma ottengo questo errore:

ERROR: pg_stat_statements deve essere caricato tramite shared_preload_libraries

Il mio sospetto è che le modifiche apportate al file postgresql.conf (/var/discourse/shared/standalone/postgres_data/postgresql.conf) non abbiano effetto (ho ricostruito Discourse dopo aver modificato il file). È possibile apportare queste modifiche tramite il file app.yml? Oppure notate qualcosa che ho sbagliato?

Ricostruire Discourse cancellerà quelle modifiche. Riavviare il container dovrebbe risolvere il problema. (Qualcosa come sv restart postgres all’interno del container potrebbe funzionare anch’esso).

Grazie, ho provato a riavviare il contenitore:

./launcher stop app
./launcher start app

Ricevo ancora lo stesso errore quando provo a eseguire la query:

ERROR: pg_stat_statements deve essere caricato tramite shared_preload_libraries

Le modifiche che ho apportato in precedenza persistono ancora nel file qui, anche dopo una ricostruzione:

/var/discourse/shared/standalone/postgres_data/postgresql.conf

Sospetto che questo non sia il file in cui dovrei apportare queste modifiche :face_with_monocle:

Forse è dovuto al fatto che utilizzo solo app.yml (e mail-receiver.yml) nella cartella dei contenitori, senza aver implementato l’uso di data.yml.

Senza guardare effettivamente, è probabile che si trovi in /etc/postgres all’interno del container. Potresti anche dover installare la libreria che richiede.

Grazie, all’inizio ha aiutato molto. :man_cartwheeling:. Il problema sembrava risolto e la coda dei lavori andava velocissima solo aumentando le connessioni massime in postgresql.conf. Purtroppo dopo circa un giorno si è rallentata di nuovo.

Di seguito riporto i passaggi, nel caso siano utili ad altri che vogliono aumentare il max_connections di PostgreSQL.

docker ps

Ottieni l’ID del contenitore, ad esempio aaabbbccc123, e sostituiscilo nei comandi qui sotto:

Copia il file postgresql.conf dall’interno del contenitore Docker al file system locale:
docker cp aaabbbccc123:/etc/postgresql/10/main/postgresql.conf /srv

Modifica la configurazione:
nano /srv/postgresql.conf

Copialo nuovamente nel contenitore Docker:
docker cp /srv/postgresql.conf aaabbbccc123:/etc/postgresql/10/main/postgresql.conf

cd /var/discourse
./launcher stop app
./launcher start app

Elimina il file residuo (opzionale):
rm /srv/postgresql.conf

@supermathie I believe I’ve successfully enabled pg_stat_statements :grinning:

I tried using this query:

SELECT
(total_time / 1000 / 60) as total,
(total_time/calls) as avg,
query
FROM pg_stat_statements
ORDER BY 1 DESC
LIMIT 100;

From this guide: The most useful Postgres extension: pg_stat_statements

I can’t really read the result though, I think I’ve done something wrong.

total | avg | query
1671.1110420745 | 374.736186677194 | SELECT COUNT(*) FROM ( +
| | SELECT $1 FROM +
| | notifications n +
| | LEFT JOIN topics t ON t.id = n.topic_id +
| | WHERE t.deleted_at IS NULL AND +
| | n.notification_type <> $2 AND +
| | n.user_id = $3 AND +
| | n.id > $4 AND

Ora che sai cosa vuoi fare, puoi apportare queste modifiche utilizzando uno stanza replace nel tuo app.yml.

In alternativa, puoi semplicemente eseguire ./launcher enter app e modificare i file direttamente. Tieni presente, tuttavia, che quando ricostruisci il container, queste modifiche non saranno presenti nel nuovo container.