Saltar al contenido
Guillermo Federico Goitea
  • Rendimiento
  • Arquitectura

El costo invisible del software

Una reflexión sobre el costo real de las decisiones técnicas: recursos, rendimiento y criterio a la hora de elegir herramientas.

Por Guillermo Federico Goitea · · 20 min de lectura

Cuando desarrollar dejó de significar medir los recursos

Prólogo: antes de ser desarrollador

Soy Guillermo Federico Goitea. Nací y crecí en Santiago del Estero, Argentina, y mi relación con la informática comenzó mucho antes de que pudiera imaginar que algún día iba a dedicarme profesionalmente a ella.

Desde chico me apasionaron los videojuegos. A finales de los años 90, alrededor de 1998 o 1999, llegó a mi casa mi primera PC. Tenía Windows 98.

Recuerdo perfectamente aquella primera noche. Me quedé fascinado con los sonidos del sistema: el sonido que hacía Windows cuando abría el menú Inicio, cuando maximizaba una ventana o cuando la minimizaba.

Puede parecer insignificante, pero para mí era algo completamente nuevo.

Sentía que aquello no tenía límites.

No entendía todavía cómo funcionaba una computadora, pero tenía la sensación de que detrás de esa pantalla había un mundo entero por descubrir.

Unos años después, alrededor de 2000 o 2001, llegó Internet a casa.

La conexión era mediante dial-up. Había que llamar al famoso 0610 y esperar mientras el módem emitía aquella secuencia de sonidos que para muchos era ruido, pero que para mí era música.

Era el sonido de una puerta abriéndose.

Internet cambió completamente mi relación con la informática. De repente, el conocimiento estaba ahí afuera y podía acceder a él.

No había YouTube.

No había inteligencia artificial.

No había Stack Overflow como lo conocemos hoy.

Había foros, tutoriales, documentación y personas compartiendo lo que habían aprendido. Y, en gran medida, estaba en inglés.

Yo tenía entre 13 y 16 años durante buena parte de esa etapa.

Comencé creando páginas web como entretenimiento. Utilizaba Microsoft FrontPage. Primero trabajaba en la vista de diseño, una especie de interfaz low-code de aquella época.

Pero rápidamente quise saber qué había detrás.

Pasé a la vista dividida.

Quería ver cómo cambiaban las etiquetas HTML cuando modificaba algo en el diseño. Empecé a entender que aquello que veía en pantalla tenía una representación concreta detrás.

Entonces apareció otro objetivo: quería tener mi propia página publicada en Internet.

Conocí GeoCities, de Yahoo!, que ofrecía alojamiento para páginas personales. Allí publiqué mi primera página web.

Después conocí Dreamweaver, de Macromedia. Para mí era increíble. El autocompletado de etiquetas y atributos parecía algo extraordinario.

También conocí Access y ASP. Y descubrí algo todavía más interesante: una página web podía almacenar información.

Después llegaron phpBB, PHP-Nuke, WordPress y muchas otras herramientas que terminaron acercándome cada vez más a PHP y MySQL.

Así comenzó un camino que, de una forma u otra, todavía continúa.

Hoy tengo 38 años y sigo desarrollando principalmente con Laravel, Inertia.js y React. Pero mi recorrido profesional no se limitó al desarrollo.

Antes estudié Ingeniería en Telecomunicaciones, una carrera que tuve que abandonar por razones económicas. Después continué con Ingeniería Electrónica en mi ciudad, donde adquirí herramientas para resolver problemas lógicos y técnicos desde otra perspectiva.

Mientras tanto, pasé por distintos trabajos y responsabilidades dentro del mundo de TI.

Mantenimiento de computadoras con Windows y Linux. Administración de servidores. Configuración de servicios. Desarrollo de software. Infraestructura. Automatización. Despliegues.

Actualmente trabajo como líder técnico, participando tanto en decisiones de desarrollo como, cuando es necesario, en infraestructura, despliegues y automatizaciones.

Mirando hacia atrás, creo que hay una característica de mi recorrido que terminó siendo especialmente importante: casi nunca tuve el lujo de poder abstraerme completamente de lo que ocurría detrás del software que desarrollaba.

Si necesitaba un servidor, tenía que configurarlo.

Si necesitaba alojar una aplicación, tenía que entender cómo funcionaba.

