¿En qué condiciones (si las hay) es una buena práctica consultar dos servidores y solo consumir la respuesta más rápida?

12

Le pregunté qué es ahora una pregunta eliminada por comunidad en SO sobre por qué alguien usaría JavaScript Promise.race, y un usuario de alta reputación comentó esto:

Si tiene dos servicios que calculan algún valor, puede consultarlos en paralelo y usar el valor que se devuelva primero, en lugar de consultar uno, esperar un error y luego consultar el segundo.

Busqué en Google la redundancia y este caso de uso en general, pero no pude encontrar nada y, desde mi punto de vista, nunca es una buena idea agregar carga de trabajo a un servidor / servicio si no vas a usar la respuesta.

Adelin
fuente
Ejemplo de juguete: en lugar de usar siempre la clasificación rápida, copia los datos, los envía a una clasificación rápida, una combinación, una pila, ... etc. No tiene que inspeccionar la entrada para ver si se trata de un caso patológico. para cualquiera de ellos, porque no será un caso patológico para todos ellos
Caleth
El artículo de Dean y Barroso, The Tail at Scale, llama a una variación de este enfoque "Solicitudes cubiertas". También analiza los pros y los contras de varios enfoques relacionados para controlar la variabilidad de cola larga en las tasas de error y la latencia.
Daniel Pryden
La segunda "solicitud del servidor" podría ser falsa. Podría querer solo 5 segundos y luego devolver una respuesta de marcador de posición. Eso te da un tiempo de espera en la solicitud real.
user253751
Pide un Lyft y luego un Uber. Tome lo que ocurra primero.
user2023861
@ user2023861 en esta analogía, mientras un conductor conducía sin sentido hacia su ubicación, él / ella podría haber aceptado otra solicitud en su lugar
Adelin

Respuestas:

11

Yo diría que esto es más una cuestión económica. Sin embargo, ese es un juicio que los ingenieros deberían poder hacer. Por lo tanto, estoy respondiendo.

Estoy dividiendo mi respuesta en cuatro partes:

  • Gestión de riesgos
  • Estrategias
  • Costos
  • Intuición

Gestión de riesgos

Entonces, a veces su cliente no puede obtener una respuesta del servidor. Asumiré que esto no se debe a un error programático (de lo contrario, la solución es solucionarlo, así que hazlo). En cambio, debe ser debido a una situación fortuita más allá de su control ...

Pero no más allá de tu conocimiento. Debes saber:

  • ¿Con qué frecuencia sucede?
  • ¿Qué impacto tiene?

Por ejemplo, si falla y vuelve a intentarlo solo aproximadamente el 2% del tiempo, probablemente no valga la pena abordarlo. Si ocurre alrededor del 80% del tiempo, bueno ... depende ...

¿Cuánto tiempo tiene que esperar el cliente? ¿Y cómo se traduce eso en costos? Verá, tiene una pequeña demora en una aplicación normal, probablemente no sea gran cosa. Si es significativo, y tiene una aplicación en tiempo real o un videojuego en línea, esto alejará a los usuarios y probablemente sea mejor que invierta en más o mejores servidores. De lo contrario, probablemente pueda poner un mensaje de "cargando" o "esperando al servidor". A menos que la demora sea realmente grande (del orden de decenas de segundos), puede ser demasiado incluso para la aplicación normal.


Estrategias

Como dije anteriormente, hay más de una forma de solucionar este problema. Asumiré que ya tiene implementado el bucle try-fail-retry. Entonces, veamos ...

  • Pon un mensaje de carga. Es barato, ayuda a la retención del usuario.
  • Consulta en paralelo. Puede ser más rápido, aún puede fallar. Requerirá un servidor redundante (puede ser costoso), desperdiciará el tiempo del servidor y el tráfico de red.
  • Consulte en paralelo para establecer el servidor más rápido y usarlo a partir de ahí. Puede ser más rápido, aún puede fallar. Requerirá un servidor redundante (puede ser costoso), no desperdiciará tanto tiempo de servidor y tráfico de red.

Ahora, note que digo que aún pueden fallar. Si suponemos que una consulta a un servidor tiene un 80% de posibilidades de falla, entonces una consulta paralela a dos servidores tiene un 64% de posibilidades de falla. Por lo tanto, es posible que deba volver a intentarlo.

Una ventaja adicional de elegir el servidor más rápido y seguir usándolo es que el servidor más rápido también tiene menos probabilidades de fallar debido a problemas de red.

