Un sistema distribuido es un conjunto de ordenadores independientes que, desde el punto de vista del usuario, se comportan como un único sistema coherente. En el mundo actual, prácticamente cualquier aplicación de cierta envergadura (una banca online, una plataforma de comercio electrónico o un sistema de gestión de pólizas de seguros) es un sistema distribuido. Comprender sus fundamentos es imprescindible antes de abordar los microservicios, porque casi todos los problemas difíciles de las arquitecturas modernas nacen de la naturaleza distribuida del sistema, no de los microservicios en sí.

En esta lección estableceremos qué significa "distribuido", desmontaremos las famosas falacias de la computación distribuida y analizaremos los tres grandes retos que todo arquitecto debe interiorizar: la latencia, los fallos parciales y la consistencia.

Contenido

  1. Qué es un sistema distribuido
  2. Por qué distribuimos: ventajas y motivaciones
  3. Las ocho falacias de la computación distribuida
  4. Reto 1: la latencia de la red
  5. Reto 2: los fallos parciales
  6. Reto 3: la consistencia de los datos
  7. Modelos de comunicación
  8. Errores comunes y consejos
  9. Ejercicios
  10. Conclusión

  1. Qué es un sistema distribuido

Una definición clásica, atribuida a Andrew Tanenbaum, dice:

Un sistema distribuido es una colección de ordenadores independientes que aparecen ante sus usuarios como un único sistema coherente.

Las tres ideas clave son:

  • Independientes: cada nodo tiene su propia memoria, su propio reloj y puede fallar por separado.
  • Coordinados: los nodos cooperan intercambiando mensajes a través de una red.
  • Transparencia: idealmente, el usuario no percibe que hay varias máquinas.

Una característica fundamental, formulada por Leslie Lamport, lo resume con ironía:

Un sistema distribuido es aquel en el que el fallo de un ordenador del que ni siquiera sabías que existía puede dejar inutilizable tu propio ordenador.

graph LR
    C[Cliente] --> GW[API Gateway]
    GW --> S1[Servicio Pólizas]
    GW --> S2[Servicio Clientes]
    S1 --> DB1[(BD Pólizas)]
    S2 --> DB2[(BD Clientes)]
    S1 -.mensajes.-> S2

En este diagrama, cada nodo (gateway, servicios, bases de datos) es un proceso independiente que se comunica por red. Las flechas continuas representan llamadas directas y la flecha discontinua una comunicación asíncrona entre servicios.

  1. Por qué distribuimos: ventajas y motivaciones

Distribuir un sistema añade complejidad, así que solo merece la pena cuando obtenemos beneficios reales:

Motivación Descripción
Escalabilidad Añadir más máquinas para atender más carga (escalado horizontal).
Disponibilidad Si una réplica cae, otra sigue dando servicio.
Tolerancia a fallos El sistema sigue operativo pese a fallos parciales.
Latencia geográfica Acercar los datos al usuario (CDN, regiones).
Aislamiento Equipos y módulos evolucionan de forma independiente.

La regla de oro: no distribuyas por moda. Distribuye cuando un único servidor ya no basta o cuando la organización necesita autonomía entre equipos.

  1. Las ocho falacias de la computación distribuida

En 1994, Peter Deutsch y otros ingenieros de Sun Microsystems enumeraron las suposiciones erróneas que los desarrolladores hacen repetidamente al construir sistemas distribuidos. Cada una de ellas es falsa, y olvidarlo provoca bugs sutiles y caídas en producción.

# Falacia Realidad
1 La red es fiable Los paquetes se pierden, las conexiones se cortan.
2 La latencia es cero Cada salto de red cuesta milisegundos o más.
3 El ancho de banda es infinito Hay límites; transferir datos grandes satura la red.
4 La red es segura Hay ataques, interceptaciones y suplantaciones.
5 La topología no cambia Servidores aparecen y desaparecen continuamente.
6 Hay un único administrador Múltiples equipos, proveedores y configuraciones.
7 El coste de transporte es cero Serializar y mover datos consume CPU y dinero.
8 La red es homogénea Conviven distintos protocolos, versiones y hardware.

El siguiente ejemplo en Java muestra código que cae en la falacia 1 y 2 (la red es fiable y la latencia es cero):

// MAL: asume que la llamada siempre funciona y es instantánea
public Cliente obtenerCliente(String id) {
    // Sin timeout: si el servicio remoto no responde,
    // este hilo queda bloqueado indefinidamente.
    return clienteRestClient.get("/clientes/" + id);
}