Si no podía pagar una determinada herramienta o servicio, tenía que buscar una alternativa y construirla.

Si un servidor tenía pocos recursos, tenía que hacer que las cosas funcionaran con esos recursos.

Y eso dejó una marca en mi forma de desarrollar.

Aprendí que los recursos no son infinitos.

Aprendí que una aplicación no vive solamente en el código que escribimos.

Y, sobre todo, aprendí que cada decisión técnica tiene un costo.

1. La computadora del desarrollador no es el mundo real

Hay algo que observo cada vez con mayor frecuencia en el desarrollo de software moderno.

Una aplicación funciona perfectamente en la computadora del desarrollador.

Tiene 32 GB de RAM.

8 núcleos y 12 hilos.

Un SSD rápido.

Una conexión de Internet excelente.

Una máquina capaz de ejecutar simultáneamente Docker, el navegador con decenas de pestañas, el IDE, bases de datos, Redis, servicios auxiliares y varias herramientas de desarrollo sin que aparentemente ocurra nada.

Entonces la aplicación funciona.

Y funciona muy bien.

El problema aparece cuando esa aplicación deja de ejecutarse en esa computadora.

Porque el usuario final no necesariamente tiene 32 GB de RAM.

El servidor de producción tampoco es infinito.

Y, especialmente, en la nube los recursos tienen un precio.

El problema es que muchas veces ese precio no aparece en el código.

2. El costo invisible

Cuando hablamos de rendimiento normalmente pensamos en tiempos de respuesta.

¿La página tarda 200 ms o 2 segundos?

¿La consulta SQL tarda 20 ms o 2 segundos?

¿El endpoint soporta 100 o 1.000 solicitudes por segundo?

Pero existe otra dimensión del rendimiento que muchas veces queda fuera de la conversación:

¿Cuántos recursos estamos utilizando para hacer algo que podría hacerse consumiendo mucho menos?

Ese costo puede aparecer en muchos lugares:

  • RAM.
  • CPU.
  • almacenamiento.
  • ancho de banda.
  • transferencia de datos.
  • tiempo de procesamiento.
  • tiempo de compilación.
  • tiempo de renderizado.
  • consumo energético.
  • dinero.

El problema es que muchos de estos costos permanecen ocultos durante el desarrollo.

Una aplicación puede funcionar perfectamente y, al mismo tiempo, estar consumiendo muchos más recursos de los necesarios.

3. De los microcontroladores a la nube

Durante mi formación en Ingeniería Electrónica tuve contacto con microcontroladores.

Y allí la situación era completamente diferente.

No podíamos asumir que teníamos recursos ilimitados.

La memoria podía medirse en cantidades que hoy parecen ridículas frente a las computadoras actuales.

Había que pensar.

Había que medir.

Había que optimizar.

Cada variable podía importar.

Cada byte podía importar.

Cada operación podía importar.

No porque optimizar fuera una cuestión filosófica.

Era simplemente una necesidad.

El programa tenía que entrar.

Y tenía que funcionar.

Hoy tenemos prácticamente el problema contrario.

Los recursos aumentaron enormemente.

Los equipos de desarrollo tienen más memoria.

Los procesadores son mucho más rápidos.

Los discos son más grandes.

Las conexiones de Internet son mucho más rápidas.

Y además aparecieron herramientas que nos permiten incorporar funcionalidades complejas en minutos.

Esto es fantástico.

Pero también puede generar un efecto secundario:

dejamos de percibir el costo de nuestras decisiones.

Cuando una computadora tiene 32 GB de RAM, consumir 500 MB adicionales puede parecer irrelevante.

Cuando el SSD tiene 1 TB disponible, agregar 200 MB parece insignificante.

Cuando Internet es rápido, una imagen de 5 MB puede parecer aceptable.

Cuando una aplicación corre en un servidor potente, una consulta poco eficiente puede pasar inadvertida.

Pero en producción, esas decisiones se multiplican.

4. El pequeño desperdicio deja de ser pequeño

Hace algún tiempo me encontré con algo que parece trivial.

Un logo utilizado en un sidebar.

La imagen original pesaba alrededor de 5 MB.

Visualmente, el usuario la veía aproximadamente a 200 píxeles de ancho.

La pregunta entonces es sencilla:

¿Qué sentido tiene enviar 5 MB para mostrar una imagen de 200 píxeles?

