Arquitectura · 12 min de lectura

Cómo DHL mueve 1.8 mil millones de paquetes: un análisis de arquitectura

Publicado el 9 de junio de 2026

Cómo DHL mueve 1.8 mil millones de paquetes: un análisis de arquitectura

DHL procesa más de 1.8 mil millones de envíos por año en más de 220 países. Es un número fácil de leer y difícil de internalizar. Significa millones de transiciones de estado concurrentes cada hora: paquetes siendo escaneados en depósitos, cruzando fronteras aduaneras, cargando en aviones, llegando a hubs de última milla, y finalmente apareciendo en la puerta de alguien.

Cada uno de esos eventos necesita ser capturado, almacenado, consultado y mostrado al cliente en tiempo casi real.

Esto no es una aplicación CRUD estándar. Es un sistema distribuido con algunos de los requisitos de consistencia, escala y disponibilidad más exigentes de la industria. En este artículo desgloso los patrones arquitectónicos que hacen que un sistema como el de DHL funcione, dónde están los problemas reales, y qué haría diferente si lo diseñara desde cero.

El problema central: tracking de paquetes a escala

La funcionalidad más visible de cualquier sistema de mensajería es el tracking. Un cliente ingresa un código y ve exactamente dónde está su paquete. UX simple, ingeniería compleja.

Cada escaneo físico — en el depósito de origen, en una planta de clasificación, en aduana, en el hub de destino, en la mano del courier — genera un evento. Ese evento debe:

  1. Escribirse de forma durable (no se puede perder un escaneo)
  2. Estar disponible para consulta en segundos (los clientes actualizan el tracking constantemente)
  3. Ser consistente en secuencia (los eventos deben reflejar el orden real en que ocurrieron)
  4. Ser atribuible y auditable (para disputas, aduana y cumplimiento normativo)

Esto es Event Sourcing por necesidad, no por elección. El estado de un paquete en cualquier momento es el resultado de aplicar todos sus eventos en secuencia. No se almacena “estado actual: en tránsito” — se almacena cada escaneo y se deriva el estado actual desde el log de eventos.

// El estado de un paquete es una proyección de su stream de eventos
type TrackingEvent = {
  eventId: string;
  trackingNumber: string;
  timestamp: Date;
  type: 'RECOGIDO' | 'LLEGADA_HUB' | 'SALIDA_HUB' | 'EN_ADUANA' |
        'EN_REPARTO' | 'ENTREGADO' | 'INTENTO_FALLIDO' | 'EXCEPCION';
  location: { facility: string; country: string; coordinates?: [number, number] };
  scannedBy: string; // ID del dispositivo/operador
};

function derivarEstadoPaquete(events: TrackingEvent[]): EstadoPaquete {
  // Estado actual = último evento relevante, ordenado por timestamp
  return events
    .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime())
    .reduce(aplicarEvento, estadoInicial);
}

La razón por la que esto importa: un escaneo no se puede modificar. Un escaneo es un hecho físico. Si un paquete fue escaneado en Frankfurt a las 14:32, ese evento es inmutable. Se puede agregar un evento de corrección después, pero no reescribir la historia. Event Sourcing es el modelo natural para este dominio.

Descomposición del sistema: qué servicios existen

Una plataforma logística a esta escala casi con certeza corre una arquitectura de microservicios, no porque los microservicios sean intrínsecamente mejores, sino porque los requisitos operacionales de los distintos subsistemas son radicalmente diferentes.

La descomposición probablemente se ve así:

  • Servicio de Tracking: Ingiere eventos de escaneo y responde consultas de seguimiento. Alto volumen de escritura, altísimo volumen de lectura, sensible a la latencia.
  • Servicio de Gestión de Envíos: Maneja el ciclo de vida de un envío — creación, modificación, cancelación. Menor volumen, requiere consistencia fuerte.
  • Servicio de Routing y Despacho: Calcula rutas, asigna couriers, gestiona la logística de última milla. Computacionalmente intensivo, necesita datos de tráfico en tiempo real.
  • Servicio de Aduanas y Cumplimiento: Gestiona documentación, cálculo de aranceles y regulaciones por país. Alta complejidad, lógica específica por país, actualizaciones lentas.
  • Servicio de Notificaciones: Envía SMS, email y push a los clientes. Alto throughput, fire-and-forget, consistencia eventual aceptable.
  • Servicio de Facturación: Gestiona precios, billing e invoices para clientes enterprise. Consistencia fuerte, auditable, transacciones ACID.
  • Servicio de Identidad y Clientes: Autenticación, perfiles, libreta de direcciones. Estándar, bajo throughput.

Cada uno tiene SLAs, modelos de datos y requisitos de escalado fundamentalmente distintos. El Servicio de Tracking maneja millones de escrituras por hora en pico. El de Facturación procesa muchas menos transacciones pero cada una tiene que ser exactamente correcta. Acoplarlos en un monolito significaría escalar todo para el peor caso de cada uno.