Aquí no hay timeout, ni reintentos, ni manejo de error. Si el servicio remoto tarda o falla, el hilo se queda colgado. La versión correcta exige timeouts explícitos y gestión de fallos, algo que veremos en la lección de resiliencia.

  1. Reto 1: la latencia de la red

La latencia es el tiempo que tarda un mensaje en viajar de un nodo a otro. A diferencia de una llamada a un método local (nanosegundos), una llamada por red cuesta órdenes de magnitud más. Una referencia mental útil (números aproximados de Jeff Dean):

Operación Tiempo aproximado
Acceso a caché L1 0,5 ns
Acceso a memoria RAM 100 ns
Lectura de SSD 150.000 ns (0,15 ms)
Ida y vuelta en el mismo centro de datos 500.000 ns (0,5 ms)
Ida y vuelta entre continentes 150.000.000 ns (150 ms)

La consecuencia práctica es que hacer muchas llamadas pequeñas por red es muy caro. Este antipatrón se conoce como "N+1 de red":

// MAL: una llamada de red por cada pedido (problema N+1)
List<Pedido> pedidos = pedidoService.listar();      // 1 llamada
for (Pedido p : pedidos) {
    Cliente c = clienteService.obtener(p.getClienteId()); // N llamadas
    p.setCliente(c);
}

Si hay 100 pedidos, esto son 101 llamadas de red. La solución es agrupar (batching), pidiendo todos los clientes de una vez:

// BIEN: dos llamadas en total, sin importar el número de pedidos
List<Pedido> pedidos = pedidoService.listar();
Set<String> ids = pedidos.stream()
        .map(Pedido::getClienteId)
        .collect(Collectors.toSet());
Map<String, Cliente> clientes = clienteService.obtenerVarios(ids); // 1 llamada
pedidos.forEach(p -> p.setCliente(clientes.get(p.getClienteId())));

Reducimos de 101 a 2 llamadas: la diferencia de rendimiento es enorme.

  1. Reto 2: los fallos parciales

En un sistema monolítico, o todo funciona o todo falla. En uno distribuido aparece un estado nuevo y peligroso: el fallo parcial, en el que unos componentes funcionan y otros no.

El caso más difícil es la incertidumbre: cuando enviamos una petición y no recibimos respuesta, no sabemos si:

  • la petición no llegó al destino,
  • llegó y se procesó pero la respuesta se perdió,
  • el destino sigue procesándola lentamente.
sequenceDiagram
    participant A as Servicio A
    participant B as Servicio B
    A->>B: Cobrar 100 EUR
    Note over B: Cobra correctamente
    B--xA: Respuesta perdida (timeout)
    Note over A: ¿Reintento? Riesgo de cobrar dos veces

Esto obliga a diseñar operaciones idempotentes: una operación es idempotente si ejecutarla varias veces produce el mismo resultado que ejecutarla una sola vez. Así un reintento es seguro.

// Operación idempotente mediante clave de idempotencia
public void cobrar(String idempotencyKey, BigDecimal importe) {
    // Si ya procesamos esta clave, no volvemos a cobrar
    if (repositorioCobros.existe(idempotencyKey)) {
        return; // ya hecho, reintento seguro
    }
    procesarPago(importe);
    repositorioCobros.guardar(idempotencyKey);
}

La idempotencyKey (un identificador único que el cliente genera y reenvía en cada reintento) permite detectar duplicados. Sin ella, un reintento podría cobrar dos veces.

  1. Reto 3: la consistencia de los datos

Cuando los datos están replicados o repartidos entre nodos, mantenerlos coherentes es difícil. Distinguimos dos modelos básicos:

Modelo Descripción Ejemplo de uso
Consistencia fuerte Toda lectura devuelve el último valor escrito. Saldo bancario, control de stock crítico.
Consistencia eventual Las réplicas convergen "con el tiempo"; puede haber lecturas desfasadas. Contador de "me gusta", catálogo de productos.

La consistencia fuerte es cómoda para el programador pero cara en rendimiento y disponibilidad. La consistencia eventual escala mejor pero obliga a tolerar datos temporalmente desactualizados. En la lección sobre el Teorema CAP profundizaremos en por qué no se puede tener todo a la vez.

  1. Modelos de comunicación

