Best Practices per la Gestione della Memoria e l'Alta Throughput di RabbitMQ

Ottimizza memoria, limiti del disco, code e consumatori di RabbitMQ in modo che l'alta throughput non si trasformi in pressione sul broker.

Best Practices per la Gestione della Memoria e l'Alta Throughput di RabbitMQ

RabbitMQ può gestire molti messaggi, ma non è contento quando la memoria diventa il piano di overflow. Il broker ha bisogno di memoria per connessioni, canali, processi delle code, metadati dei messaggi, consegne non confermate, plugin, metriche e il runtime Erlang stesso. Se i publisher sono più veloci dei consumatori per un periodo sufficientemente lungo, la domanda non è più "Quanto veloce può andare RabbitMQ?" ma "Dove si manifesterà prima la pressione?"

Una buona gestione della memoria consiste principalmente nel mantenere quella pressione visibile e controllata. Vuoi che RabbitMQ applichi contropressione prima che il sistema operativo inizi a uccidere i processi. Vuoi anche abbastanza spazio su disco per scrivere in sicurezza messaggi persistenti e stato interno.

Inizia con l'allarme di memoria, ma non trattarlo come magia di ottimizzazione

RabbitMQ usa vm_memory_high_watermark per decidere quando l'uso della memoria è troppo alto. Quando la soglia viene superata, RabbitMQ alza un allarme di memoria e blocca i publisher finché la memoria non scende. Questo comportamento è intenzionale. Un publisher bloccato è fastidioso; un broker senza memoria è peggio.

Un punto di partenza comune è un watermark relativo intorno al 40% della memoria disponibile:

vm_memory_high_watermark.relative = 0.40

Quel numero non è sacro. Una piccola VM con altri servizi potrebbe aver bisogno di una soglia più bassa. Un broker dedicato con carichi di lavoro ben compresi può tollerare un valore diverso. Il punto è lasciare spazio per la cache di pagina del sistema operativo, l'attività del filesystem, gli agenti di monitoraggio e i picchi che si verificano prima che i tuoi grafici li catturino.

Puoi anche impostare un valore assoluto, che spesso è più facile in container o ambienti dove la "memoria disponibile" può essere fraintesa:

vm_memory_high_watermark.absolute = 6GiB

Usa uno stile che corrisponda a come è distribuito il nodo. Nei container, verifica che RabbitMQ veda il limite del container che ti aspetti, non l'intera memoria dell'host. Un watermark basato sul totale di memoria sbagliato è un modo silenzioso per creare un incidente di produzione.

Il limite di spazio libero su disco è l'altra barriera di sicurezza

L'impostazione di protezione del disco di RabbitMQ è disk_free_limit. Si basa sullo spazio libero, non su una percentuale disk_high_watermark. Quando lo spazio libero su disco scende al di sotto del limite configurato, RabbitMQ alza un allarme del disco e blocca i publisher.

Per molti nodi di produzione, un limite assoluto è più chiaro di uno relativo:

disk_free_limit.absolute = 20GB

Il valore giusto dipende dalla dimensione dei messaggi, dal tasso di pubblicazione, dalla persistenza, dalla rotazione dei log e dalla velocità con cui il tuo team può aggiungere spazio o svuotare le code. Un nodo che riceve grandi messaggi persistenti ha bisogno di un cuscinetto molto più grande di un nodo che gestisce piccoli eventi transienti.

Non impostare questo valore a un valore minuscolo solo per evitare allarmi. Gli allarmi del disco sono lì per proteggere il broker. Se un disco raggiunge zero byte liberi, puoi finire con scritture fallite, disponibilità danneggiata e un recupero molto più complicato.

Comprendi cosa sta effettivamente usando la memoria

Quando la memoria aumenta, evita di indovinare. RabbitMQ espone una ripartizione della memoria tramite l'interfaccia di gestione e la CLI:

rabbitmq-diagnostics memory_breakdown
rabbitmqctl status
rabbitmqctl list_queues name type messages_ready messages_unacknowledged memory

La prima suddivisione più utile è tra messaggi pronti e messaggi non confermati. I messaggi pronti sono ancora in attesa nella coda. I messaggi non confermati sono stati consegnati ai consumatori e aspettano basic.ack, basic.nack o la chiusura del canale.

Se i messaggi pronti stanno aumentando, i produttori superano i consumatori o i consumatori non sono connessi. Se i messaggi non confermati stanno aumentando, i consumatori stanno prendendo messaggi ma non li stanno completando. Sono problemi diversi. Aumentare i limiti di memoria guadagnerà solo tempo se lo squilibrio del flusso rimane.

I messaggi grandi meritano un'attenzione speciale. Una coda con un numero modesto di messaggi può comunque consumare molta memoria se ogni messaggio trasporta un payload grande. Se i messaggi contengono immagini, documenti o grandi blob JSON, considera di memorizzare il payload altrove e inviare un riferimento tramite RabbitMQ. I broker di messaggi sono generalmente migliori per spostare notifiche di lavoro che per fungere da archivi di blob.