Probablemente, para el usuario, la diferencia visual sea prácticamente inexistente.

Pero la diferencia en recursos sí existe.

Ese archivo tiene que almacenarse.

Tiene que ser servido.

Tiene que viajar por la red.

Tiene que descargarse.

Tiene que ser procesado por el navegador.

Y todo eso ocurre cada vez que corresponde cargarlo.

Puede parecer insignificante si pensamos en una sola visita.

Pero el software no suele tener una sola visita.

Multipliquemos ese exceso por miles, cientos de miles o millones de solicitudes.

El pequeño desperdicio deja de ser pequeño.

El problema tampoco está limitado al desarrollo web.

También he visto videos grabados en 4K para ser reproducidos posteriormente en pantallas que ni siquiera tienen esa resolución.

Imágenes con una cantidad de DPI muy superior a la necesaria para el medio donde van a utilizarse.

Archivos enormes utilizados en lugares donde nadie puede percibir la diferencia.

Hace poco me ocurrió algo todavía más concreto.

Un repositorio comenzó a quedarse sin espacio.

Al investigar qué estaba ocupando tanto almacenamiento, encontré un video de unos pocos segundos que pesaba aproximadamente 200 MB y había sido agregado al proyecto para utilizarse como parte del hero de una página.

El archivo funcionaba.

La página funcionaba.

Visualmente, probablemente cumplía su objetivo.

Pero había una pregunta que faltaba:

¿Era necesario que pesara 200 MB?

5. Cuando escalar es más fácil que optimizar

Una de las experiencias que más me hizo pensar en este tema ocurrió mientras trabajaba como desarrollador en una empresa que tercerizaba parte de su infraestructura.

La aplicación debía ser llevada a AWS.

Una empresa externa se encargaba de construir los contenedores y montar la infraestructura utilizando Kubernetes.

Sobre el papel, todo parecía una evolución natural.

Una arquitectura moderna.

Contenedores.

Orquestación.

Múltiples instancias.

Escalabilidad.

Cloud.

Pero apareció un problema.

Después de la migración, la aplicación funcionaba más lentamente en AWS que en el antiguo VPS.

La reacción frente al problema fue aumentar los recursos.

Más capacidad.

Más instancias.

Más infraestructura.

El problema de rendimiento no había desaparecido realmente.

Simplemente se había intentado compensar utilizando más recursos.

Y eso tiene una característica particular:

la factura también escala.

A fin de mes, la diferencia entre mantener el antiguo VPS y operar múltiples instancias en AWS era considerable.

La experiencia me dejó una pregunta:

¿Cuántos problemas de rendimiento estamos solucionando realmente con infraestructura, cuando en realidad deberíamos solucionarlos con ingeniería?

Escalar horizontalmente puede ser la solución correcta.

Aumentar recursos puede ser necesario.

Kubernetes puede ser exactamente la herramienta adecuada para un determinado escenario.

El problema no es utilizar estas tecnologías.

El problema es utilizarlas como sustituto de la optimización.

Antes de preguntarnos cuánto más hardware podemos agregar, quizás deberíamos preguntarnos:

¿Qué está consumiendo los recursos actuales?

6. Base de datos: indexar no significa indexarlo todo

Existe otro comportamiento que observo frecuentemente en bases de datos.

Los desarrolladores aprenden algo fundamental:

Los índices aceleran las búsquedas.

Y es cierto.

El problema aparece cuando esa afirmación se transforma en:

Entonces indexemos todo.

Un índice no es gratis.

Ocupa espacio.

Tiene un costo de mantenimiento.

Las operaciones de escritura deben mantener esos índices.

Y tener un índice sobre una columna que prácticamente nunca participa de una consulta relevante puede no aportar ningún beneficio significativo.

Supongamos que tenemos una columna utilizada en una búsqueda que ocurre una vez al mes.

¿Tiene sentido mantener un índice específicamente para esa consulta?

No necesariamente.

La pregunta correcta no es:

¿Esta columna aparece en un WHERE?

La pregunta debería ser:

¿Qué consultas son realmente importantes para nuestra aplicación?

La optimización debería comenzar observando el comportamiento real.

¿Cuáles son las consultas más frecuentes?

¿Cuáles tardan más?

¿Cuáles consumen más recursos?

¿Cuáles se ejecutan miles de veces?