Async por defecto: Kafka como sistema nervioso

La decisión arquitectónica más importante en un sistema así es cómo se comunican los servicios. Con microservicios que necesitan reaccionar a eventos del mundo real — un escáner se cae, un vuelo se atrasa, aduana pone un hold — HTTP síncrono es el default equivocado.

DHL casi seguro usa una plataforma de streaming de eventos distribuida — Kafka siendo el estándar de la industria a esta escala — como columna vertebral de la comunicación entre servicios.

Por qué Kafka específicamente:

  • Durabilidad: Los eventos se persisten en disco con retención configurable. Un evento de escaneo no se pierde porque un servicio downstream estuviera temporalmente caído.
  • Replay: Si una proyección o read model se corrompe, se puede reconstruir reproduciendo el event log desde el principio. Exactamente así funcionan los read models de Event Sourcing.
  • Fan-out: Un único evento PaqueteEscaneado publicado en Kafka puede ser consumido independientemente por el Servicio de Tracking (para actualizar estado), el Servicio de Notificaciones (para enviar update al cliente), la plataforma de Analytics (para actualizar dashboards) y el Servicio de Facturación — sin que ninguno esté acoplado a los demás.
  • Backpressure: Si el Servicio de Notificaciones es lento durante un pico de Black Friday, Kafka actúa como buffer. Los consumidores procesan a su propio ritmo sin que el productor se frene o falle.

El flujo de un escaneo luce aproximadamente así:

[Dispositivo Escáner]
      ↓ HTTP POST (API interna)
[Servicio de Ingesta de Escaneos]
      ↓ publica evento PaqueteEscaneado
[Kafka Topic: parcel-events]
      ↓ consumido por múltiples servicios en paralelo
[Proyector de Tracking] → actualiza Redis/caché de lectura
[Servicio de Notificaciones] → envía SMS/email al cliente
[Analytics Sink] → stream hacia el data warehouse
[Servicio de Aduanas] → verifica si el evento dispara un hold

Este diseño hace que el camino de ingesta de escaneos sea rápido (escribir a Kafka, acknowledge), y todo el procesamiento downstream es asíncrono. La página de tracking del cliente lee desde una proyección — un read model precalculado, no desde el event log crudo.

El problema del read model: CQRS en práctica

El Servicio de Tracking no puede consultar el event log crudo en cada request del cliente. Con millones de paquetes y cientos de eventos cada uno, reconstruir el estado en cada consulta no es viable.

Aquí entra CQRS. El lado de escritura publica eventos; un consumidor de proyección separado se suscribe y mantiene una vista denormalizada y optimizada para consulta.

Esa vista probablemente vive en Redis para datos calientes — paquetes activos que los clientes están siguiendo ahora mismo. Un paquete entregado hace seis meses se desaloja a cold storage (S3, BigQuery o un data warehouse) y solo se necesita para resolución de disputas o analytics.

Camino de escritura:  Escáner → Kafka → EventStore (append-only, ej. PostgreSQL/Cassandra)
Camino de lectura:    Cliente → API → Proyección Redis (estado actual, últimos 10 eventos)
                                     ↓ cache miss
                                     Cold storage (historial completo)

El consumidor de proyección aplica cada evento a Redis de forma atómica. Si Redis cae y vuelve, el consumidor reproduce el topic de Kafka desde el último offset confirmado para reconstruirlo. El event log es la fuente de verdad; el read model es derivado y descartable.

Optimización de rutas: el problema más difícil del stack

El tracking recibe más atención por parte del usuario, pero la logística de última milla es donde vive la verdadera complejidad computacional.

El problema central es una variante del Vehicle Routing Problem (VRP): dados N couriers con restricciones de capacidad, M paradas de entrega con ventanas horarias y una red vial con tráfico en tiempo real — encontrar la asignación óptima de paradas a couriers y la secuencia óptima para cada ruta.

El VRP es NP-hard. No se puede resolver exactamente para cientos de paradas y decenas de vehículos en tiempo real. Lo que se hace en cambio:

  • Heurísticas y metaheurísticas: Algoritmos como Clarke-Wright savings, or-opt y simulated annealing para encontrar soluciones suficientemente buenas rápidamente.
  • Solvers basados en constraints: Google OR-Tools es usado en producción por muchas compañías logísticas para exactamente este tipo de problema. Maneja ventanas horarias, capacidades de vehículos y restricciones de depósito.
  • Re-optimización en tiempo real: Las rutas no están fijas al inicio del día. Cuando un courier marca una entrega como fallida, o el tráfico genera un retraso, el motor de routing re-optimiza las paradas restantes.
  • Machine learning para predicción de demanda: Datos históricos entrenan modelos para predecir volúmenes de entrega por zona, permitiendo el pre-posicionamiento de vehículos y personal antes de los períodos pico.