Ottimizza il prefetch per fermare backlog nascosti

Il prefetch controlla quanti messaggi non confermati RabbitMQ può consegnare a un consumatore. Un valore di prefetch alto può migliorare la produttività per consumatori veloci, ma sposta anche il backlog fuori dalla coda e nella memoria del consumatore.

Ad esempio, dieci consumatori con prefetch_count=500 possono contenere fino a 5.000 messaggi non confermati al di fuori della coda dei pronti. Se ogni messaggio è grande o lento da elaborare, ciò può creare pressione sulla memoria e latenza irregolare. Un nuovo messaggio potrebbe aspettare dietro centinaia di messaggi più vecchi già seduti all'interno di un consumatore lento.

Inizia con un valore di prefetch che corrisponda al lavoro. Per chiamate API lente o scritture su database, prova un numero piccolo come 5 o 10 e aumenta solo dopo aver misurato. Per lavoro CPU locale molto veloce, valori più alti possono aiutare. Per una stretta equità, prefetch_count=1 è talvolta il giusto compromesso, anche se la produttività totale è inferiore.

La chiave è misurare il tempo di elaborazione e il ritardo di ack. RabbitMQ non può finire i messaggi per te. Può solo limitare quanto lavoro non finito distribuisce.

Mantieni le code corte quando possibile

RabbitMQ funziona meglio quando i messaggi fluiscono attraverso il sistema piuttosto che rimanere nelle code per ore. Una coda che è di solito vicino a zero e occasionalmente ha un picco è sana. Una coda che cresce tutto il giorno e si svuota durante la notte è un avviso di capacità. Una coda che cresce solo è un'interruzione al rallentatore.

Per lunghi backlog, decidi se il backlog è previsto. Se è previsto, usa il tipo di coda e il design di archiviazione che si adatta. Le code di quorum sono buone per carichi di lavoro durabili replicati. Gli stream possono adattarsi a carichi di lavoro di tipo replay. Le code classiche possono andare bene per lavori transienti più semplici. Se il backlog non è previsto, aggiusta i consumatori o i servizi a valle prima di ottimizzare la memoria del broker.

Imposta TTL sui messaggi solo quando il lavoro scaduto è genuinamente inutile. Un TTL non è un sostituto della capacità. Può proteggere un sistema dall'elaborazione di messaggi obsoleti, ma può anche nascondere la perdita di dati se applicato con noncuranza.

Le code di dead-letter aiutano a separare i messaggi problematici dal flusso normale. Senza una strategia di dead-letter, un singolo payload difettoso può essere ritentato all'infinito, consumare risorse e far sembrare la coda più lenta di quanto non sia in realtà.

La persistenza cambia il budget di throughput

Le code durabili e i messaggi persistenti sono la scelta giusta quando i messaggi devono sopravvivere a un riavvio del broker. Richiedono anche scritture su disco. Le conferme del publisher aggiungono un segnale di affidabilità in modo che i publisher sappiano quando il broker ha accettato la responsabilità per un messaggio.

Il pattern lento è pubblicare un messaggio persistente, aspettare in modo sincrono la sua conferma, poi pubblicare il successivo. È semplice e sicuro, ma la produttività sarà limitata dal tempo di andata e ritorno e dal comportamento del disco. Un pattern migliore è usare conferme asincrone del publisher o piccoli batch, gestendo comunque le conferme negative e i timeout.

Evita le transazioni AMQP per la pubblicazione ad alta produttività a meno che tu non abbia una ragione molto specifica. Le conferme del publisher sono il solito strumento di affidabilità per i publisher RabbitMQ.

Dai a RabbitMQ un'infrastruttura prevedibile

A RabbitMQ piacciono macchine prevedibili: abbastanza memoria, dischi veloci per carichi di lavoro persistenti, latenza di rete stabile e nessun vicino rumoroso che ruba CPU. Se il broker condivide un host con un database, un elaboratore di log e lavori cron casuali, l'ottimizzazione della memoria diventa un'ipotesi.

Usa storage SSD o NVMe per code persistenti ad alta produttività. Controlla la latenza del disco, non solo l'utilizzo del disco. Un disco può mostrare una produttività moderata e avere comunque una dolorosa latenza di scrittura. Negli ambienti cloud, IOPS provisionati e crediti burst possono contare più dell'etichetta del disco.

Limita il ricambio di connessioni. Connessioni e canali di lunga durata sono più economici che aprirne di nuovi per ogni pubblicazione. Se un'applicazione crea migliaia di connessioni di breve durata, l'uso di memoria e descrittori di file può aumentare anche quando i tassi di messaggi sono ordinari.

I container richiedono un pensiero esplicito

RabbitMQ funziona bene nei container, ma i limiti di memoria devono essere chiari. Il watermark di memoria del broker è utile solo se è calcolato rispetto al limite che il container può effettivamente utilizzare. Se RabbitMQ pensa di avere la memoria dell'host ma il runtime del container impone un limite più piccolo, il container può essere ucciso prima che il comportamento di allarme di RabbitMQ lo protegga.

