Los sinsabores del software imaginario

Los otros días circuló un artículo del equipo de Prime Video1 en el que cuentan cómo, contrayendo un conjunto de microservicios a una aplicación monolítica, resolvieron un cuello de botella y redujeron los costos de infraestructura en un 90%. La noticia llamó la atención, por un lado porque se supone que todo el chiste de los microservicios era facilitar la escalabilidad de los sistemas y, por otro, porque venía justamente de Amazon que, cabe suponer, es el principal beneficiario del derroche en infraestructura. Los detractores de los microservicios, que estaban agazapados esperando su oportunidad, se apuraron a leer el artículo como una admisión de derrota y prueba de la inviabilidad del modelo2, mientras que sus defensores reaccionaron explicando que el problema no es el comunismo la Arquitectura Orientada a Servicios, sino sus implementaciones existentes3.

El de los microservicios es un caso más de tecnología que tiene su utilidad en un contexto determinado, que se pone de moda y se empieza a utilizar indiscriminadamente fuera de ese contexto y termina causando mucho más daño del que alivia4. Mi percepción es que la moda (el uso indiscriminado, por defecto) ya pasó hace unos años y que, a esta altura, ensañarse contra la tecnología es como pegarle a alguien que está en el suelo. Pero me interesa revisar el trasfondo de aquella moda, caso particular de una tendencia generalizada en la industria: el “preparacionismo” del software imaginario, que justifica la implementación de soluciones complicadas a problemas que no existen5.

∗ ∗ ∗

El estilo de arquitectura de microservicios no resuelve un problema técnico o de negocio sino uno organizacional. Es una manera, siguiendo las leyes de Conway6 y Brooks7, de desacoplar los componentes de un ecosistema para que cada uno pueda ser desarrollado y operado por equipos independientes, reduciendo la coordinación y la fricción típicos de los sistemas compartidos (y el caos alternativo del equipo único que crece indefinidamente).

Implementar microservicios implica una gran inversión inicial y un esfuerzo constante de mantenimiento: más componentes, más costos de infraestructura, más dificultad en ejecutar los servicios localmente, la necesidad de estandarizar la funcionalidad común (configuración, deployment, logging, monitoreo, seguridad), de implementar mecanismos para encontrar y comunicar los servicios y para garantizar la consistencia de los datos, etc. Quizás el mayor de los problemas, sobre el que no se insiste lo suficiente, es que los sistemas distribuidos son complicados8 y es un error grave hacer de cuenta que da igual llamar una función local que ejecutar un procedimiento remoto9.

Toda esa inversión solo tiene sentido en organizaciones grandes, cuando hay oportunidades concretas de asignarle servicios distintos (preferentemente pocos) a cada equipo y obtener el beneficio del desarrollo independiente. Sam Newman, autor del texto canónico sobre microservicios10, nos previene desde el principio:

Despite the drive in some quarters to make microservice architectures the default approach for software, I feel that because of the numerous challenges I’ve outlined, adopting them still requires careful thought. (…) For a small team, a microservice architecture can be difficult to justify because there is work required just to handle the deployment and management of the microservices themselves. Some people have described this as the “microservice tax.” When that investment benefits lots of people, it’s easier to justify. But if one person out of your five-person team is spending their time on these issues, that’s a lot of valuable time not being spent building your product.

Una de las razones por las que usar microservices por default podría parecer una buena idea, es que si el software y la organización crecen (y ¿quién no espera que su proyecto sea exitoso?), el trabajo de dividir y repartir el sistema ya estaría hecho. Pero Newman lo desmiente:

I do see a temptation for startups to go microservice first, the reasoning being, “If we’re really successful, we’ll need to scale!” The problem is that you don’t necessarily know if anyone is even going to want to use your new product. And even if you do become successful enough to require a highly scalable architecture, the thing you end up delivering to your users might be very different from what you started building in the first place. Uber initially focused on limos, and Flickr spun out of attempts to create a multiplayer online game. The process of finding product market fit means that you might end up with a very different product at the end than the one you thought you’d build when you started. (…) It’s much easier to move to microservices later, after you understand where the constraints are in your architecture and what your pain points are—then you can focus your energy on using microservices in the most sensible places.

