7 puntos por GN⁺ 2024-09-03 | 1 comentarios | Compartir por WhatsApp
  • La principal dificultad de los sistemas distribuidos no es tanto la latencia como las fallas parciales; incluso el manejo de excepciones que es simple en una sola máquina pasa a formar parte del diseño del protocolo cuando hay varias máquinas
  • Los sistemas distribuidos robustos tienen bugs que solo aparecen con varias máquinas reales, condiciones de red de un centro de datos y conjuntos de datos grandes, por lo que cuestan más que los sistemas de una sola máquina
  • Para crear un sistema operable hay que reducir la coordinación y aumentar la independencia, usando backpressure, disponibilidad parcial, métricas e indicadores por percentiles como herramientas básicas
  • Los feature flags, el diseño del espacio de IDs, la localidad de los datos y la extracción de servicios son técnicas prácticas para reducir el alcance de las fallas y el costo de coordinación durante migraciones y escalamiento
  • El teorema CAP es más una herramienta para criticar diseños que un punto de partida para construir sistemas, y en los sistemas distribuidos la coordinación social entre equipos y organizaciones es tan difícil como los problemas técnicos

Lo que es fácil pasar por alto al aprender sistemas distribuidos por primera vez

  • Muchas de las lecciones de los ingenieros de sistemas distribuidos vienen de errores sufridos con tráfico de producción y de cicatrices operativas, pero los ingenieros principiantes necesitan un contexto más directo y accionable
  • Fallacies of Distributed Computing y el teorema CAP ayudan en el autoaprendizaje, pero son abstractos para que un ingeniero con poca experiencia pueda actuar de inmediato
  • Los sistemas distribuidos requieren más máquinas y capital, por lo que tienden a involucrar a más equipos y organizaciones más grandes
    • Los problemas sociales suelen ser la parte más difícil del trabajo de un desarrollador de software, y pueden ser especialmente notorios en el desarrollo de sistemas distribuidos
    • A veces las soluciones sociales son más eficientes y satisfactorias que las técnicas, pero la formación, educación y experiencia de los ingenieros los inclinan hacia soluciones técnicas

Diseñar asumiendo fallas y costos

  • Los sistemas distribuidos fallan con frecuencia y, peor aún, fallan parcialmente
    • Si falla el unlock de un mutex en un solo proceso, quizá baste con hacer crash del proceso; si falla el unlock de un mutex distribuido, eso debe formar parte del protocolo de locking
    • Enfoques como “basta con enviar el write a ambas máquinas” o “reintentar hasta que tenga éxito” no manejan adecuadamente el caso en que solo un write tiene éxito y el otro falla
    • Pueden ocurrir fallas de switches, desaparición del líder por una pausa de GC, fallas remotas de un socket write que parecía exitoso, o que un solo disco lento retrase todo el protocolo de comunicación del clúster
  • Una solución distribuida robusta cuesta más que una solución de una sola máquina
    • Hay fallas que solo ocurren con varias máquinas; las VM y la nube reducen costos, pero no son tan baratas como diseñar, implementar y probar en una sola computadora que ya se tiene
    • Las fallas que solo aparecen con tamaños de datasets difíciles de alojar en una máquina compartida, o bajo condiciones de red de un centro de datos, requieren un entorno distribuido real
    • La simulación es útil, pero no sustituye todos los bugs de los sistemas distribuidos
  • Los sistemas distribuidos open source robustos son mucho más raros que los sistemas robustos de una sola máquina
    • El costo de ejecutar muchas máquinas durante mucho tiempo es una carga para la comunidad open source
    • Los desarrolladores aficionados y diletantes crean open source con las máquinas que ya tienen y su tiempo libre, por lo que es difícil encontrar desarrolladores que levanten, mantengan y paguen varias máquinas
    • Los ingenieros corporativos cubren parte del vacío, pero las prioridades de su organización pueden no coincidir con las de la organización del usuario