¿Dónde están los cuellos de botella?

A partir de ahí podemos decidir qué optimizar.

7. El código que imaginamos no siempre es el código que ejecuta producción

Esto también ocurre durante el diseño de una aplicación.

Podemos pensar:

'Esta búsqueda va a ser muy frecuente.'

Y crear índices, estructuras y optimizaciones alrededor de ella.

Pero después llega producción.

Y descubrimos que esa búsqueda casi nunca se utiliza.

Mientras tanto, otra consulta que jamás consideramos crítica se ejecuta miles de veces por hora.

Por eso la optimización basada exclusivamente en suposiciones puede ser peligrosa.

La producción nos cuenta una historia diferente de la que imaginamos durante el diseño.

La observabilidad debería formar parte de ese proceso.

Primero medir.

Después identificar.

Finalmente optimizar.

8. Cuando una consulta puede hacer demasiado trabajo

Otro ejemplo aparece en las búsquedas sobre múltiples columnas.

Podemos encontrarnos con consultas que buscan un término en varias columnas mediante OR.

Dependiendo de la consulta, los índices disponibles y el volumen de datos, esto puede terminar provocando recorridos costosos sobre grandes cantidades de registros.

En determinados escenarios puede ser más eficiente plantear la búsqueda de otra manera.

Por ejemplo, dividir la operación en consultas independientes y combinar sus resultados mediante UNION.

Y cuando el problema realmente consiste en buscar palabras dentro de grandes cantidades de texto, quizás la herramienta apropiada no sea seguir agregando condiciones OR, sino utilizar un mecanismo diseñado específicamente para búsquedas de texto, como un índice FULLTEXT.

Nuevamente, la cuestión no es memorizar una técnica.

Es entender el problema.

No existe una optimización universal.

Existe una consulta concreta, un volumen concreto de datos y un patrón de uso concreto.

9. Frontend: lo que el usuario no ve también consume

En el frontend ocurre algo similar.

Trabajar con React permite construir interfaces complejas de manera relativamente sencilla.

Pero que algo funcione visualmente no significa que sea eficiente.

Un componente puede renderizarse muchas más veces de las necesarias.

Y probablemente el usuario ni siquiera lo note.

La pantalla sigue mostrando exactamente lo que esperaba.

Pero detrás de esa interfaz hay trabajo:

  • JavaScript ejecutándose.
  • Componentes evaluándose.
  • Estado actualizándose.
  • Cálculos repitiéndose.
  • El navegador utilizando CPU y memoria.

Ese consumo puede pasar completamente desapercibido.

En una ocasión tuve que investigar una página que se volvía extremadamente lenta al mostrar alrededor de 100 registros.

A primera vista, cien registros no deberían representar un volumen especialmente grande.

Pero la interfaz tenía un problema de rendimiento en JavaScript.

Después de analizar qué estaba ocurriendo, encontré que parte del problema estaba relacionado con mecanismos que provocaban renderizados adicionales después de que la vista ya había sido procesada.

Visualmente, el resultado podía parecer correcto.

Pero el navegador estaba haciendo trabajo innecesario.

La diferencia entre una aplicación que funciona y una aplicación que funciona eficientemente puede estar justamente ahí:

en todo el trabajo que el usuario nunca llega a ver.

10. Miles de elementos que nadie está mirando

Otro problema habitual consiste en renderizar miles de elementos del DOM aunque solamente una pequeña parte sea visible en el viewport.

Supongamos que una aplicación necesita mostrar 5.000 registros.

Podemos generar los 5.000 elementos HTML.

El navegador puede hacerlo.

Una computadora potente probablemente pueda soportarlo.

Pero ¿por qué deberíamos hacerlo?

El usuario está viendo quizá 20 o 30 registros.

Los otros miles están fuera de la pantalla.

En estos casos, técnicas como la virtualización pueden permitir mantener solamente una fracción de los elementos en el DOM.

Esto se vuelve todavía más importante cuando el usuario no dispone de un equipo potente.

Y especialmente cuando hablamos de teléfonos, tablets, terminales o dispositivos de baja potencia.

La aplicación puede funcionar perfectamente en la computadora del desarrollador.

Y ser una experiencia completamente diferente para el usuario.

11. Procesar todo de una vez tampoco es gratis

El problema también aparece cuando trasladamos procesamiento al navegador o al backend.