Lo que me recuerda, si puede descubrir por qué falla la solicitud, hágalo. Puede ayudarlo a manejar mejor la situación, incluso si no puede evitar las fallas. Por ejemplo, ¿necesita más velocidad de transferencia en el lado del servidor?

Algo mas:

  • Implemente varios servidores en todo el mundo y elija el servidor por geolocalización.
  • Haga el equilibrio de carga en el lado del servidor (una máquina dedicada tomará todas las solicitudes y las reenviará a sus servidores, puede tener su paralelismo allí o una mejor estrategia de equilibrio).

¿Y quién dijo que tienes que hacer solo uno de estos? Puede poner un mensaje de carga, consultar varios servidores que se extienden por todo el mundo para elegir el más rápido y solo usarlo a partir de ahí, en caso de error, vuelva a intentarlo en un bucle y haga que cada uno de esos servidores sea un grupo de máquinas con equilibrio de carga . Por qué no? Bueno, cuesta ...


Costos

Hay cuatro costos:

  • El costo de desarrollo (generalmente muy barato)
  • El costo de implementación (generalmente alto)
  • El tiempo de ejecución de costos (depende del tipo de aplicación y el modelo de negocio)
  • El costo de la falla (probablemente bajo, pero no necesariamente)

Tienes que equilibrarlos.

Por ejemplo, digamos que gana aproximadamente un dólar por usuario satisfecho. Que tienes 3000 usuarios por día. Que las solicitudes fallan aproximadamente el 50% del tiempo. Y ese 2% de los usuarios se van sin pagar cuando la solicitud falla. Esto significa que está perdiendo (3000 * 50% * 2%) 30 dólares por día. Ahora, digamos que desarrollar la nueva característica le costará 100 dólares y la implementación de los servidores le costará 800 dólares, e ignorando los costos de tiempo de ejecución, esto significa que tendría un retorno de la inversión en ((100 + 800) / 30 ) 30 dias. Ahora, puede consultar su presupuesto y decidir.

No considere estos valores representativos de la realidad, los elegí por conveniencia matemática.

Addendums:

  • Recuerda que también estoy ignorando los detalles. Por ejemplo, puede tener un bajo costo de implementación, pero está pagando por el tiempo de CPU y debe tenerlo en cuenta.
  • Algunos clientes pueden apreciar si no desperdicia su paquete de datos en solicitudes redundantes.
  • Mejorar su producto puede ayudar a traer publicidad natural.
  • No olvide los costos de oportunidad. ¿Deberías estar desarrollando algo más?

La cuestión es que si considera el problema en términos de equilibrar costos, puede hacer una estimación del costo de las estrategias que considera y utilizar este análisis para decidir.


Intuición

Intuición si se fomenta por experiencia. No sugiero hacer este tipo de análisis cada vez. Algunas personas lo hacen, y eso está bien. Le sugiero que comprenda algo de esto y desarrolle una intuición.

Además, en ingeniería, además del conocimiento que obtenemos de la ciencia real, también aprendemos en la práctica y compilamos pautas de lo que funciona y lo que no. Por lo tanto, a menudo es aconsejable ver cuál es el estado del arte ... aunque, a veces, necesita ver fuera de su área.

En este caso, miraría los videojuegos en línea. Tienen pantallas de carga, tienen múltiples servidores, elegirán un servidor en función de la latencia e incluso pueden permitir que el usuario cambie de servidor. Sabemos que funciona.

Sugeriría hacer eso en lugar de perder el tráfico de red y el tiempo del servidor en cada solicitud, también tenga en cuenta que incluso con un servidor redundante, puede ocurrir una falla.

Theraot
fuente
2
No creo que necesite decirlo, pero esta es una gran respuesta :) Sabía que lo aceptaría en las primeras 10 líneas, pero te di la oportunidad de fallar y leerlo hasta el final. No lo hiciste
Adelin
9

Esto es aceptable si el tiempo del cliente es más valioso que el tiempo en el servidor.

Si el cliente necesita ser rápido y preciso. Puede justificar la consulta de varios servidores. Y es bueno cancelar la solicitud si se recibe una respuesta válida.

Y, por supuesto, siempre es aconsejable consultar a los propietarios / gerentes de los servidores.

Toon Krijthe
fuente
¿Por qué necesita cancelar la solicitud? Seguramente eso es subjetivo.
JᴀʏMᴇᴇ
@ JᴀʏMᴇᴇ, eso es construir en paranoia. Una vez trabajé con un sistema que no borró su cola y se bloqueó cuando la cola estaba llena (Sí, era un software profesional).
Toon Krijthe
4