Reducir la coordinación y absorber fallas

  • La coordinación entre máquinas debe evitarse siempre que sea posible
    • La clave de la escalabilidad horizontal es ubicar los datos de forma lo bastante independiente como para minimizar la comunicación y el consenso entre máquinas
    • Cada vez que dos máquinas deben ponerse de acuerdo sobre algo, implementar el servicio se vuelve más difícil
    • Hay un límite superior a la velocidad de transmisión de la información, la comunicación de red es más inestable de lo que parece y nuestras intuiciones sobre el consenso pueden estar equivocadas
    • Entender Two Generals, Byzantine Generals y la dificultad de implementar Paxos ayuda
  • Cuando el problema cabe en memoria, para un ingeniero de sistemas distribuidos suele estar cerca de ser un problema trivial
    • Procesar rápido es más difícil cuando los datos no están a unos cuantos punteros, sino al otro lado de varios switches
    • Hay muchos algoritmos e implementaciones para una sola máquina, pero mucha menos literatura e implementaciones para sistemas distribuidos
  • “Es lento” es el problema más difícil de depurar
    • Puede que uno o más de los múltiples sistemas que participan en una solicitud de usuario estén lentos, o que alguna parte de una pipeline de transformaciones que atraviesa varias máquinas esté lenta
    • La propia descripción del problema da muy pocas pistas sobre dónde está la falla, y puede haber fallas parciales ocultas que no aparecen en los gráficos que se miran normalmente
    • Hasta que la degradación de rendimiento sea muy clara, es difícil obtener suficientes recursos como tiempo, dinero y herramientas
    • Dapper y Zipkin se crearon por esta razón
  • Hay que implementar backpressure en todo el sistema
    • Backpressure es una forma en que el sistema que presta el servicio señala una falla al sistema que hace la solicitud, y este maneja esa falla para evitar la sobrecarga propia y la del otro sistema
    • Limitar el uso de recursos durante sobrecargas o fallas del sistema es un componente básico de un sistema distribuido robusto
    • La implementación suele consistir en descartar mensajes nuevos cuando los recursos están limitados o se produce una falla, o en devolver un error al usuario; en ambos casos deben incrementarse métricas
    • Los timeouts y el exponential backoff son indispensables en las conexiones y solicitudes a otros sistemas
    • Sin backpressure, es fácil que ocurran fallas en cascada o pérdidas de mensajes no intencionales
  • Hay que encontrar formas de ofrecer disponibilidad parcial
    • La disponibilidad parcial es la capacidad de devolver algunos resultados aunque falle una parte del sistema
    • Un sistema de búsqueda hace compromisos entre la calidad de los resultados y el tiempo que hace esperar al usuario, y puede devolver solo los resultados reunidos cuando vence el límite de tiempo
    • En mensajería privada, puede ser mejor que la falla afecte solo a algunos usuarios que hacer que algunos mensajes desaparezcan para todos los usuarios
    • Hay que decidir cuánto separar los dominios de falla para que una falla de mensajería privada no afecte una funcionalidad no relacionada, como subir imágenes públicas

Usar observabilidad, capacidad y despliegues como herramientas operativas

  • Las métricas son un medio indispensable para saber qué hace realmente el sistema en producción
    • Métricas como percentiles de latencia, contadores incrementales de acciones específicas y tasas de cambio reducen la brecha entre el comportamiento que se cree que tiene el sistema y el comportamiento real
    • Saber cómo difiere el comportamiento del sistema en el día 20 respecto del día 15 separa la ingeniería exitosa de la brujería fallida
    • Las métricas son necesarias para entender problemas y comportamientos, pero no bastan para decir qué hacer después
  • Los archivos de log son útiles, pero tienden a mentir
    • Aunque algunas clases de errores ocupen gran parte del espacio del archivo de log, su proporción real de solicitudes puede ser baja
    • Los logs de éxito suelen ser redundantes y pueden reventar el disco, y los ingenieros a menudo adivinan mal qué logs de error serán útiles
    • Conviene escribir logs asumiendo que los leerá alguien que nunca vio el código
    • Si se sobrestima algo extraño visto en los logs sin confirmarlo con métricas, la interrupción puede prolongarse
  • Hay que usar percentiles en vez de promedios
    • Los percentiles 50, 99, 99.9 y 99.99 son más precisos y útiles que el promedio en la mayoría de los sistemas distribuidos
    • El promedio asume que la métrica evaluada sigue una distribución con forma de campana, pero muy pocas de las métricas importantes para los ingenieros son así
    • Si la latencia de un sistema distribuido no sigue una distribución con forma de campana, la latencia promedio lleva a decisiones y entendimientos equivocados
  • Hay que aprender a estimar capacidad
    • Saber cuántas máquinas requiere una tarea separa un sistema duradero de uno que habrá que reemplazar en 3 meses
    • A fines de 2012, una máquina típica tenía 24GB de memoria; se necesitaban 4–5GB para el OS, al menos algunos GB para procesar solicitudes, y un tweet id ocupaba 8 bytes: se requieren cálculos aproximados de ese tipo
    • Numbers Everyone Should Know de Jeff Dean ayuda a calibrar expectativas
  • Los feature flags son una forma de hacer rollout de infraestructura
    • Los feature flags son potentes no solo para pruebas A/B en el frontend, sino también para reemplazos de infraestructura
    • El enfoque de “big cutover” ha hecho fracasar muchos proyectos cuando bugs descubiertos tarde obligaron a hacer rollback
    • Al pasar de una sola base de datos a un servicio que oculta un nuevo almacenamiento, se puede aumentar lentamente el envío paralelo de writes al nuevo servicio, probar reads sin usarlos para respuestas al usuario después de completar el backfill, comparar reads entre el sistema viejo y el nuevo, y luego aumentar gradualmente los reads reales
    • Si aparece un problema, se puede bajar el valor del flag o volverlo a 0 de inmediato, y ajustar la cantidad de tráfico para depurar y experimentar
    • Los feature flags son un trade-off que intercambia complejidad local en el código por simplicidad global y resiliencia
    • Hay que aceptar que tener varias versiones de infraestructura y de datos es lo normal, no una excepción

