---
title: "Diseño de una cola de capacidad multidimensional"
date: 2020-12-02
language: es
topics: ["data", "ecommerce", "lemmid-store", "payments", "server", "work"]
source_url: https://willem.com/es/2020-12-02_diseno-de-una-cola-de-capacidad-multidimensional/
---
# Diseño de una cola de capacidad multidimensional

*Gestionar la capacidad de cocina, reparto y recogida*

> Este mes tuve que añadir dimensiones a un mecanismo de cola de capacidad. La app de pedidos de comida que creé tenía que poder limitar la capacidad según el número de pedidos, el contenido de cada pedido y el tipo de entrega (para llevar o a domicilio). Sigue leyendo para ver cómo usé una arquitectura Lambda para conseguirlo.

## Limitar la capacidad

Aunque quizá no te des cuenta, una de las funciones más potentes de la app_de_pedidos_de_comida_que_creé (https://willem.com/blog/lemmid-store/) es poder limitar el número de pedidos. Suena contradictorio, pero al limitar los pedidos que se pueden hacer, mis clientes (los restaurantes y dueños de tiendas que usan mi app) se aseguran de que los pedidos se preparen y se entreguen como es debido.

## La complejidad de tres pizzas

Supongamos que quieres pedir tres pizzas. La app tiene que saber:

- ¿Quieres **recogerlas** o que te las **lleven a casa**?

- ¿A **qué hora?** ¿Cuándo tienen que entrar en el horno?

- ¿Hay **sitio suficiente** para *tres* pizzas **en el horno** *a esa hora*?

- Si has elegido recogida: ¿**podemos atenderte sin problemas** a esa hora?

- Si has elegido entrega a domicilio: ¿**hay un repartidor o repartidora disponible** para llevarte las pizzas?

- ¿**Todo en orden?** Entonces **paga, por favor**, mientras **te reservamos las pizzas** en el horno y en el reparto o la recogida. ¿Falla el pago? ¿Te lo has pensado mejor? Entonces «deshacemos» toda la planificación y esperamos a que otra persona pida pizzas.

![Comer pizza durante el desarrollo es como se «vive el algoritmo»](https://willem.com/global/images/e9229d524bd1.webp)

## Tres dimensiones de capacidad

Sobre todo a «la hora de cenar» hay riesgo de que demasiada gente pida comida a la vez. La app aguanta el tráfico extra gracias a su arquitectura_muy_escalable (https://willem.com/en/2020-05-11_scalable-application-design-without-magic/), pero los límites físicos siguen ahí, por ejemplo:

- **capacidad de cocina**: cuántos platos se pueden preparar a la vez

- **capacidad de recogida**: cuánta gente se puede atender a la vez sin riesgos, según las restricciones por el COVID-19

- **capacidad de reparto**: cuántos pedidos se pueden entregar, según el tráfico, la distancia y la ruta

![Tres límites de capacidad: cocina / reparto / recogida](https://willem.com/global/images/db564dd30d9f.webp)

### Distintas pero interdependientes

Estos tres tipos de límites dependen unos de otros. Por ejemplo, un solo pedido grande para recoger puede ocupar toda la capacidad de la cocina y dejar sin disponibilidad también el reparto. Aún se podrían aceptar pedidos más pequeños para llenar la cocina, mientras que ya no habría capacidad para pedidos grandes. Por eso hace falta un mecanismo que mire *todas* las métricas a la vez para determinar la disponibilidad de pedidos nuevos, incluido el contenido de un pedido que aún no se ha hecho (por ejemplo, lo que llevas en el carrito).

### Diferencias en la granularidad del intervalo de tiempo

Lo habitual es definir un límite físico con un intervalo de tiempo, por ejemplo *«la cocina puede preparar 10 platos cada 15 minutos»*. Se complica cuando otros límites físicos usan intervalos de otra duración. La cocina puede estar limitada cada 15 minutos, pero la capacidad de reparto podría usar un intervalo más largo (por ejemplo, 3 entregas cada 30 minutos). De algún modo, el mecanismo tiene que saber manejar estas diferencias de granularidad.

### Pedidos por adelantado

A este reto se suma que la gente puede hacer sus pedidos por adelantado. Se puede elegir una franja horaria futura y reservar la capacidad necesaria. A veces veo a gente que pide con días de antelación para asegurarse de recibir la comida un día y una hora concretos. Eso significa que cada pedido tiene (al menos) dos horas asociadas: la del pedido y la de la entrega.

### Reservar y liberar capacidad

La mayoría de los dueños de negocios exigen pago online en los pedidos online, para asegurarse de que todos los pedidos son de verdad y de que no se desperdicia capacidad (ni comida) en gente que no aparece o en pedidos fantasma. El problema_de_los_pagos_online (https://willem.com/en/2020-03-25_designing-and-implementing-a-micro-payment-system/) es que no todas las transacciones salen bien. A veces se rechaza un pago con tarjeta de crédito o débito (por ejemplo, por falta de fondos). A veces la gente simplemente cierra la app antes de terminar el pedido. El sistema debe reservar la capacidad necesaria para un pedido mientras la persona hace el pago online, y liberarla si el pedido se abandona o el pago falla.

### Mucho volumen, mucha velocidad

Por último, pero no menos importante, el mecanismo tiene que ser fiable y rápido. La disponibilidad de un pedido debe determinarse en cuestión de milisegundos y con mucha concurrencia, porque en hora punta hay muchos clientes distintos conectados a la app. Cuando alguien hace un pedido, afecta a la disponibilidad de todos los demás pedidos (potenciales). Y no todo el mundo tiene buena conexión: hay quien usa la app en móviles con mala cobertura o con wifi. El sistema debe funcionar bien en condiciones muy variadas y con mucha gente conectada.

## Arquitectura Lambda

La arquitectura_Lambda (https://en.wikipedia.org/wiki/Lambda_architecture) es una forma de manejar grandes cantidades de datos combinando el procesamiento en flujo y el procesamiento por lotes. Una arquitectura Lambda se basa en un modelo de datos con una fuente de datos inmutable en la que solo se añade, a menudo formada por eventos con marca de tiempo.

### Datos inmutables

Es una base muy importante del mecanismo: todos los datos llevan marca de tiempo y *nunca* cambian. En lugar de modificar registros existentes, simplemente se añaden registros nuevos que sustituyen a los anteriores. Esto permite mucha concurrencia y algoritmos distribuidos, porque la sincronización es más sencilla: solo hay que buscar datos nuevos (¡y no nuevos y modificados!).

### Capa de velocidad (tiempo real)

El procesamiento en flujo, o capa de velocidad o de tiempo real, de una arquitectura Lambda está pensado para dar respuestas rapidísimas a las necesidades de datos en tiempo real. Mantiene vistas y métricas basadas en el procesamiento de eventos en tiempo real.

### Capa de precálculo

El procesamiento por lotes, o capa de precálculo, ofrece una base completa y precisa de todos los datos del sistema. Procesar grandes cantidades de datos lleva tiempo, así que el procesamiento por lotes no es lo bastante rápido para hacerlo todo al instante. Por eso, en una arquitectura Lambda, las dos capas trabajan en tándem.

## Implementación

Los servidores backend que gestionan los pedidos de la app trabajan con un flujo de datos de eventos con marca de tiempo. En esencia, cada pedido es un evento del sistema. Cada pedido se guarda con su fecha y hora de pedido, su fecha y hora de entrega (la hora programada de reparto o recogida), los datos del cliente y el contenido del pedido (número de platos, etc.).

### Reservar y liberar capacidad

Si falla el pago o se abandona un pedido, se añade al flujo de datos un pedido «negativo». Sus métricas negativas neutralizan cualquier efecto sobre los límites de capacidad. Por ejemplo: para cancelar un pedido de tres pizzas, se crea en el flujo un pedido negativo de «menos tres pizzas» con la misma marca de tiempo de entrega. Es una forma elegante de asegurar que la capacidad se reserva y se libera.

### Métricas multidimensionales en tiempo real

En lugar de reducir la capacidad a simples contadores, el servidor backend usa los datos *reales* del flujo para responder a las consultas de métricas en tiempo real. Así el sistema puede manejar las diferencias de granularidad del intervalo de tiempo.

![El corazón del mecanismo es un flujo de eventos de pedidos, y las consultas se resuelven mirando ese flujo: ¿cuántas pizzas estarán listas a las 18:00?](https://willem.com/global/images/1e59e0b31de7.webp)

La primera vez que llega una consulta sobre una métrica, el sistema carga datos históricos con un mecanismo por lotes para inicializar el modelo, que después mantiene actualizado en tiempo real. Esa métrica se mantiene al día mirando todos los pedidos nuevos que entran al sistema mientras sigan llegando consultas sobre ella. Cuando ya no se piden esos datos, el modelo se descarga. Si vuelve a hacer falta, el proceso se repite.

Lo mejor de este enfoque es que la mayoría de las consultas se responden en milisegundos, porque se leen directamente de la memoria RAM, sin pasar por la base de datos ni el disco. Así se consigue un rendimiento altísimo «solo cuando hace falta». Y funciona bien con la app de pedidos, porque no *todos* los intervalos de tiempo son igual de populares.

La app consulta una y otra vez a un servidor central de capacidad que mantiene las métricas de la cola. Después filtra las franjas disponibles mirando lo que la persona está eligiendo para su pedido. Elegir una franja (probablemente) disponible ocurre, por tanto, en el lado del cliente, lo que reduce la carga del backend. Cuando se hace el pedido, el backend reserva la capacidad actualizando las métricas centrales de la cola, y así nadie más puede quedarse con el mismo hueco.

## Conclusión

Los mecanismos de cola pueden ser complicados cuando hay varias métricas que controlan la velocidad. Tómate el tiempo de modelarlo bien y piensa qué datos *necesitas de verdad* para hacer las comprobaciones de la cola.

¡Ya sabes que pedir tres pizzas no es tan sencillo! Garantizar que lleguen calientes y a tiempo es un gran reto técnico, además de ser buen cocinero. ¡Menos mal que *yo* no me dedico a lo segundo, ja!
