Best practice per applicazioni RTB

Questa guida illustra le best practice da considerare durante lo sviluppo di applicazioni in base al protocollo RTB.

Gestire le connessioni

Mantenere attive le connessioni

L'instaurazione di una nuova connessione aumenta le latenze e richiede molto più risorse su entrambe le estremità rispetto al riutilizzo di una esistente. Chiudendo meno connessioni, puoi ridurre il numero di connessioni che devono essere riaperte.

Innanzitutto, ogni nuova connessione richiede un viaggio di andata e ritorno aggiuntivo sulla rete per essere stabilita. Poiché stabiliamo le connessioni su richiesta, la prima richiesta su una connessione ha una scadenza effettiva più breve ed è più probabile che scada rispetto alle richieste successive. Eventuali timeout aggiuntivi aumentano il tasso di errore, il che può portare alla limitazione dell'offerente.

In secondo luogo, molti server web generano un thread di lavoro dedicato per ogni connessione stabilita. Ciò significa che per chiudere e ricreare la connessione, il server deve arrestare ed eliminare un thread, allocarne uno nuovo, renderlo eseguibile e compilare lo stato della connessione prima di elaborare definitivamente la richiesta. Si tratta di un aggravio molto inutile.

Evitare di chiudere le connessioni

Inizia ottimizzando il comportamento di connessione. La maggior parte dei valori predefiniti del server è personalizzata per ambienti con un numero elevato di client, ognuno dei quali effettua un numero ridotto di richieste. Per l'RTB, invece, un piccolo pool di macchine invia richieste per conto di un numero relativamente elevato di browser. In queste condizioni, ha senso riutilizzare le connessioni il più possibile. Ti consigliamo di impostare:

  • Timeout per inattività su 2,5 minuti.
  • Numero massimo di richieste su una connessione al valore possibile più alto.
  • Numero massimo di connessioni al valore più alto che la RAM può supportare, facendo attenzione a verificare che il numero di connessioni non si avvicini troppo a questo valore.

In Apache, ad esempio, ciò comporterebbe l'impostazione di KeepAliveTimeout su 150, MaxKeepAliveRequests su zero e MaxClients su un valore che dipende dal tipo di server.

Una volta ottimizzato il comportamento di connessione, devi anche assicurarti che il codice dell'offerente non chiuda le connessioni inutilmente. Ad esempio, se hai un codice frontend che restituisce una risposta predefinita "Nessuna offerta" in caso di errori o timeout del backend, assicurati che il codice restituisca la risposta senza chiudere la connessione. In questo modo, eviti la situazione in cui, se l'offerente viene sovraccaricato, le connessioni iniziano a chiudersi e il numero di timeout aumenta, causando il rallentamento dell'offerente.

Mantieni le connessioni in equilibrio

Se Authorized Buyers si connette ai server dell'offerente tramite un server proxy, le connessioni potrebbero diventare sbilanciate nel tempo perché, conoscendo solo l'indirizzo IP del server proxy, Authorized Buyers non può determinare quale server dell'offerente riceve ogni callout. Nel tempo, man mano che Authorized Buyers stabilisce e chiude le connessioni e i server dell'offerente vengono riavviati, il numero di connessioni mappate a ciascuna può diventare molto variabile.

Quando alcune connessioni sono molto utilizzate, altre connessioni aperte possono rimanere inattive per la maggior parte del tempo perché non sono necessarie al momento. Man mano che il traffico di Authorized Buyers cambia, le connessioni inattive possono diventare attive e le connessioni attive possono diventare inattive. Ciò potrebbe causare carichi non uniformi sui server dell'offerente se le connessioni sono raggruppate in modo inadeguato. Google tenta di evitarlo chiudendo tutte le connessioni dopo 10.000 richieste, in modo da riequilibrare automaticamente le connessioni più utilizzate nel tempo. Se il traffico continua a non essere bilanciato nel tuo ambiente, puoi intraprendere ulteriori azioni:

  1. Seleziona il backend per richiesta anziché una volta per connessione se utilizzi proxy frontend.
  2. Specifica un numero massimo di richieste per connessione se utilizzi un proxy per le connessioni tramite un bilanciatore del carico o un firewall hardware e la mappatura è fissa una volta stabilite le connessioni. Tieni presente che Google specifica già un limite massimo di 10.000 richieste per connessione, quindi dovresti fornire un valore più rigoroso solo se continui a trovare connessioni calde che si raggruppano nel tuo ambiente. In Apache, ad esempio, imposta MaxKeepAliveRequests su 5000
  3. Configura i server dell'offerente per monitorare le frequenze di richiesta e chiudere alcune delle proprie connessioni se gestiscono costantemente troppe richieste rispetto ai coetanei.