Podemos recibir miles de registros y procesarlos todos de una sola vez.

Funciona.

Hasta que deja de funcionar.

El proceso tiene memoria limitada.

El dispositivo tiene una CPU determinada.

Y cada operación tiene un costo.

En algunos casos, además, ni siquiera se utilizan lotes.

Se intenta cargar y procesar todo simultáneamente.

Aquí aparece un equilibrio que considero fundamental:

memoria contra tiempo de ejecución.

Reducir el consumo de memoria puede significar realizar más operaciones o procesar los datos en más etapas.

Procesar todo simultáneamente puede reducir el tiempo total, pero aumentar considerablemente el consumo de memoria.

Por eso no existe una única respuesta correcta.

Hay que encontrar un punto de equilibrio entre:

  • memoria;
  • CPU;
  • tiempo de ejecución;
  • volumen de datos;
  • frecuencia de ejecución;
  • capacidad del dispositivo;
  • experiencia del usuario.

Optimizar no significa simplemente consumir menos.

Optimizar significa utilizar los recursos adecuados para conseguir el resultado necesario.

12. Cachear todo tampoco es optimizar

La caché es otra herramienta que puede utilizarse incorrectamente.

Sabemos que consultar una caché puede ser mucho más rápido que ejecutar determinadas operaciones nuevamente.

Entonces aparece otra tentación:

'Cacheemos todo.'

Pero una caché también consume recursos.

Necesita memoria.

Necesita invalidación.

Necesita políticas de expiración.

Necesita mantenimiento.

Y puede introducir problemas de consistencia.

Si almacenamos en caché información que prácticamente nunca se consulta nuevamente, estamos ocupando recursos para conservar algo que no aporta un beneficio real.

Es el mismo principio que aparece con los índices.

No todo lo que puede optimizarse necesita ser optimizado.

La pregunta debería ser:

¿Qué operación representa realmente un costo significativo y ocurre con suficiente frecuencia como para justificar esta optimización?

13. La dependencia para resolver lo que podríamos resolver nosotros mismos

Otro fenómeno que observo es la cantidad de dependencias que incorporamos para resolver problemas relativamente simples.

Supongamos que una aplicación necesita manejar usuarios, roles y permisos.

El modelo conceptual puede ser extremadamente sencillo:

  • usuarios;
  • roles;
  • permisos;
  • una relación entre usuarios y roles;
  • una relación entre roles y permisos.

La lógica necesaria para determinar si un usuario posee un determinado permiso tampoco necesariamente es compleja.

Sin embargo, muchas veces la primera pregunta que aparece es:

¿Existe un paquete que haga esto?

Y probablemente exista.

Eso tiene ventajas.

Una biblioteca bien mantenida puede ahorrarnos muchísimo tiempo.

No estoy planteando que utilizar dependencias externas sea malo.

El problema aparece cuando incorporamos una dependencia sin analizar el costo de hacerlo.

Una dependencia no es solamente el código que importamos.

También estamos incorporando:

  • código que no escribimos;
  • funcionalidades que probablemente nunca utilizaremos;
  • nuevas versiones que tendremos que mantener;
  • posibles incompatibilidades futuras;
  • nuevas dependencias transitivas;
  • potenciales vulnerabilidades;
  • tiempo de actualización;
  • tiempo de aprendizaje;
  • y una nueva pieza que forma parte de nuestra cadena de suministro de software.

Supongamos que necesitamos una funcionalidad extremadamente pequeña.

¿Qué ocurre si para resolverla incorporamos una biblioteca que contiene cientos o miles de funcionalidades adicionales?

Es posible que estemos utilizando el 1% de una herramienta enorme para resolver algo que podríamos implementar directamente.

En algunos casos, puede ser una excelente decisión.

En otros, quizás no.

La pregunta debería ser:

¿Qué estamos comprando realmente al agregar esta dependencia?

14. No todo problema necesita inteligencia artificial

Esta misma lógica aparece ahora con la inteligencia artificial.

He visto aplicaciones donde se incorporan modelos de IA para realizar predicciones que, por las características del problema y de los datos disponibles, podrían resolverse con algo tan sencillo como una regresión lineal.

La inteligencia artificial puede ser una herramienta extraordinariamente poderosa.

Pero que podamos utilizarla no significa que debamos utilizarla.