Diseño del modelo de datos y de los límites

  • El espacio de IDs determina la forma del sistema
    • Cuantos más IDs se necesiten para llegar a un dato, más opciones habrá para particionar los datos
    • Cuantos menos IDs se necesiten para un dato, más fácil será consumir la salida del sistema
    • Twitter API v1 consultaba, creaba y eliminaba tweets con un único ID numérico de 64 bits, y ese ID no estaba vinculado con otros datos
    • A medida que aumenta la cantidad de tweets, se pueden almacenar los tweets de un mismo usuario en la misma máquina para construir eficientemente el timeline de tweets del usuario y el timeline de suscripciones; pero como la API pública hizo que todos los tweets fueran direccionables solo por tweet id, se necesita un servicio de consulta que sepa qué user posee qué tweet id
    • Como alternativa, se puede exigir un user id para consultar un tweet, o incluir el user id dentro del propio tweet id, pero esto último tiene el costo de que el tweet id deja de ser numérico k-sortable
    • La estructura de los IDs puede afectar la desanonimización de datos privados, el crawling inesperado, los problemas de IDs autoincrementales y ataques como Insecure Direct Object References
  • Hay que aprovechar la localidad de los datos
    • Cuanto más cerca del almacenamiento persistente se ubiquen el procesamiento de datos y el caching, mayor será la eficiencia del procesamiento y más fácil será mantener la consistencia y velocidad del caché
    • La red tiene más fallas y latencia que una dereferencia de puntero o fread(3)
    • La localidad se aplica no solo al espacio, sino también al tiempo
    • Si varios usuarios hacen casi al mismo tiempo la misma solicitud costosa, se pueden combinar en una sola; si solicitudes de datos del mismo tipo ocurren cerca en el tiempo, pueden agruparse en una solicitud más grande
    • Este enfoque reduce el overhead de comunicación y facilita la gestión de fallas
  • Escribir de vuelta datos cacheados al almacenamiento persistente es malo
    • Este defecto puede aparecer especialmente en sistemas diseñados por primera vez por personas con poca experiencia en sistemas distribuidos
    • En implementaciones que hablan de “Russian-doll caching”, es muy probable encontrar bugs visibles
    • Un síntoma común es que información de usuario, por ejemplo screenname, email o hashed password, vuelva a valores anteriores
  • Las computadoras pueden hacer más trabajo de lo que uno cree
    • Incluso un servidor web liviano de fines de 2012 tenía más de 6 procesadores, 24GB de memoria y una enorme cantidad de espacio en disco
    • Incluso aplicaciones CRUD relativamente complejas en runtimes de lenguajes modernos pueden manejar miles de requests per second en una sola máquina, en cientos de milisegundos
    • En la mayoría de los casos, cientos de requests per second por máquina no es una cifra de la que presumir desde el punto de vista de capacidad operativa
    • Si se perfila la aplicación y se introducen optimizaciones basadas en mediciones, no es difícil obtener mayor rendimiento
  • El teorema CAP debe usarse como herramienta para criticar diseños, no como una receta para construir sistemas
    • El teorema CAP es demasiado general y el espacio de soluciones posibles es demasiado amplio como para servir como primer principio del que derivar un sistema que funcione
    • Revisar repetidamente las restricciones que CAP impone a los subsistemas dentro del diseño de un sistema puede llevar a un mejor diseño
    • De C, A y P, CA no se puede elegir
  • Extraer servicios aporta ventajas de encapsulación y despliegue
    • Aquí, servicio se refiere a un sistema distribuido que incluye lógica de mayor nivel que un sistema de almacenamiento y normalmente tiene una API de estilo request-response
    • Hay que evaluar continuamente si cierto código sería más fácil de cambiar estando en un servicio separado en lugar de dentro del sistema
    • Un servicio extraído ofrece encapsulación como una biblioteca, pero puede hacer que desplegar cambios sea más rápido y fácil que actualizar la biblioteca del sistema cliente
    • Los servicios pequeños tienen menos dependencias de código y operación, y los límites estrictos reducen los atajos que se permiten en una biblioteca
    • Cuando hay varios sistemas cliente, una biblioteca compartida exige coordinar el despliegue de cada cliente, y se vuelve más difícil si la corrupción de datos puede depender del orden de despliegue
    • Si los mantenedores de los sistemas cliente son distintos, las prioridades pueden no coincidir y aumenta el costo de coordinación social para obtener acuerdo sobre una actualización
    • Un caso de uso típico es ocultar una capa de almacenamiento en cambio detrás de un servicio con una API más conveniente y de menor superficie