Imposta un limite di memoria del container, quindi imposta un watermark assoluto di RabbitMQ che lasci spazio all'interno di quel limite:

vm_memory_high_watermark.absolute = 3GiB

Ad esempio, su un container limitato a 4 GiB, un watermark del broker di 3 GiB può essere ragionevole per un pod dedicato, mentre un valore più basso può essere migliore se sidecar o plugin usano memoria significativa. Non copiare quel numero ciecamente. Il punto è rendere esplicita la relazione.

I dati persistenti hanno anche bisogno di storage persistente. Se un riavvio del container perde la directory dei dati di RabbitMQ, le code durabili e i messaggi persistenti non ti salveranno. Usa volumi appropriati, comprendi la tua classe di storage e testa un riavvio del broker prima di fidarti della configurazione.

Code lazy, code di quorum e aspettative di memoria

I vecchi consigli su RabbitMQ spesso dicono "usa code lazy per grandi backlog". Quel consiglio ha bisogno di contesto. Le code lazy classiche sono state progettate per mantenere più messaggi su disco e ridurre la pressione della memoria per code lunghe. Possono ancora essere utili per carichi di lavoro di code classiche dove sono previsti grandi backlog.

Le code di quorum si comportano diversamente e sono comunemente usate per carichi di lavoro durabili replicati. Possono gestire backlog, ma replicano anche i dati e hanno il proprio profilo di memoria e disco. Una coda di quorum è prima di tutto una scelta di affidabilità. Non è una scorciatoia per un backlog illimitato.

Se il business si aspetta che i messaggi rimangano per giorni e vengano riprodotti da molti consumatori, uno stream o un altro sistema di tipo log potrebbe adattarsi meglio di una normale coda di lavoro. RabbitMQ è eccellente per distribuire lavoro. È meno piacevole quando diventa l'unico strato di archiviazione a lungo termine per grandi payload storici.

Separa i sintomi del broker dai sintomi del carico di lavoro

Un allarme di memoria ti dice che RabbitMQ è sotto pressione. Non ti dice se RabbitMQ è la causa principale. Una lenta API di fatturazione può far sì che i consumatori smettano di fare ack, il che fa aumentare i messaggi non confermati, il che aumenta la memoria del broker, il che blocca i publisher. L'allarme del broker è reale, ma la prima correzione potrebbe essere al di fuori del broker.

Durante una revisione, grafica insieme il tasso di pubblicazione, il tasso di consegna, il tasso di ack, i messaggi pronti, i messaggi non confermati, la memoria, lo spazio libero su disco e il tempo di elaborazione del consumatore. L'ordine del movimento è importante. Se il tasso di ack scende prima che la memoria aumenti, guarda i consumatori. Se la latenza del disco aumenta prima che le conferme rallentino, guarda lo storage. Se il tasso di pubblicazione raddoppia dopo un lancio di prodotto, guarda la capacità e la contropressione.

Questo è anche il motivo per cui i test di carico dovrebbero includere consumatori e dipendenze a valle. Un benchmark solo di pubblicazione dimostra molto poco su un flusso di lavoro reale. Il broker può accettare messaggi rapidamente per un po', ma il sistema funziona solo se i consumatori li finiscono al tasso richiesto.

Rendi visibile la contropressione ai team applicativi

Il blocco dei publisher non dovrebbe essere invisibile. Le applicazioni dovrebbero registrare eventi di connessione bloccata e sbloccata quando la libreria client li espone, e i publisher dovrebbero avere timeout attorno ai percorsi di pubblicazione che alimentano richieste rivolte all'utente.

Senza quella visibilità, un allarme di memoria diventa una vaga lamentela "l'app è lenta". Con essa, il team può vedere che RabbitMQ ha applicato contropressione in un momento specifico, quindi confrontare quel timestamp con la profondità della coda, gli errori del consumatore, la latenza del disco e gli eventi di distribuzione.

Cosa controllo durante una revisione di alta produttività

Inizio con queste domande:

  • Gli allarmi di memoria o disco stanno scattando?
  • I messaggi sono per lo più pronti o non confermati?
  • Quali code usano più memoria?
  • I consumatori stanno tenendo il passo con il tasso di pubblicazione?
  • Le conferme del publisher sono asincrone o bloccanti una per una?
  • I messaggi sono più grandi del necessario?
  • La latenza del disco sta aumentando durante i picchi?
  • Le connessioni sono stabili o si riconnettono costantemente?

Queste risposte di solito indicano la correzione. A volte la correzione è un cambiamento di configurazione. Più spesso è un cambiamento di flusso: consumatori più veloci, prefetch più basso, messaggi più piccoli, migliore batching, un percorso di dead-letter o un tipo di coda che corrisponde al carico di lavoro.

L'alta produttività non è solo un numero più grande su un benchmark. È la capacità di assorbire periodi di attività senza perdere il controllo di memoria, disco e latenza. RabbitMQ ti dà le barriere di sicurezza, ma devi comunque mantenere il traffico in movimento.