Existen dos grandes formas de comunicar nodos, que estudiaremos en detalle más adelante:

  • Síncrona (petición/respuesta): el llamante espera la respuesta (REST, gRPC). Sencilla pero acopla disponibilidad: si B está caído, A se ve afectado.
  • Asíncrona (mensajería/eventos): el llamante publica un mensaje y continúa (colas, eventos). Desacopla, pero introduce complejidad y consistencia eventual.
# Ejemplo conceptual de evento publicado en un bus de mensajes
evento: PolizaCreada
version: 1
datos:
  polizaId: "POL-00123"
  ramo: "hogar"
  fechaAlta: "2026-06-30"

Este evento describe un hecho ocurrido ("una póliza fue creada"). Otros servicios pueden suscribirse y reaccionar sin que el emisor sepa quiénes son, lo que reduce el acoplamiento.

Errores Comunes y Consejos

  • Tratar las llamadas de red como llamadas locales: siempre habrá fallos y latencia. Pon timeouts en todas las llamadas remotas.
  • Olvidar la idempotencia: si vas a reintentar, asegúrate de que la operación sea segura de repetir.
  • Distribuir prematuramente: un monolito bien hecho suele ser mejor punto de partida que diez microservicios mal definidos.
  • Ignorar la observabilidad: sin logs correlacionados, métricas y trazas, depurar un sistema distribuido es casi imposible. Usa un identificador de correlación que viaje en todas las peticiones.
  • Confiar en los relojes: los relojes de distintas máquinas no están perfectamente sincronizados; no uses marcas de tiempo para ordenar eventos críticos sin un mecanismo adecuado.

Ejercicios

  1. Enumera las ocho falacias de la computación distribuida e indica, para cada una, una consecuencia práctica si la ignoras en el diseño.
  2. Tienes un servicio de pagos que recibe peticiones por red. Explica por qué un simple reintento puede provocar un cobro doble y diseña, en pseudocódigo Java, una solución idempotente.
  3. Estima cuántas veces más lenta es una llamada entre continentes (150 ms) frente a un acceso a memoria RAM (100 ns). Razona qué implicación tiene esto para el diseño de APIs.

Soluciones

  1. Por ejemplo: La red es fiable → si no manejas errores, una conexión cortada deja la operación en estado inconsistente. La latencia es cero → si diseñas APIs "charlatanas" con muchas idas y vueltas, el rendimiento se degrada. (El resto se completa de forma análoga con la tabla de la sección 3.)

  2. Un reintento provoca cobro doble porque, si la primera petición sí se procesó pero su respuesta se perdió, el cliente cree que falló y reenvía. Solución:

public void cobrar(String idempotencyKey, BigDecimal importe) {
    if (repositorioCobros.existe(idempotencyKey)) {
        return; // ya cobrado, ignoramos el duplicado
    }
    procesarPago(importe);
    repositorioCobros.guardar(idempotencyKey);
}

La clave es registrar la clave de idempotencia de forma atómica junto con el cobro.

  1. 150 ms = 150.000.000 ns; 150.000.000 / 100 = 1.500.000 veces más lenta. Implicación: hay que minimizar el número de llamadas remotas (batching, cachés, agregación de datos) en lugar de diseñar APIs que requieran muchas idas y vueltas.

Conclusión

Hemos visto que un sistema distribuido es un conjunto de nodos independientes que cooperan por red y que parecen uno solo. Las ocho falacias nos recuerdan que la red no es fiable, ni instantánea, ni segura, ni gratuita. Los tres grandes retos (latencia, fallos parciales y consistencia) condicionan todas las decisiones de arquitectura que tomaremos a partir de ahora.

Con estos fundamentos claros, en la siguiente lección, Arquitectura de Microservicios, veremos cómo un estilo arquitectónico concreto aprovecha (y a la vez sufre) la naturaleza distribuida para construir sistemas escalables y mantenibles.

Curso de Arquitectura de Aplicaciones

Módulo 1: Fundamentos de la Arquitectura de Aplicaciones

Módulo 2: Principios y Tácticas de Diseño

Módulo 3: Estilos y Patrones Arquitectónicos

Módulo 4: Arquitecturas Distribuidas y Microservicios

Módulo 5: Arquitecturas Dirigidas por Eventos y Mensajería

Módulo 6: Diseño Dirigido por el Dominio (DDD)

Módulo 7: Datos y Persistencia

Módulo 8: Arquitectura en la Nube y Despliegue

Módulo 9: Calidad, Seguridad y Observabilidad

Módulo 10: Evolución, Gobernanza y Casos Prácticos

© Copyright 2026. Todos los derechos reservados