Gestire il sovraccarico in modo efficiente

Idealmente, le quote dovrebbero essere impostate in modo sufficientemente elevato da consentire all'offerente di ricevere tutte le richieste che può gestire, ma non più di quelle. In pratica, mantenere le quote a livelli ottimali è un compito difficile e si verificano sovraccarichi per una serie di motivi: un backend che si arresta in modo anomalo durante i picchi di traffico, una modifica del mix di traffico che richiede un'elaborazione maggiore per ogni richiesta o un valore di quota impostato troppo elevato. Di conseguenza, vale la pena considerare il comportamento dell'offerente con un volume di traffico eccessivo.

Per gestire i cambiamenti temporanei del traffico (fino a una settimana) tra le regioni (in particolare tra Asia e Stati Uniti occidentali e Stati Uniti orientali e Stati Uniti occidentali), consigliamo di prevedere un margine del 15% tra il picco di 7 giorni e il QPS per località di trading.

In termini di comportamento in caso di carichi elevati, gli offerenti rientrano in tre categorie generali:

L'offerente "risponde a tutto"

Sebbene sia semplice da implementare, questo offerente ha il rendimento peggiore quando è sovraccaricato. Prova semplicemente a rispondere a ogni richiesta di offerta inviata, indipendentemente dalle circostanze, mettendo in coda quelle che non possono essere pubblicate immediatamente. Lo scenario che ne consegue è spesso simile al seguente:

  • Con l'aumento della frequenza delle richieste, aumentano anche le latenze delle richieste, fino a quando tutte le richieste non iniziano a scadere
  • Le latenze aumentano notevolmente quando le percentuali di callout si avvicinano al picco
  • Viene attivata la limitazione, che riduce drasticamente il numero di callout consentiti
  • Le latenze iniziano a recuperare, causando una riduzione del throttling
  • Il ciclo ricomincia.

Il grafico della latenza per questo offerente assomiglia a un andamento a dente di sega molto ripido. In alternativa, le richieste in coda inducono il server ad avviare la paginazione della memoria o a eseguire un'altra operazione che causa un rallentamento a lungo termine e le latenze non si riprendono fino al termine delle ore di picco, con un conseguente calo dei tassi di callout durante l'intero periodo di picco. In entrambi i casi, vengono effettuati o risposti meno callout rispetto a quanto accadrebbe se la quota fosse stata impostata su un valore inferiore.

L'offerente "errore in caso di sovraccarico"

Questo offerente accetta i callout fino a una certa frequenza, poi inizia a restituire errori per alcuni callout. Questo può essere fatto tramite timeout interni, disattivando la messa in coda delle connessioni (controllata da ListenBackLog su Apache), implementando una modalità di abbandono probabilistica quando l'utilizzo o le latenze diventano troppo elevate o tramite un altro meccanismo. Se Google rileva una percentuale di errori superiore al 15%, inizieremo a applicare il throttling. A differenza dell'offerente "risponde a tutto", questo offerente "riduce le perdite", il che gli consente di recuperare immediatamente quando i tassi di richiesta diminuiscono.

Il grafico della latenza per questo offerente assomiglia a un andamento a dente di sega superficiale durante i sovraccarichi, localizzato intorno alla frequenza massima accettabile.

L'offerente "nessuna offerta in caso di sovraccarico"

Questo offerente accetta callout fino a una certa frequenza, poi inizia a restituire risposte "nessuna offerta" per eventuali sovraccarichi. Analogamente all'offerente "errore in caso di sovraccarico", questo può essere implementato in diversi modi. La differenza è che non viene restituito alcun segnale a Google, quindi non riduciamo mai la frequenza dei callout. Il sovraccarico viene assorbito dalle macchine frontend, che consentono solo il traffico che possono gestire di continuare fino ai backend.

Il grafico della latenza per questo offerente mostra un plateau che (artificialmente) si interrompe parallelamente al tasso di richieste nei periodi di picco e un corrispondente calo della frazione di risposte che contengono un'offerta.

