Cajitas que piensan

Designing Data-Intensive Applications : Chapter 8

The internet and most internal networks in datacenters (often Ethernet) are asynchronous packet networks. In this kind of network, one node can send a message (a packet) to another node, but the network gives no guarantees as to when it will arrive, or whether it will arrive at all. If you send a request and expect a response, many things could go wrong

Dado esto, los problemas que podemos tener son varios:

  • Los paquetes de la solicitud o la respuesta
    • Se pierden
    • Se encolan para procesar luego
  • El destinatario puede estar apagado o caído.
  • El destinatario puede no estar respondiendo (por un stop-the-world tal vez) por un breve periodo de tiempo.

El problema es que es imposible poder determinar cual de estas cosas pasaron en un contexto asincronico:

Network problems

Fallas de red en la vida real

En un estudio de VMware comprobaron que en un datacenter mediano (1000 elementos conectados en la red por ejemplo), ocurren 12 particiones de la red al mes, de estas la mitad desconecta un rack entero y la otra mitad solo un servidor. En los casos de datacenters con menos de 600 elementos, las fallas afectan a mas de un rack.

Incluso podría pasar que alguien intente morder un cable transoceánico, atraído por el campo electromagnético generado en el cable:

[YouTube 4WlOnlRncK0]

Incluso aunque nada de esto sucediera, los sistemas tienen que estar preparados a qué pueda suceder. Puede ser conveniente generar problemas de red para testear como responden los sistemas ante estos, este es el concepto detrás de Chaos Monkey de Netflix o ToxiProxy de Shopify.

Detectando fallas

Algunos sistemas tienen que detectar las fallas de red:

  • Un balanceador de carga debe saber cuándo dejar de enviar trafico a un nodo.
  • En una base de datos distribuida que funcione con Líder y Seguidores, si el líder esta caído, los seguidores tienen que detectarlo y promover un nuevo líder.

Si bien es difícil saber a través de la red ante que escenario nos encontramos o si un nodo esta funcionando o no, hay algunos indicios:

  • Si podemos llegar a una maquina donde esta corriendo un nodo de la aplicación, pero ningún proceso escucha en el puerto de destino (porque el proceso esta muerto), el sistema operativo va a rechazar conexiones TCP enviando paquetes de RST o FIN en la respuesta.
  • Si el proceso falla, pero el sistema operativo esta funcionando, un script podría notificar a los otros nodos de la caída del proceso para que tomen acciones sin esperar el timeout (HBase hace esto)
  • Si se tiene acceso a la interfaz de gestión de los switches de la red, se puede consultar por fallas a nivel de hardware (maquinas apagadas por ejemplo). Es difícil poder hacer esto salvo en casos de datacenter propio.
  • Si el router esta seguro que la IP de destino no esta disponible, puede responder con un paquete de ICMP Destination Unreachable.

Si bien la detección temprana de nodos caídos es util, no podemos asegurarla. El protocolo TCP no asegura que un paquete entregado fue procesado por la aplicación de destino, solo la respuesta de la aplicación puede darnos eso.

Timeouts y Retrasos sin limite

Si la única manera certera de dar a un nodo por muerto que tenemos es el timeout, de cuánto debe ser este?

  • Un valor alto significa que vamos a demorar más en declarar un nodo como caído. Esto afectara durante más tiempo a los clientes.
  • Un valor bajo va a detectar las fallas rápidamente, pero aumentara la tasa de falsos positivos, al declarar incorrectamente a un nodo por muerto cuando solo tuvo una pequeña demora (por ejemplo por un pico de carga en un nodo o en la red).

Si damos por caído a un nodo prematuramente, otro nodo deberá hacerse cargo de su tarea, generando que tengamos opción de que ambos la ejecuten, dando lugar a duplicaciones.

Además pasar la carga de un nodo al resto genera sobre carga en los nodos y en la red según el caso. Si el sistema ya esta con problemas de carga, esto puede aumentar el problema y llevar en casos extremos a que se caiga el cluster cuando todos los nodos declaren al resto como caído.

La variabilidad de los tiempos de respuesta en las redes es normalmente debido a encolamiento:

  • Si multiples nodos envian simultáneamente paquetes a un mismo destinatario, el switch de la red debe encolarlos y enviarlos de a uno a la conexión que tiene con el destinatario. En una conexión saturada un paquete puede demorar bastante en conseguir ser enviado, si además la cola del switch se llena entonces el paquete es descartado y deberá ser reenviado (a pesar de que la salud de la red es correcta).
  • Si al llegar a destino un paquete, todos los CPU del nodo están ocupados, entonces el sistema operativo encola la solicitud que recibe por la red hasta que la aplicación este disponible para atenderla.
  • En ambientes virtualizados, el sistema operativo es pausado unos milisegundos mientras otra virtual usa el CPU. En esta pausa, la virtual pausada no puede consumir información de la red, así que la información que llega sera encolada por el monitor de la virtual. En AWS esto genera demoras hasta 100 veces mayores que el tiempo de enviar el paquete en condiciones normales.
  • El protocolo TCP hace control del flujo, es decir que cada nodo limita la tasa de envío de paquetes para no sobrecargar al destinatario, esto implica encolamiento en el emisor.

TCP vs UDP

UDP no tiene tantos controles como TCP, no se hace control del flujo en el emisor ni se retransmiten paquetes perdidos. Esto permite quitar parte de los retrasos variables de la red (siguiendo con los que afectan a switches).

El tradeoff aquí es entre variabilidad de los delay y seguridad en que el mensaje llegue. Si la información que llega tarde es intrascendente, entonces UDP es ideal como protocolo. Esto sucede en audio o video donde si un paquete no llega al momento de ser emitido por los parlantes o el monitor, no tiene sentido que lo manden después.

Nube pública

En contextos donde los recursos son compartidos entre muchos clientes, se pueden empezar a ver impactos de un cliente a otro. Tareas batch como un MapReduce puede saturar la red con el flujo de datos que maneja, generando que si en el host físico tenemos virtuales con mucha carga, puedan afectar a las otras (noisy neighbor).

En estos casos la única manera de elegir los timeouts es experimentalmente, medir la distancia de un viaje ida y vuelta entre 2 host durante un periodo de tiempo, repetirlo en varias maquinas, para determinar la variabilidad esperada de las demoras.

Importante tener en cuenta este tradeoff entre deteccion temprana y timeout prematuro, según nuestro sistema puede ser que necesitemos mas de uno que de otro.

Algunos sistemas realizan esto periódicamente, midiendo tiempos de respuesta y su variabilidad (jitter se le llama a la variabilidad de los tiempos de respuesta), y ajustan automáticamente los timeout. Esto es parte de lo que hacia Akka y Cassandra con el detector de fallas Phi Accrual..