Esta técnica puede reducir la latencia. El tiempo de respuesta del servidor no es determinista. A escala, es probable que haya al menos un servidor que muestre tiempos de respuesta deficientes. Cualquier cosa que use ese servidor, por lo tanto, también tendrá malos tiempos de respuesta. Al enviar a varios servidores, se mitiga el riesgo de hablar con un servidor de bajo rendimiento.

Los costos incluyen tráficos de red adicionales, procesamiento de servidor desperdiciado y complejidad de la aplicación (aunque esto se puede ocultar en una biblioteca). Estos costos pueden reducirse cancelando solicitudes no utilizadas o esperando brevemente antes de enviar una segunda solicitud.

He aquí un artículo , y otro . Recuerdo haber leído un artículo de Google sobre su implementación también.

Michael Green
fuente
2

Principalmente estoy de acuerdo con las otras respuestas, pero creo que esto debería ser extremadamente raro en la práctica. Quería compartir un ejemplo mucho más común y razonable de cuándo lo usarías Promise.race(), algo que usé hace un par de semanas (bueno, el equivalente de Python).

Supongamos que tiene una larga lista de tareas, algunas que pueden ejecutarse en paralelo y otras que deben ejecutarse antes que otras. Puede iniciar todas las tareas sin dependencias, luego esperar en esa lista con Promise.race(). Tan pronto como se complete la primera tarea, puede comenzar cualquier tarea que dependiera de esa primera tarea, y Promise.race()nuevamente en la nueva lista combinada con tareas no finalizadas de la lista original. Sigue repitiendo hasta que todas las tareas estén terminadas.

Tenga en cuenta que la API de Javascript no está idealmente diseñada para esto. Es prácticamente lo mínimo que funciona, y tienes que agregar un poco de código de pegamento. Sin embargo, mi punto es que funciones como race()rara vez se usan para la redundancia. Están principalmente allí para cuando realmente quieres los resultados de todas las promesas, pero no quieres esperar a que se completen antes de tomar acciones posteriores.

Karl Bielefeldt
fuente
El problema es que, al menos con Promise.race de Javascript, realmente comienzas la tarea cada vez que ejecutas el método de carrera. No estará en la tarea inacabada, sería un nuevo conjunto de tareas, sin tener en cuenta lo que se ejecutó antes (a menos que implemente esa lógica en el nivel de tarea). De lo contrario, se olvida la lista original, y solo queda el valor de retorno de la primera tarea
Adelin
1
Las promesas en Javascript se inician con entusiasmo, cuando new Promisese llama, y ​​no se reinician cuando Promise.race()se llama. Algunas implementaciones prometedoras son flojas, pero las ansiosas son mucho más comunes. Puede probar creando una promesa en la consola que inicie sesión en la consola. Verás registros inmediatamente. Entonces pasa esa promesa a Promise.race(). Verás que no se registra nuevamente.
Karl Bielefeldt
Ah eso es verdad. Pero afaik el valor de retorno del resto de las promesas, excepto la primera, se olvida, con promesa
Adelin
Es por eso que dije que la API no está diseñada de manera ideal. Debe almacenar el conjunto original de tareas en una variable en algún lugar.
Karl Bielefeldt
1

Además de las consideraciones técnicas, es posible que desee utilizar este enfoque cuando sea parte de su modelo comercial real.

Las variaciones en este enfoque son relativamente comunes en las ofertas en tiempo real de los anuncios. En este modelo, un editor (proveedor de espacio publicitario) solicitará a los anunciantes (proveedores de anuncios) que oferten por una impresión de un usuario en particular. Entonces, para cada impresión, consultaría a cada uno de los anunciantes suscritos, enviando una consulta con los detalles de la impresión a un punto final proporcionado por cada anunciante (o alternativamente, un script proporcionado por el anunciante que se ejecuta como punto final en sus propios servidores), compitiendo todas estas solicitudes hasta un tiempo de espera (por ejemplo, 100 ms) y luego tomar la oferta más alta, ignorando las demás.

Una variación particular de esto que ayuda a reducir el tiempo de espera del cliente es que el editor permita un valor objetivo mínimo para la oferta, de modo que la primera oferta del anunciante que supere ese valor sea aceptada de inmediato (o, si ninguna de las ofertas supera el valor, se tomará el máximo). Entonces, en esta variación, la primera consulta que llega podría ganar y la otra descartada, incluso si son tan buenas o incluso mejores.

yoniLavi
fuente