- Al reconstruir el sistema de comparación de planes de seguro de salud Medicare de EE. UU., comparte la experiencia de procesar de forma estable más de mil millones de solicitudes web con una estructura simple compuesta solo por tecnologías probadas (Postgres, golang, React, etc.)
- Diseñó la arquitectura con el objetivo de priorizar la simplicidad y la estabilidad, logrando una velocidad de respuesta promedio inferior a 10 ms y una tasa de fallas muy baja
- Aplicó la innovación (
innovation token) solo al mínimo necesario en la separación estructural clave (3 módulos grandes, comunicación por gRPC), y para todo lo demás eligió metodologías aburridas pero confiables
- Desde la gestión del esquema de la base de datos, el pipeline ETL, las pruebas, el logging, la documentación y las herramientas CLI, construyó todos los elementos operativos de forma repetible y simple para completar un sistema que todo el equipo puede entender y mantener fácilmente
- Muestra vívidamente un caso en el que la gestión continua de la calidad y un fuerte trabajo en equipo también funcionan en proyectos gubernamentales a gran escala
Serving a billion web requests with boring code
Panorama general
- Lideró durante dos años y medio el desarrollo de un sitio web del gobierno de EE. UU. para comparar y comprar planes de Medicare
- Procesó 5 millones de solicitudes API por día en promedio, con un tiempo de respuesta promedio inferior a 10 ms, y el 95% de las solicitudes por debajo de 100 ms
- La tasa de incidentes fue muy baja, al punto de que los casos reales en los que llamaron a un ingeniero de madrugada podían contarse con una mano
- Al usar solo tecnologías probadas y entendibles por cualquiera, como Postgres, golang y React, construyó un sistema estable de forma consistente
Boring über alles
- El principio prioritario fue anteponer siempre las "tecnologías aburridas y probadas" (Choose Boring Technology)
- Los intentos de innovación se reservaron solo para donde eran realmente necesarios, usando con cuidado los innovation token
- Se prefirieron tecnologías y procesos estables y claros por encima de soluciones complejas y vistosas
Las partes aburridas
- Postgres: la base del almacenamiento de datos; cumplió tanto en confiabilidad como en escalabilidad. Incluso las búsquedas complejas (como la búsqueda facetada) se resolvieron con Postgres
- golang: compilación y despliegue rápidos, con binarios claros. El manejo de errores es directo y los nuevos integrantes del equipo pueden adaptarse fácilmente
- React: era el framework SPA más probado y el equipo ya estaba familiarizado con él. La accesibilidad y el soporte para distintos dispositivos también fueron factores importantes
- A largo plazo surgieron problemas de tamaño del bundle y degradación de velocidad, pero en ese momento fue la mejor elección para entregar resultados a tiempo
Los innovation tokens
- Backend modular: todo el backend se organizó no como microservicios ni como monolito, sino como 3 módulos grandes (
druginfo, planinfo, beneinfo)
- Cada módulo usa su propia base de datos Postgres, y el intercambio de datos ocurre únicamente a través de gRPC
druginfo: indexa con mucha precisión información de precios de medicamentos, donde las combinaciones de farmacia, seguro, empaque, etc. crecen exponencialmente, y requiere preprocesamiento complejo y optimización de rendimiento
planinfo: recibe nuevos datos de CMS todos los días y vuelve a crear toda la base de datos para usarla así (manteniendo la inmutabilidad)
beneinfo: es la única parte que guarda información real de afiliados y almacena la mínima cantidad posible de PII sensible. Se puso especial atención al diseño y la operación para minimizar el riesgo de filtración de datos
- gRPC: ofrece la ventaja de definir claramente en código las interfaces de comunicación entre módulos. También se integra muy bien con herramientas de automatización
- Sin embargo, también se experimentaron desventajas: compilación, tooling y depuración más complejos, y menos intuición que una API JSON
- Mediante grpc-gateway se dio soporte al cliente web y se manejó tráfico masivo sin problemas
Compatibilidad estricta hacia atrás
- Se mantuvo de forma estricta la compatibilidad hacia atrás de la API y la base de datos
- Los campos de la API pública nunca se eliminan y, salvo que haya un problema de seguridad, se mantienen para siempre
- En las columnas de la BD se pueden agregar libremente, pero para eliminarlas se sigue un proceso de varias etapas (quitar referencias → esperar varias semanas → eliminación real)
- Esta disciplina fue la base clave para una alta velocidad de cambio y un despliegue y operación estables
Búsqueda facetada
- Implementación de búsqueda facetada usando solo Postgres en lugar de ElasticSearch
- Toda la lógica de búsqueda se resolvió con una sola función de 250 líneas que combina condiciones sobre una tabla
plan bien indexada
- Se enfocó en los requisitos del negocio y se resolvió de forma simple, sin complejidad innecesaria
Base de datos
-
creación
- El esquema de la BD se gestionó con archivos
.sql numerados, cargados en orden para garantizar confiabilidad
- Las BD de
planinfo y beneinfo se recrean todos los días, por lo que no se requieren migraciones. Además, se diseñó para que la app ni siquiera arranque si hay errores de configuración como desajustes de versión
-
ETL
- Los datos se cargan a S3 mediante scripts de shell por fuente de datos → por
cron, una instancia EC2 obtiene el código/datos ETL más recientes y crea una nueva BD en RDS
- Se aprovechó activamente la instrucción COPY de Postgres para manejar la carga masiva de datos de forma eficiente en lugar de
INSERT
- En 2 a 4 horas al día era posible cambiar a una nueva BD con cientos de millones de filas
-
modelos
- Se usó la librería xo para generar automáticamente modelos de BD, con templates personalizados para producir código adaptado al equipo
-
pruebas
- El mayor error fue crear demasiadas pruebas usando
sqlmock, lo que volvió muy engorroso el mantenimiento en un contexto donde los datos cambiaban con frecuencia
- Si la BD real era inmutable, habría sido más eficiente probar directamente contra una BD real
-
Base de datos local para desarrollo
- Con scripts que generan automáticamente subconjuntos de datos de cada tabla, cada desarrollador podía probar y desarrollar con una pequeña BD local basada en datos reales
- Preparar este tipo de herramientas antes de que la BD crezca maximiza enormemente la eficiencia de desarrollo de todo el equipo
Herramientas varias
- Para diversas automatizaciones operativas y de observabilidad, se implementaron herramientas CLI con scripts de shell, reuniendo y administrando todas las utilidades en un solo lugar
- Se desarrollaron y usaron activamente herramientas orientadas a la operación real, como visualizar de inmediato gráficos de logs de Splunk mediante comandos de Slack
Logging
- Al ingresar una solicitud, se genera un request id, y ese id se adjunta a todo el contexto de logs para poder rastrearlo desde cualquier lugar
- Se diseñó un sistema de logging seguro y estructurado con zerolog
- Se dejan logs obligatorios en momentos importantes, como la entrada y salida del sistema o situaciones excepcionales
Documentación
- Los documentos Markdown de GitHub se convirtieron con sphinx-book-theme para operar un wikibook
- Todo el equipo contribuyó activamente a la documentación, de modo que toda la documentación del sistema pudiera encontrarse en un solo lugar
- Una excelente cultura de documentación mejoró enormemente el crecimiento del equipo, el mantenimiento y la eficiencia del onboarding de nuevos integrantes
Integraciones en tiempo de ejecución
- Las solicitudes del cliente que degradaban el rendimiento (como insertar scripts de analítica) se minimizaron en lo posible mediante persuasión
- También se movieron consultas del runtime del navegador al build time para mantener el rendimiento del servicio
- En la práctica no fue posible bloquear todas las solicitudes del cliente, y algunas sí derivaron en degradación de rendimiento
Y más
- Más allá de la tecnología, se enfatiza que un ambiente de equipo positivo y colaborativo y una fuerte motivación fueron el verdadero motor del éxito de un sistema a gran escala
- Fue un caso que permitió sentir de primera mano el poder de decisiones prácticas pequeñas pero importantes y de una gestión constante de la calidad
18 comentarios
¿¿¿2.5 años de esto tan aburrido?!?
Tenía curiosidad por saber qué es la búsqueda facetada, así que seguí investigando y hay más cosas que vale la pena leer.
https://www.cybertec-postgresql.com/en/faceting-large-result-sets/
https://roaringbitmap.org/about/
https://github.com/cybertec-postgresql/pgfaceting
Las opiniones sobre "aburrido" están interesantes jaja. Si lo cambiáramos por otra palabra, ¿cuál quedaría bien? ¿Predecible, común?
Traducir
boringcomo «aburrido» realmente no transmite bien su significado original.boringnesses una de las filosofías de diseño de Go.Haciéndose el muy aburrido…
En Corea todo termina siendo JavaLandia, así que se les hace raro, jaja
Creo que tanto golang como React son lenguajes de programación empresariales “boring” de una nueva era.
Como
boring -> 지루한no se puede traducir al 100% de forma correcta, parece que a los lectores coreanos no les está llegando bien el matiz.Quiero vivir en el mundo aburrido de Postgres, Golang y React.
Sí, la verdad es que al ver el título pensé que era una broma.
Parece que en el extranjero eso se considera una stack aburrida.
En realidad, Go es simplemente la opción más fácil para crear un servidor web...
Parece que piensan que para que no sea aburrido hay que desarrollar con algo como Rust o lenguajes del lado de FP.
Historias demasiado obvias... tan obvias que estamos pasando por alto cosas importantes...
Esta pila no parece tan aburrida, al menos hasta este nivel. Si de verdad fuera aburrida, me da la irreverente impresión de que al menos tendría que aparecer algo como Java 1.8 o una versión anterior, o quizá VB...
>Lo bueno de lo aburrido (tan limitado) es que las capacidades de estas cosas se entienden bien. Pero, más importante aún, sus modos de falla se entienden bien.
En el texto original hay un enlace relacionado con
boring, y por el contenido parece queboringsignifica = demasiado familiar.Hay palabras más adecuadas como
experienced,verifiedoskillful, pero parece que usóboringa propósito para llamar la atención.¿No será que lo describió como un stack clásico y aburrido no porque sea tedioso de escribir, sino porque se ha usado tantísimo que ya resulta aburrido?
Más o menos Linux kernel 2.6.29...
El simple hecho de que usaron gRPC... jajaja
A mí también me vino primero a la cabeza algo como: ¿que Go es aburrido?
Diría que algo como Classic ASP sí podría considerarse aburrido.