In quali condizioni (se presenti) è buona norma interrogare due server e consumare solo la risposta più veloce?


12

Ho posto quella che ora è una domanda eliminata dalla community su SO sul perché qualcuno dovrebbe usare javascript Promise.racee un utente di alto livello ha commentato questo:

Se si dispone di due servizi che calcolano un valore, è possibile interrogarli entrambi in parallelo e utilizzare quale valore viene restituito per primo, anziché interrogarne uno, attendere un errore, quindi interrogare il secondo.

Ho cercato su Google la ridondanza e questo caso d'uso in generale, ma non sono riuscito a trovare nulla e, dal mio POV, non è mai una buona idea aggiungere un carico di lavoro a un server / servizio se non si intende utilizzare la risposta.


Esempio giocattolo: anziché utilizzare sempre quicksort, copi i dati, li invii a quicksort, a mergesort e a heapsort, ... ecc. Non è necessario ispezionare l'input per vedere se si tratta di un caso patologico per nessuno di questi, perché non sarà un caso patologico per tutti loro
Caleth,

Il documento di Dean e Barroso The Tail at Scale chiama una variazione di questo approccio "Richieste coperte". Discute anche i pro e i contro di diversi approcci correlati al controllo della variabilità della coda lunga nei tassi di errore e nella latenza.
Daniel Pryden,

La seconda "richiesta del server" potrebbe essere un falso. Potrebbe volere solo per 5 secondi e quindi restituire una risposta segnaposto. Questo ti dà un timeout sulla richiesta reale.
user253751

Ordina un Lyft e quindi un Uber. Prendi quello che viene prima.
user2023861

@utente2023861 in questa analogia, mentre un guidatore stava guidando inutilmente verso la tua posizione, avrebbe potuto invece
accettare

Risposte:


11

Direi che questa è più una questione economica. Tuttavia, questa è una chiamata di giudizio che gli ingegneri dovrebbero essere in grado di fare. Quindi, sto rispondendo.

Divido la mia risposta in quattro parti:

  • Gestione del rischio
  • strategie
  • Costi
  • Intuizione

Gestione del rischio

Pertanto, a volte il client non riesce a ottenere una risposta dal server. Presumo che ciò non sia dovuto a un errore programmatico (altrimenti la soluzione è risolverlo, quindi vai a farlo). Invece, deve essere a causa di una situazione fortuita al di fuori del tuo controllo ...

Ma non oltre la tua conoscenza. Devi sapere:

  • Quanto spesso succede.
  • Che impatto ha.

Ad esempio, se il fallimento e il tentativo si verificano solo circa il 2% delle volte, probabilmente non vale la pena affrontarlo. Se succede circa l'80% delle volte, beh ... dipende ...