Si tenemos un problema cuya relación entre las variables puede modelarse adecuadamente mediante una regresión lineal, utilizar un modelo mucho más complejo no necesariamente agrega valor.

De hecho, puede agregar complejidad.

Un modelo más sofisticado puede requerir más infraestructura, más memoria, más procesamiento, más tiempo de entrenamiento, más conocimientos para mantenerlo y, eventualmente, más dinero.

La pregunta no debería ser:

¿Podemos utilizar IA para resolver esto?

La pregunta debería ser:

¿Qué herramienta es suficiente para resolver correctamente este problema?

Una solución sencilla que resuelve correctamente el problema puede ser técnicamente superior a una solución compleja que solamente demuestra que somos capaces de utilizar una tecnología determinada.

15. La tecnología no debería convertirse en el objetivo

Y aquí aparece el punto central.

No solamente tenemos inteligencia artificial.

Tenemos frameworks excelentes.

Tenemos cientos de miles de paquetes disponibles.

Tenemos Kubernetes.

Tenemos servicios administrados en la nube.

Tenemos sistemas de caché.

Tenemos bases de datos distribuidas.

Tenemos arquitecturas de microservicios.

Tenemos herramientas capaces de hacer en segundos cosas que hace veinte años podían llevarnos días.

Todo eso es extraordinariamente útil.

Pero existe una diferencia entre tener una herramienta disponible y tener una razón para utilizarla.

Una herramienta debería resolver un problema.

No deberíamos crear problemas para justificar una herramienta.

16. No estoy proponiendo volver al pasado

Es importante aclarar algo.

No estoy proponiendo volver a programar como hace veinte años.

No estoy proponiendo dejar de utilizar paquetes de terceros.

No estoy proponiendo abandonar los frameworks.

No estoy proponiendo volver a administrar servidores manualmente porque la nube sea costosa.

No estoy proponiendo dejar Kubernetes.

Y mucho menos estoy proponiendo abandonar la inteligencia artificial.

De hecho, yo utilizo todas estas herramientas.

Utilizo frameworks.

Utilizo repositorios y bibliotecas de terceros.

Utilizo Docker.

Utilizo infraestructura cloud.

Utilizo Kubernetes cuando el problema lo justifica.

Y utilizo inteligencia artificial como herramienta de desarrollo.

La tecnología moderna nos permite construir software de una manera que hace años hubiera sido impensable.

Sería absurdo renunciar a esas ventajas.

El problema no es utilizar tecnología moderna.

El problema es utilizarla sin comprender por qué.

17. La IA debe ser una herramienta, no quien toma las decisiones

Esto se vuelve especialmente importante con la inteligencia artificial.

La IA puede escribir código.

Puede diseñar arquitecturas.

Puede elegir bibliotecas.

Puede proponer consultas SQL.

Puede optimizar funciones.

Puede crear componentes.

Puede investigar alternativas.

Puede implementar soluciones completas.

Y todo eso puede multiplicar nuestra productividad.

Pero existe una diferencia fundamental entre pedirle que nos ayude a implementar una decisión y permitir que tome la decisión por nosotros.

La IA no conoce necesariamente todas las restricciones de nuestro sistema.

No conoce necesariamente el costo de nuestra infraestructura.

No conoce el comportamiento real de nuestros usuarios.

No conoce qué funcionalidades vamos a mantener durante los próximos cinco años.

No conoce necesariamente qué parte del sistema es crítica y cuál no.

Puede proponer una solución técnicamente válida y, aun así, ser una mala decisión para nuestro contexto.

Por eso creo que existe una regla que deberíamos conservar:

Hay que usar la IA, pero no debemos dejar que la IA nos use a nosotros.

La IA puede sugerir.

Puede acelerar.

Puede investigar.

Puede implementar.

Puede ayudarnos a pensar.

Pero el criterio técnico sigue siendo responsabilidad de quien desarrolla el sistema.

18. El criterio técnico

La complejidad no es mala.

Una solución sencilla no siempre es mejor.

Hay problemas que requieren arquitecturas complejas.

Hay sistemas que necesitan múltiples servicios.

Hay aplicaciones que necesitan Kubernetes.

Hay problemas que requieren modelos de machine learning sofisticados.

Hay consultas que necesitan índices complejos.

Hay aplicaciones que necesitan cachés distribuidas.