Este preparacionismo es la naturaleza de lo que en otro post llamé software imaginario11, software que en una etapa temprana, cuando hay menos información disponible —pocos usuarios, un modelo de negocio poco claro—, se ajusta para un futuro que probablemente no llegue o que si llega lo hará de forma imprevisible. El compromiso con el proyecto se confunde con optimismo y justifica elecciones de tecnología —arquitecturas escalables, bases de datos distribuidas, lenguajes rústicos– que inducen un costo innecesario en el corto plazo e incluso pueden llegar a ser contraproducentes en el caso de que el software efectivamente necesite escalar. Es preferible optar por la simplicidad: tomar el camino que nos dé más libertad de movimiento en el futuro, y que nos traiga ese futuro lo antes posible.

La propuesta no es novedosa; en lo que hace al diseño de programas, en la industria decantó un sentido común que prefiere limitarse a lo necesario y que incluso asume la misión de reducir la complejidad en el proceso de desarrollo. Pero, por alguna razón, esa austeridad no alcanza a la elección de tecnologías ni al armado de infraestructura. Resabio, tal vez, de la época en que la arquitectura se concebía como la parte del sistema que hay que definir al principio y que no se puede cambiar.

O Acaso el fenómeno se pueda explicar sociológicamente: nos entrenamos como ingenieros y científicos para resolver problemas difíciles; un trabajo ejecutado con sencillez puede parecer poco meritorio o por debajo de nuestras capacidades, la simplicidad se toma por simpleza. A fin de cuentas, la sociedad contemporánea es una inmensa máquina de marketing y las soluciones rebuscadas nos permiten escribir papers y hablar en conferencias, las buzzwords le dan brillo a los currículums y hacen subir las acciones de startups con balances negativos. La austeridad es para los pobres.

∗ ∗ ∗

La mayor parte de mi carrera la dediqué, a pesar mío, a proyectos de software imaginario: sistemas que nunca llegaban a producción, productos fallidos, modernizaciones que no igualaban al sistema que se proponían reemplazar. Cuando finalmente conseguí un trabajo de software real —un proyecto de escala global con casi una década en producción y restricciones muy específicas de performance—, me sorprendió descubrir que no aplicaba ninguna de las herramientas y prácticas sofisticadas que había estudiado durante años. Las instancias se levantaban y morían sin coordinación, los datos se mantenían en memoria para evitar la necesidad y la latencia de un acceso a base de datos, la comunicación entre servicios se hacía escribiendo y leyendo archivos en S3, con bash y cron. La arquitectura equivalente que me imaginaba antes de empezar en ese trabajo, usando Kafka o RabbitMQ para mover los datos en tiempo real, no solo requería más código y más dificultad operacional sino que implicaba más gastos de infraestructura y daba menos garantías de disponibilidad.

Los problemas del software real no nos requerían lidiar con algoritmos complejos ni arquitecturas distribuidas sino reducir costos de infraestructura, automatizar tareas frecuentes, mejorar la observabilidad del sistema y estudiar sus modos de fallo para hacerlo más estable. Dedicábamos más tiempo a operar el sistema que a escribir código; partir nuestro majestuoso monolito hubiera multiplicado el trabajo sin agregar valor.

Esa experiencia personal parece coincidir con otras más notables, la de Basecamp12, Stack Overflow1314, Shopify15, la del emprendedor serial Pieter Levels16. Incluso en organizaciones grandes, en las que cabe suponer la necesidad de construir soluciones más complejas, la experiencia prescribe simplicidad. Esto concluía en 2007 James Hamilton, sobre los sistemas de escala global en Microsoft17:

  1. Expect failures. A component may crash or be stopped at any time. Dependent components might fail or be stopped at any time. There will be network failures. Disks will run out of space. Handle all failures gracefully.
  2. Keep things simple. Complexity breeds problems. Simple things are easier to get right. Avoid unnecessary dependencies. Installation should be simple. Failures on one server should have no impact on the rest of the data center.
  3. Automate everything. People make mistakes. People need sleep. People forget things. Automated processes are testable, fixable, and therefore ultimately much more reliable. Automate wherever possible.

