INGENIERÍAPENSAMIENTO Y PRÁCTICA

Las decisiones técnicas detrás de los sistemas que construyo.

Una colección de temas de ingeniería moldeados por mi experiencia con sistemas backend, procesamiento distribuido, rendimiento, datos y evolución de software.

Intento entender el problema antes de decidir cómo resolverlo.

Un requerimiento describe qué necesita cambiar, pero no siempre explica por qué existe el problema. Intento comprender el comportamiento y las restricciones subyacentes antes de elegir una implementación.

01

Comprender

¿Qué intenta lograr el sistema? ¿Qué sucede actualmente? ¿Qué restricciones ya existen?

02

Investigar

Rastreo el comportamiento a través del código, los datos y las interacciones relevantes para identificar dónde está el problema real.

03

Evaluar

Cuando hay varias soluciones posibles, considero sus ventajas y desventajas en lugar de asumir que el primer enfoque es el correcto.

04

Validar

Verifico que el cambio resuelva el problema original y se comporte correctamente dentro del resto del sistema.

La arquitectura debe servir al sistema, no al revés.

No veo la arquitectura como una plantilla fija. La estructura adecuada depende de las responsabilidades del sistema, sus restricciones, el crecimiento esperado y el tipo de cambios que deberá soportar.

01

Comprender los límites

Antes de definir servicios o capas, intento entender qué responsabilidades pertenecen juntas y dónde deben existir límites claros entre las distintas responsabilidades.

02

Elegir el nivel adecuado de complejidad

No todos los problemas necesitan servicios distribuidos o infraestructura adicional. Prefiero introducir complejidad cuando resuelve un requerimiento concreto del sistema.

03

Diseñar para el cambio

Los sistemas evolucionan. Intento aislar responsabilidades y cambios para que extender una parte del sistema no afecte innecesariamente todo lo que la rodea.

El rendimiento comienza por entender dónde consume realmente sus recursos el sistema.

Prefiero medir antes de optimizar. El tiempo de procesamiento, la carga de la base de datos, el uso de memoria, las asignaciones y las operaciones costosas pueden revelar cuellos de botella muy distintos, incluso cuando el sistema parece lento desde fuera.

01

Medir antes de cambiar

Intento identificar el costo real de una operación antes de decidir que necesita optimización. Sin medición, la optimización puede convertirse fácilmente en una suposición.

02

Eliminar trabajo innecesario

A veces, la mejor optimización es evitar una operación por completo: una consulta innecesaria a la base de datos, un cálculo, transformación, asignación o búsqueda de estado repetidos.

03

Comprender el costo de las abstracciones

Las abstracciones pueden mejorar la mantenibilidad, pero también tienen implicaciones de ejecución y memoria. Considero estas compensaciones al trabajar en rutas sensibles al rendimiento.

04

Validar con carga realista

Un cambio que se ve mejor de forma aislada puede comportarse distinto bajo una carga de trabajo real. Utilizo escenarios de alto volumen y estrés para comparar el comportamiento antes y después de una optimización.

PRINCIPIO DE RENDIMIENTOLa operación más rápida suele ser aquella que el sistema no necesita realizar.

Escalar consiste en controlar cómo se comporta un sistema conforme crece la carga de trabajo.

Cuando aumenta la carga de trabajo, simplemente añadir más recursos no siempre es suficiente. Pienso en dónde se acumula el trabajo, cómo interactúan los componentes y qué partes del sistema necesitan escalar de forma independiente.

01

Encontrar dónde se acumula el trabajo

El aumento del tráfico puede revelar cuellos de botella en lugares inesperados. Reviso las etapas de procesamiento, las colas, las operaciones de base de datos y los recursos compartidos para entender dónde aumenta la presión.

02

Controlar la concurrencia

Más trabajo en paralelo no significa automáticamente mayor capacidad de procesamiento. La concurrencia debe controlarse de acuerdo con la capacidad del sistema y los recursos de los que depende.

03

Separar cargas de trabajo independientes

Cuando distintas cargas de trabajo tienen características diferentes, aislarlas puede evitar que un tipo de trabajo afecte la capacidad de procesamiento de otro.

04

Diseñar considerando las restricciones reales

Las decisiones de escalabilidad deben considerar CPU, memoria, almacenamiento, red, capacidad de base de datos, latencia y complejidad operativa, en lugar de centrarse en una sola métrica.

PRINCIPIO DE ESCALABILIDADMás concurrencia no siempre significa mayor capacidad de procesamiento.

El diseño de datos es parte del diseño del sistema.

La forma en que los datos se almacenan, consultan y sincronizan afecta el comportamiento de todo el backend. Considero el propósito de los datos, sus patrones de acceso, requisitos de consistencia y la carga de trabajo que el sistema debe soportar.

01

Modelar para el problema

Los modelos de datos deben representar el dominio y las relaciones que importan para el sistema. Parto de cómo se utilizan los datos en lugar de diseñar tablas de forma aislada.

02

Comprender los patrones de acceso

Los mismos datos pueden requerir estructuras diferentes según la frecuencia con la que se leen, escriben, filtran, agregan o comparten entre distintas partes del sistema.

03

Elegir el almacenamiento deliberadamente

El almacenamiento relacional y no relacional resuelven problemas distintos. Considero las características de la carga de trabajo antes de decidir dónde y cómo debe persistirse la información.

04

Mantener el estado de forma intencional

El estado en caché o temporal puede mejorar el rendimiento, pero también introduce consideraciones de consistencia y ciclo de vida. Intento hacer explícitas esas responsabilidades.

Una buena ingeniería también implica saber cómo cambiar un sistema existente.

El software rara vez se mantiene en el estado para el que fue diseñado originalmente. Los requerimientos cambian, las cargas de trabajo crecen, las suposiciones se vuelven obsoletas y aparecen nuevas restricciones. Veo la evolución como parte del proceso de ingeniería, no como algo que ocurre después del desarrollo.

01

Comprender lo que ya existe

Antes de cambiar un sistema existente, intento entender su comportamiento actual, dependencias, datos y las razones detrás de sus restricciones importantes.

02

Cambiar de forma incremental

Los cambios grandes pueden aumentar el riesgo. Cuando es posible, prefiero separar los cambios en pasos más pequeños que puedan entenderse, probarse y validarse de manera independiente.

03

Preservar lo que aún funciona

No todo en un sistema existente necesita reemplazarse. Intento distinguir entre lo que debe cambiar y lo que puede seguir cumpliendo su propósito.

04

Facilitar los cambios futuros

Idealmente, un cambio exitoso debe dejar el sistema en un estado en el que el siguiente cambio sea más fácil de comprender e implementar, en lugar de añadir otra capa de complejidad accidental.

PRINCIPIO DE EVOLUCIÓNUn sistema debe ser más fácil de cambiar, no más difícil, conforme lo comprendemos mejor.

Toda decisión técnica existe dentro de un contexto.

Intento evaluar las soluciones según el problema que deben resolver, las restricciones del sistema y las consecuencias que introducen. Rara vez existe una elección técnica universalmente correcta.

01Problema

¿Qué intentamos resolver realmente?

02Restricciones

¿Qué limitaciones ya existen?

03Compensaciones

¿Qué ganamos y a qué renunciamos?

04Consecuencia

¿Cómo afectará esta decisión al sistema más adelante?

PRINCIPIO DE DECISIÓNLa mejor decisión técnica es la que tiene sentido para el problema y el contexto.