Ti consigliamo di combinare l'approccio "errore in caso di sovraccarico" con quello "nessuna offerta in caso di sovraccarico", nel seguente modo:

  • Esegui il provisioning eccessivo dei front-end e impostali in modo che generino un errore in caso di sovraccarico, per massimizzare il numero di connessioni a cui possono rispondere in qualche modo.
  • In caso di errore di sovraccarico, le macchine front-end possono utilizzare una risposta predefinita "no-bid" e non devono analizzare la richiesta.
  • Implementa il controllo di integrità dei backend in modo che, se nessuno dispone di capacità sufficiente, restituiscano una risposta di tipo "no-bid".

In questo modo è possibile assorbire un certo sovraccarico e i backend hanno la possibilità di rispondere esattamente al numero di richieste che possono gestire. Puoi considerare questa situazione come "Nessuna offerta in caso di sovraccarico", con le macchine di front-end che tornano a "Errore in caso di sovraccarico" quando il numero di richieste è notevolmente superiore alle aspettative.

Se hai uno strumento di offerta che risponde a tutto, valuta la possibilità di trasformarlo in uno strumento di offerta che genera un errore in caso di sovraccarico regolando il comportamento di connessione in modo che, in pratica, rifiuti di essere sovraccaricato. Sebbene questo causi il ritorno di più errori, riduce i timeout e impedisce al server di entrare in uno stato in cui non può rispondere a nessuna richiesta.

Valuta la possibilità di utilizzare il peering

Un altro modo per ridurre la latenza o la variabilità della rete è eseguire il peering con Google. Il peering consente di ottimizzare il percorso del traffico per raggiungere l'offerente. Gli endpoint di connessione rimangono invariati, ma i link intermedi cambiano. Per maggiori dettagli, consulta la guida al peering. Il motivo per cui considerare il peering come una best practice può essere riassunto come segue:

  • Su internet, i link di transito vengono scelti principalmente tramite il "routing hot-potato", che trova il link più vicino all'esterno della nostra rete che può indirizzare un pacchetto alla sua destinazione e lo instrada tramite quel link. Quando il traffico attraversa una sezione della dorsale di proprietà di un fornitore con cui abbiamo molti peering, è probabile che il link scelto si trovi vicino all'inizio del pacchetto. Oltre questo punto, non abbiamo alcun controllo sul percorso seguito dal pacchetto fino all'offerente, pertanto potrebbe essere reindirizzato ad altri sistemi autonomi (reti) lungo il percorso.

  • Al contrario, quando è in vigore un contratto di peering diretto, i pacchetti vengono sempre inviati tramite un link di peering. Indipendentemente dalla sua origine, il pacchetto attraversa i link di proprietà o in leasing di Google fino a raggiungere il punto di peering condiviso, che dovrebbe essere vicino alla località dell'offerente. Il viaggio di ritorno inizia con un breve salto sulla rete di Google e rimane sulla rete di Google per il resto del percorso. Mantenere la maggior parte del percorso nell'infrastruttura gestita da Google garantisce che il pacchetto segua un percorso a bassa latenza ed evita molta potenziale variabilità.

Invia DNS statico

Consigliamo agli acquirenti di inviare sempre un singolo risultato DNS statico a Google e di fare affidamento su Google per gestire l'invio del traffico.

Di seguito sono riportate due pratiche comuni dei server DNS degli offerenti quando si tenta di bilanciare il carico o gestire la disponibilità:

  1. Il server DNS fornisce un indirizzo o un sottoinsieme di indirizzi in risposta a una query, quindi esegue il ciclo di questa risposta in qualche modo.
  2. Il server DNS risponde sempre con lo stesso insieme di indirizzi, ma alterna l'ordine degli indirizzi nella risposta.

La prima tecnica non è molto efficace per il bilanciamento del carico, poiché viene utilizzata molta memorizzazione nella cache su diversi livelli dello stack e i tentativi di aggirare la memorizzazione nella cache probabilmente non daranno nemmeno i risultati preferiti, poiché Google addebita al offerente il tempo di risoluzione DNS.

La seconda tecnica non consente alcun bilanciamento del carico, poiché Google seleziona in modo casuale un indirizzo IP dall'elenco di risposte DNS, quindi l'ordine nella risposta non ha importanza.

Se un offerente apporta una modifica al DNS, Google rispetterà il TTL(Time-to-live) impostato nei suoi record DNS, ma l'intervallo di aggiornamento rimane incerto.