1 comentarios

 
GN⁺ 2024-09-03
Opiniones en Hacker News
  • El artículo definitivamente debió haber tratado CALM (consistencia como monotonicidad lógica). Es mucho más fácil de entender que CAP, es un resultado más fundamental y permite que incluso personas con poca experiencia construyan sistemas distribuidos muy sólidos.
    Idempotencia, CRDT, WAL y Raft son todos casos especiales del principio CALM.
    https://arxiv.org/pdf/1901.01930

    • Ese paper salió 6 años después que este artículo.
    • Viendo el repositorio de bloom, parece bastante estancado; me pregunto si todavía siguen trabajando en él.
  • Hay algo que falta aquí y que me gusta como principio general: la entrega exactamente una vez es imposible.
    Como máximo una vez o al menos una vez sí son posibles, pero hay que elegir qué modo de falla vas a aceptar y diseñar en función de eso.

    • Aclaro: eso aplica entre dos sistemas distribuidos que no comparten el mismo dominio transaccional o que no son lógicamente monótonos.
      En una base de datos en clúster, es posible mover datos de una fila a otra, y eso podría interpretarse como que se entregó un mensaje.
      Si todo el sistema es idempotente, o si puedes tratar todo el sistema distribuido como una sola unidad que se puede revertir en conjunto, entonces puedes obtener entrega exactamente una vez. Es decir, no debe haber efectos secundarios sobre otros sistemas fuera del dominio.
      Ambos son una forma de monotonicidad lógica. La idempotencia es fácil de reconocer, y la transaccionalidad también se basa en monotonicidad mediante algoritmos como WAL y Raft.
      El artículo debió haber tratado CALM (consistencia como monotonicidad lógica). Es mucho más fácil de entender que CAP y es un resultado más fundamental.
      https://arxiv.org/pdf/1901.01930
    • A lo largo de mi carrera he visto a muchos ingenieros que no conocen este concepto al diseñar sistemas distribuidos, y de verdad no se puede enfatizar lo suficiente.
    • En un entorno con fallas de red de nivel arbitrario, ni siquiera es posible la entrega al menos una vez.
    • La parte importante de esta lección es “y, de hecho, tampoco la necesitas”.
    • Apache Flink ofrece garantías de exactamente una vez de extremo a extremo cuando se usa con fuentes y sumideros de datos que participan en su mecanismo de checkpoints.
      An Overview of End-to-End Exactly-Once Processing in Apache Flink (with Apache Kafka, too!) — https://flink.apache.org/2018/02/28/an-overview-of-end-to-en...
      Flink's Fault Tolerance Guarantees — https://nightlies.apache.org/flink/flink-docs-release-1.20/d...
  • Como corolario de “si puedes meter el problema en memoria, probablemente sea trivial”, lo que cabe en memoria es mucho más grande de lo que uno cree.
    Creía saber qué era una RAM grande, pero cambié de opinión cuando los principales proveedores cloud empezaron a ofrecer VMs de 12 TB para SAP HANA.
    El artículo también lo toca muy brevemente con “las computadoras pueden hacer más de lo que crees”, pero ahí solo habla de una máquina de 24 GB. Aunque hay que considerar que era 2012, incluso entonces seguramente había bastantes máquinas con 10 veces esa RAM.

    • Incluso ingenieros relativamente senior cometen este error con bastante frecuencia. Si tienes un SaaS donde los datos analíticos por cliente llegan como máximo a 100 GB, al final probablemente baste con Postgres particionado.
  • Comparto este documento con las personas más prometedoras con las que trabajo.
    Cuando trabajaba en Lookout, Jeff Hodges compartió este ensayo como una presentación, y al final agregó el corolario: “no finjas que la ingeniería no es política”.
    Quienes creen que el código habla por sí solo se están perdiendo un aspecto importante para influir en cómo se construye algo y para lograr resultados reales.
    Incluso 10 años después, no hay mucha gente que entienda con tanta concisión la intersección entre liderazgo de ingeniería y lo que normalmente considero competencias básicas de SRE/DevOps.

    • Me gustaría conocer otros buenos materiales para leer sobre este tema.
  • Discusiones anteriores:
    https://news.ycombinator.com/item?id=5055371
    346 points|jcdavis|12 years ago|42 comments
    https://news.ycombinator.com/item?id=12245909
    386 points|kiyanwang|8 years ago|133 comments

  • Es una gran lista, y me gusta su explicación práctica y realista. No hay buzzwords ni “microservicios”.
    Creo que buena parte de estos consejos también aplica a sistemas de una sola máquina. Puede haber muchos subcomponentes distribuidos en cierto grado, como IPC entre programas o coordinación de hilos dentro de un proceso.
    La idea de una memoria unificada en una sola máquina también es, hasta cierto punto, una mentira, aunque el hardware sí puede ofrecer mejores garantías que un entorno distribuido “real”.
    Buena parte de los consejos del artículo que comparan “distribuido” con “una sola máquina” también encajan bastante bien al comparar multihilo con monohilo.
    En otro eje, cuando creas un programa y lo distribuyes para que muchas personas lo ejecuten, eso también se vuelve una especie de situación “distribuida”. Existen distintas versiones en circulación, y hay que preocuparse por compatibilidad y actualizaciones, así que las feature flags mencionadas en el artículo también son relevantes.
    La distribución se parece más a un espectro que va desde una sola CPU, a múltiples CPU, a múltiples computadoras fuertemente conectadas, hasta múltiples computadoras distribuidas por todo el mundo, con muchos puntos y muchas dimensiones entre medio.

    • La expresión “sistema distribuido” no impone restricciones sobre la forma de despliegue. La característica esencial de la definición es simplemente que distintos flujos de control se comunican por red mediante paso de mensajes.
      Varios procesos en la misma máquina comunicándose por localhost también son un ejemplo muy conocido de sistema distribuido, y de hecho mucha gente aprende por primera vez sobre sistemas distribuidos ahí.
    • A menudo pienso en un universo vecino, tan cercano que da pena, donde AMD podría haber dado a cada chiplet su propio espacio de memoria.
      Si hubiera sido así, todos estaríamos escribiendo todo el código como hermosos programas MPI de memoria distribuida. También desaparecería el false sharing, y nos veríamos obligados a pensar explícitamente y a fondo en los patrones de comunicación.
  • En los últimos años tuve la oportunidad de trabajar brevemente con el autor de este artículo. Jeff fue una de las personas más perspicaces y positivas de las que he aprendido.
    Era sorprendentemente honesto sobre las dificultades que enfrentaba, y también muy accesible de una forma agradable para mentoría y consejos.

  • En “los sistemas distribuidos son diferentes porque fallan con frecuencia”, lo importante no es la simple tasa de fallas, sino la tasa de fallas en un sistema de múltiples nodos.
    Además, los “problemas de sistemas distribuidos” no aparecen solo en varios servidores conectados por red. Si tienes un conjunto de nodos relacionados entre sí, incluso archivos en discos conectados lógicamente o buffers de distintos dispositivos de entrada/salida pueden sufrir problemas similares.

    • Correcto. De hecho, este tipo de problemas puede aparecer, y aparece, en casi cualquier sistema de software que vaya más allá de un único proceso monohilo fijado en memoria.
      Algunos veteranos se burlan de la complejidad excesiva que surge al mitigar estos problemas y se quejan de que sería mucho más simple ejecutar el software en un solo servidor.
      En la práctica, eso casi nunca fue cierto ni siquiera en la época de AS/400 o VAXft, y mucho menos aplica al mundo más caótico de Unix multiusuario y multiproceso.
  • “Si puedes meter el problema en memoria, probablemente sea trivial” es un error común entre algunos ingenieros de sistemas distribuidos.
    No es trivial en absoluto; simplemente los problemas que hay que resolver se trasladan a un dominio complementario.
    Este error puede llevar fácilmente a situaciones en las que terminas usando un clúster de 100 máquinas para procesar algo que, bien optimizado, podría hacerse en una sola máquina.