Más específicamente:

Complicated algorithms and component interactions multiply the difficulty of debugging, deploying, etc. Simple and nearly stupid is almost always better in a high-scale service —the number of interacting failure modes is already daunting before complex optimizations are delivered. Our general rule is that optimizations that bring an order of magnitude improvement are worth considering, but percentage or even small factor gains aren’t worth it.

Los desarrolladores de software somos plomeros18. Y cuando el software escala no pasamos a ser ingenieros hidráulicos: seguimos siendo plomeros. Con más inodoros, con caños más largos.

∗ ∗ ∗

Manifiestos como Choose Boring Techonology19 y Radical Simplicty20 se proponen como antídotos para el preparacionismo y el glamour de la complejidad:

MySQL is boring. Postgres is boring. PHP is boring. Python is boring. Memcached is boring. Squid is boring. Cron is boring. The nice thing about boringness is that the capabilities of these things are well understood. But more importantly, their failure modes are well understood. (…) The “best” tool is the one that occupies the “least worst” position for as many of your problems as possible. It is basically always the case that the long-term costs of keeping a system working reliably vastly exceed any inconveniences you encounter while building it. Mature and productive developers understand this.

Radical Simplicity means having as few components and moving parts as possible. Reuse technology for different purposes instead of having a new moving part for each purpose. (…) A much smaller setup that achieves the same but has much fewer moving parts that need to be maintained, learned and debugged. Many fewer components need to be monitored, added to a logging server and alerts created. Do some companies need that complex setup when they have 50+ developers and millions of users? Yes. Do most of the companies, especially in their first years, need that complex setup? No.

Eligiendo tecnologías y arquitecturas “aburridas”, y limitando la cantidad de componentes, se reduce la carga de trabajo operacional y de mantenimiento, recuperando ese tiempo para proveer valor de negocio.

En sus ensayos clásicos2122, Paul Graham describía cómo (en una era anterior de internet y del capitalismo) su startup le había sacado ventaja a la competencia ahorrándose la burocracia de las empresas grandes y usando como arma secreta un lenguaje de programación mejor adecuado al problema. Acaso hoy exista una nueva oportunidad —mientras la industria se hunde bajo su propio peso y la mayoría infla burbujas con el zumbido que está de moda— de construir valor con herramientas aburridas, radicalmente simples.

Notas


3

En realidad dijeron (acá y acá) que el artículo hablaba más de los costos de usar lambdas en vez de servidores, pero preferí quedarme con el chiste comunista.

4

Me resulta parecido al de los patrones de diseño clásicos del Gang of Four.

5

Vale aclarar que los desarrolladores de Prime Video dicen que su arquitectura inicial de lambdas les permitió experimentar rápido con el producto, por lo que no es un ejemplo del preparacionismo que describo en este artículo.

6

Ley de Conway: “Las organizaciones dedicadas al diseño de sistemas están abocadas a producir diseños que son copias de las estructuras de comunicación de dichas organizaciones”.

7

Ley de Brooks: “Si crece el número de personas, también crecerá el tiempo invertido en tratar de averiguar lo que hace el resto”.

11

En contraposición al software realmente existente, el que ya está en producción y tiene una cantidad no despreciable de usuarios a los que les provee alguna utilidad.



2023-05-19 #software
powered by jorge | source | contact

Facundo Olano Los otros días circuló un artículo del equipo de Prime Video1 en el que cuentan cómo, contrayendo un conjunto de microservicios a una aplicación monolítica, resolvieron un cuello de botella y redujeron los costos de infraestructura en un 90%. La noticia llamó la atención, por un lado porque se supone que todo el chiste de los microservicios era facilitar la escalabilidad de los sistemas y, por otro, porque venía justamente de Amazon que, cabe suponer, es el principal beneficiario del derroche en infraestructura. Los detractores de los microservicios, que estaban agazapados esperando su oportunidad, se apuraron a leer el artículo como una admisión de derrota y prueba de la inviabilidad del modelo2, mientras que sus defensores reaccionaron explicando que el problema no es el comunismo la Arquitectura Orientada a Servicios, sino sus implementaciones existentes3.