La complejidad puede estar perfectamente justificada.

La cuestión es otra:

¿Podemos explicar por qué existe cada pieza de nuestro sistema?

Si podemos responderlo, probablemente exista una razón.

Si la respuesta es:

'Porque era lo que recomendaba la biblioteca.'
'Porque lo generó la IA.'
'Porque todos lo hacen así.'
'Porque AWS permite hacerlo.'
'Porque Kubernetes lo resuelve.'

Entonces quizás deberíamos detenernos un momento.

No necesariamente para eliminarlo.

Sino para entenderlo.

19. Primero el problema, después la herramienta

Después de todos estos años, hay algo que cada vez considero más importante.

La optimización no comienza con una herramienta.

Comienza con una pregunta.

¿Qué problema estamos intentando resolver?

¿Cuánto ocurre?

¿Con qué frecuencia?

¿Cuántos datos tenemos?

¿Cuántos usuarios tenemos?

¿Qué recursos consume actualmente?

¿Dónde está el cuello de botella?

¿Qué costo tiene?

¿Qué ocurrirá cuando el sistema crezca?

Recién después deberían aparecer las herramientas.

Quizás la respuesta sea un índice.

Quizás sea una caché.

Quizás sea una consulta diferente.

Quizás sea virtualización del DOM.

Quizás sea procesamiento por lotes.

Quizás sea aumentar recursos.

Quizás sea Kubernetes.

Quizás sea inteligencia artificial.

Y quizás la respuesta sea simplemente:

no hacer nada, porque el problema no es suficientemente importante como para justificar la complejidad.

Ese último caso también es una decisión técnica.

Conclusión: usar con conciencia

No creo que el problema de la industria sea que tengamos demasiadas herramientas.

Creo que el problema aparece cuando dejamos de preguntarnos por qué las estamos utilizando.

No quiero volver a la época en la que había que contar cada byte porque no existían los recursos que tenemos hoy.

No quiero abandonar la nube.

No quiero dejar de utilizar Kubernetes.

No quiero dejar de utilizar bibliotecas de terceros.

No quiero volver a desarrollar todo desde cero.

Y tampoco quiero dejar de utilizar inteligencia artificial.

Quiero utilizar todas esas herramientas.

Pero quiero utilizarlas con conciencia.

Una biblioteca puede ahorrarnos meses de trabajo.

Un framework puede darnos una arquitectura sólida.

Kubernetes puede resolver problemas de orquestación extremadamente complejos.

La nube puede proporcionarnos elasticidad, disponibilidad y capacidad de crecimiento.

La inteligencia artificial puede multiplicar nuestra productividad y permitirnos resolver problemas que antes requerían mucho más tiempo.

Todas esas herramientas son extraordinarias.

Pero ninguna de ellas debería reemplazar nuestro criterio.

El hecho de que una herramienta exista no significa que debamos utilizarla.

El hecho de que podamos escalar no significa que debamos escalar.

El hecho de que podamos agregar una dependencia no significa que la necesitemos.

El hecho de que podamos utilizar IA no significa que necesitemos un modelo de IA.

Y el hecho de que tengamos más recursos no significa que debamos consumirlos.

La tecnología debería ayudarnos a resolver problemas, no convertirse en el problema.

Por eso, quizás la pregunta más importante que deberíamos hacernos como desarrolladores no sea:

¿Qué tecnología puedo utilizar?

Sino:

¿Qué necesito realmente para resolver este problema y cuál es el costo de hacerlo?

Porque detrás de cada abstracción, cada dependencia, cada servidor, cada proceso y cada línea de código existe un costo.

A veces ese costo es pequeño.

A veces es enorme.

Y muchas veces simplemente no lo estamos midiendo.

No se trata de utilizar menos tecnología.

Se trata de utilizar la tecnología necesaria.

No se trata de volver al pasado.

Se trata de no olvidar las lecciones que aprendimos cuando los recursos eran limitados.

No se trata de desconfiar de las herramientas modernas.

Se trata de asegurarnos de que nosotros seguimos tomando las decisiones.

Y, sobre todo, se trata de recordar algo que sigue siendo cierto aunque nuestras computadoras tengan 32 GB de RAM, nuestros servidores tengan cientos de gigabytes y la nube parezca infinita:

Los recursos siguen siendo finitos.
Y cada decisión técnica tiene un costo.