El servicio de routing no es event-driven de la misma forma que el tracking. Es request-driven y compute-intensivo — más cercano a un batch job que corre continuamente con actualizaciones incrementales que a un procesador de eventos reactivo.

Infraestructura global y resiliencia

220 países significa que no se puede correr una base de datos centralizada y servir al mundo desde ahí. La latencia sola hace inviable la centralización: un courier en Nairobi escaneando un paquete no puede esperar un round-trip a un data center en Frankfurt para completar el escaneo.

La arquitectura probablemente está particionada por región:

  • Clusters regionales: Américas, Europa/África, Asia-Pacífico — cada uno con su propio cómputo, almacenamiento y clusters de Kafka.
  • Replicación de eventos: Los eventos de alto valor (envíos internacionales, handoffs de aduana) se replican cross-region para visibilidad global.
  • Ingesta en el edge: Los eventos de escaneo se escriben al cluster regional más cercano y se propagan hacia afuera. Un escaneo en Bangkok no está bloqueado por la disponibilidad europea.

Para resiliencia, el camino crítico es la ingesta de escaneos. Si el cluster de Kafka en una región está degradado, el ingestor de escaneos necesita encolar localmente y reintentar. Que el dispositivo de un courier bufferee algunos cientos de escaneos durante una caída de red es aceptable; perder esos escaneos permanentemente no lo es.

La API pública de tracking se sirve a través de un CDN para respuestas estáticas o casi estáticas (un estado de paquete que no cambió en dos horas no necesita un hit a la base de datos en cada request), con TTLs cortos e invalidación de caché disparada por nuevos eventos.

Observabilidad a esta escala

No se puede debuggear un sistema distribuido de este tamaño solo con logs. El stack de observabilidad necesita tres capas trabajando juntas:

  • Distributed tracing: Un único request de cliente (“¿dónde está mi paquete?”) puede tocar el API gateway, el servicio de tracking, un caché Redis y — en un cache miss — el event store. OpenTelemetry con trace IDs propagados a través de cada límite de servicio es el estándar para correlacionar todo esto en un único trace.
  • Métricas y alertas: Monitoreo de SLA por servicio (latencia P99 en consultas de tracking, error rates en ingesta de escaneos), no solo uptime. Un servicio puede estar “arriba” pero degradado.
  • Logging estructurado: Cada línea de log es JSON con un schema consistente — trace ID, nombre del servicio, parcel ID cuando corresponde. Los logs no estructurados no escalan operacionalmente; no se puede hacer grep a través de terabytes de logs de escaneos de couriers.

El on-call en un sistema como el de DHL no está reaccionando a downtime — está reaccionando a señales de anomalía. Un aumento del 3% en eventos de “escaneo fallido” en un hub específico puede indicar un problema de firmware en un dispositivo antes de que alguien llame a soporte.

Mi perspectiva personal

Lo que me parece genuinamente interesante de la arquitectura de DHL es que no es innovadora en el sentido de usar tecnología de vanguardia — es innovadora en el sentido de aplicar patrones bien conocidos a una escala que expone cada debilidad de esos patrones.

Event Sourcing es elegante en un tutorial. Con 1.8 mil millones de paquetes por año, estás pensando en compactación del event log, tiempos de rebuild de proyecciones, y semántica de exactly-once delivery en los consumidores de Kafka. CQRS es sencillo hasta que tu read model se desincroniza del event log en el momento en que 50,000 clientes están actualizando su página de tracking al mismo tiempo.

La parte que me resultaría más difícil de construir no es el sistema de tracking — ese es un problema bien definido con soluciones conocidas. Es la capa de aduanas y cumplimiento: 220 países significa 220 regímenes regulatorios distintos, aranceles, listas de artículos prohibidos y requisitos de documentación, cada uno cambiando a su propio ritmo. Eso es menos un problema de ingeniería y más un problema de gestión del conocimiento que la ingeniería tiene que codificar. Lo modelaría como un motor de reglas con configuración específica por país en lugar de lógica hardcodeada, pero la carga de mantenimiento es enorme de todas formas.

Si diseñara esto desde cero hoy, haría una apuesta distinta en la capa de datos: en lugar de bases de datos relacionales tradicionales para el event store, evaluaría Apache Cassandra o un event store de propósito específico como EventStoreDB para el log central de eventos de tracking. Los patrones de escritura son puro append, los patrones de lectura son por partition key (número de tracking), y el volumen justifica un motor de almacenamiento optimizado exactamente para ese patrón de acceso en lugar de una base de datos relacional de propósito general forzada bajo esa carga.

La arquitectura de DHL no es el caso de estudio más glamoroso, pero sí uno de los más honestos. Sin historia de product-market fit viral, sin crecimiento explosivo que justifique un rewrite. Solo la ingeniería poco glamorosa de asegurarse de que un paquete que salió de Buenos Aires realmente llegue a Rotterdam, y que alguien en ambas ciudades pueda ver exactamente dónde está en cualquier momento. Eso es más difícil de lo que parece.