Quanto tempo deve attendere il cliente? E come si traduce in costi ... vedi, hai un piccolo ritardo in un'applicazione normale, probabilmente non è un grosso problema. Se è significativo e hai un'applicazione in tempo reale o un videogioco online, questo allontanerà gli utenti e probabilmente stai meglio investendo in server più o migliori. Altrimenti, probabilmente puoi inserire un messaggio "caricamento" o "in attesa del server". A meno che, il ritardo sia davvero grande (nell'ordine delle decine di secondi), allora può essere troppo anche per l'applicazione normale.


strategie

Come ho detto sopra, ci sono più di un modo per affrontare questo problema. Presumo che tu abbia già l'attrezzo loop try-fail-retry. Quindi, vediamo ...

  • Inserisci un messaggio di caricamento. È economico, aiuta la fidelizzazione dell'utente.
  • Query in parallelo. Può essere più veloce, può ancora fallire. Richiederà un server ridondante (può essere costoso), perderà tempo e traffico di rete.
  • Eseguire una query in parallelo per stabilizzare il server più veloce e utilizzarlo da lì in poi. Può essere più veloce, può ancora fallire. Richiederà server ridondanti (può essere costoso), non sprecherà tanto tempo server e traffico di rete.

Ora, noti che dico che questi possono ancora fallire. Se supponiamo che una query a un server abbia una probabilità dell'errore dell'80%, una query parallela a due server ha una probabilità dell'errore del 64%. Pertanto, potrebbe essere necessario riprovare.

Un vantaggio aggiuntivo di scegliere il server più veloce e continuare a usarlo è che anche il server più veloce ha meno probabilità di fallire a causa di problemi di rete.

Il che mi ricorda, se riesci a capire perché la richiesta non riesce, fallo. Può aiutarti a gestire meglio la situazione, anche se non riesci a prevenire gli errori. Ad esempio, hai bisogno di una maggiore velocità di trasferimento sul lato server?

Alcuni di più:

  • Distribuire più server in tutto il mondo e selezionare il server in base alla geolocalizzazione.
  • Effettua il bilanciamento del carico sul lato server (una macchina dedicata prenderà tutte le richieste e le invierà ai tuoi server, puoi avere il tuo parallelismo lì o una migliore strategia di bilanciamento).

E chi ha detto che devi fare solo uno di questi? Puoi inserire un messaggio di caricamento, interrogare più server che sono sparsi in tutto il wrold per scegliere il più veloce e usarlo solo da lì in poi, in caso di tentativi falliti su un ciclo e avere ognuno di quei server come un cluster di macchine con bilanciamento del carico . Perchè no? Bene, costa ...


Costi

Ci sono quattro costi:

  • Il costo dello sviluppo (di solito molto economico)
  • Il costo di implementazione (generalmente elevato)
  • Durata dei costi (dipende dal tipo di applicazione e dal modello aziendale)
  • Il costo del fallimento (probabilmente basso, ma non necessariamente)

Devi bilanciarli.

Ad esempio, supponiamo che tu guadagni circa un dollaro per utente soddisfatto. Che hai 3000 utenti al giorno. Che le richieste falliscono circa il 50% delle volte. E quel 2% degli utenti se ne va senza pagare quando la richiesta fallisce. Ciò significa che stai perdendo (3000 * 50% * 2%) 30 dollari al giorno. Ora, diciamo che lo sviluppo della nuova funzionalità ti costerà 100 dollari e la distribuzione dei server ti costerà 800 dollari - e ignorando i costi di runtime - questo significa che avresti un ritorno dell'investimento in ((100 + 800) / 30 ) 30 giorni. Ora puoi controllare il tuo budget e decidere.

Non considerare questi valori rappresentativi della realtà, li ho scelti per motivi di matematica.

addendum:

  • Ricorda che sto anche ignorando i dettagli. Ad esempio, potresti avere pochi costi di implementazione ma pagare per il tempo della CPU e devi tenerlo presente.
  • Alcuni clienti potrebbero apprezzare se non sprechi il loro pacchetto di dati in richieste ridondanti.
  • Migliorare il tuo prodotto può aiutare a portare pubblicità naturale.
  • Non dimenticare i costi delle opportunità. Dovresti sviluppare qualcos'altro?

Il fatto è che se si considera il problema in termini di costi di bilanciamento, è possibile effettuare una stima del costo per le strategie che si considerano e utilizzare questa analisi per decidere.


Intuizione

Intuizione se favorita dall'esperienza. Non sto suggerendo di fare questo tipo di analisi ogni volta. Alcune persone lo fanno e va bene. Ti sto suggerendo di comprenderlo e di sviluppare un'intuizione per questo.

Inoltre, in ingegneria, oltre alle conoscenze che otteniamo dalla scienza reale, impariamo anche nella pratica e compiliamo linee guida su ciò che funziona e cosa no. Pertanto, è spesso saggio vedere qual è lo stato dell'arte ... anche se a volte è necessario vedere al di fuori della propria area.

In questo caso, guarderei i videogiochi online. Hanno schermate di caricamento, hanno più server, sceglieranno un server in base alla latenza e potrebbero persino consentire all'utente di cambiare server. Sappiamo che funziona.

Vorrei suggerire di farlo invece di sprecare il traffico di rete e il tempo del server su ogni richiesta, anche essere consapevoli del fatto che anche con un server ridondante, può verificarsi un errore.


2
Non credo di doverlo dire, ma questa è un'ottima risposta :) Sapevo che lo avrei accettato alle prime 10 righe, ma ti ho dato l'opportunità di fallire e di leggerlo fino alla fine. Non l'hai fatto
Adelin,

9

Questo è accettabile, se l'ora del client è più preziosa dell'ora sul server.

Se il cliente deve essere veloce e preciso. È possibile giustificare l'interrogazione di più server. Ed è bello annullare la richiesta se si riceve una risposta valida.

E ovviamente è sempre consigliabile consultare i proprietari / gestori dei server.


Perché è necessario annullare la richiesta? Sicuramente è soggettivo.
JᴀʏMᴇᴇ,

@ JᴀʏMᴇᴇ, che è costruito in paranoia. Una volta ho lavorato con un sistema che non ha cancellato la coda e si è arrestato in modo anomalo quando la coda era piena (Sì, era un software professionale).
Toon Krijthe

4

Questa tecnica può ridurre la latenza. Il tempo di risposta del server non è deterministico. Su larga scala è probabile che ci sarà almeno un server che mostra tempi di risposta scadenti. Pertanto, qualsiasi cosa utilizzi quel server avrà tempi di risposta scadenti. Inviando a più server si riduce il rischio di parlare con un server con prestazioni scarse.

I costi includono traffici di rete aggiuntivi, elaborazione server sprecata e complessità dell'applicazione (pensato che questo possa essere nascosto in una libreria). Questi costi possono essere ridotti annullando le richieste non utilizzate o aspettando brevemente prima di inviare una seconda richiesta.

Ecco una carta , e un altro . Ricordo di aver letto anche un articolo di Google sulla loro implementazione.


2

Concordo principalmente con le altre risposte, ma penso che questo dovrebbe essere estremamente raro nella pratica. Volevo condividere un esempio molto più comune e ragionevole per quando avresti usato Promise.race(), qualcosa per cui mi è capitato di usarlo per un paio di settimane fa (beh, l'equivalente di Python).

Supponi di avere un lungo elenco di attività, alcune che possono essere eseguite in parallelo e altre che devono essere eseguite prima di altre. È possibile avviare tutte le attività senza dipendenze, quindi attendere tale elenco con Promise.race(). Non appena viene completata la prima attività, è possibile avviare qualsiasi attività che dipendesse da quella prima e di Promise.race()nuovo dal nuovo elenco combinato con le attività non completate dall'elenco originale. Continua a ripetere fino a quando tutte le attività non sono state completate.

Nota l'API di Javascript non è progettata in modo ideale per questo. È praticamente il minimo indispensabile e devi aggiungere un po 'di codice colla. Tuttavia, il mio punto è che funzioni come race()raramente vengono utilizzate per la ridondanza. Sono principalmente lì quando vuoi davvero i risultati di tutte le promesse, ma non vuoi aspettare che vengano completati tutti prima di intraprendere azioni successive.


Il problema è che, almeno con Promise.race di Javascript, si avvia effettivamente l'attività ogni volta che si esegue il metodo race. Non si tratterà di un compito incompiuto, sarebbe un nuovo insieme di compiti, indipendentemente da ciò che è stato eseguito prima (a meno che non si implementi quella logica a livello di compito). L'elenco originale viene dimenticato altrimenti e rimane solo il valore di ritorno del primo compito
Adelin,

1
Le promesse in Javascript vengono avviate con entusiasmo, quando new Promiseviene chiamato, e non riavviate quando Promise.race()viene chiamato. Alcune implementazioni promettenti sono pigre, ma l'entusiasmo è molto più comune. È possibile verificare creando una promessa nella console che accede alla console. Lo vedrai registrare immediatamente. Quindi passa quella promessa a Promise.race(). Vedrai che non si registra più.
Karl Bielefeldt,

Ah questo è vero. Ma dopo aver dimenticato il valore di ritorno del resto delle promesse, tranne la prima, con promise.race
Adelin,

Ecco perché ho detto che l'API non è progettata idealmente. Devi archiviare il set originale di attività in una variabile da qualche parte.
Karl Bielefeldt,

1

Oltre alle considerazioni tecniche, potresti voler utilizzare questo approccio quando fa parte del tuo modello di business reale.

Le variazioni di questo approccio sono relativamente comuni nelle offerte in tempo reale sugli annunci. In questo modello un editore (provider di spazi pubblicitari) chiederà agli inserzionisti (provider di annunci) di fare offerte per un'impressione di un determinato utente. Pertanto, per ogni impressione del genere, dovrai interrogare ciascuno degli inserzionisti iscritti, inviando una query con i dettagli dell'impressione a un endpoint fornito da ciascun inserzionista (o, in alternativa, uno script fornito dall'inserzionista in esecuzione come endpoint sui tuoi server), correndo tutte queste richieste fino a un timeout (ad es. 100 ms) e quindi l'offerta più alta, ignorando le altre.

Una variante particolare di ciò che aiuta a ridurre i tempi di attesa del cliente è che l'editore consenta un valore obiettivo minimo per l'offerta, in modo tale che la prima offerta dell'inserzionista che supera tale valore venga immediatamente accettata (o, se nessuna delle offerte supera il valore, quello massimo sarà preso). Quindi in questa variante, la prima query in arrivo potrebbe vincere e l'altra scartata, anche se sono buone o addirittura migliori.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.