Redis en clúster: por qué un MSET se convierte en decenas de viajes de red
Un ingeniero de una app de reparto intentó cachear tiempos de conducción en Redis y descubrió que el reparto por slots convierte una lectura en una tormenta de peticiones individuales.
Un ingeniero que trabaja en la asignación de repartos de una aplicación de gig economy cuenta por qué su caché de tiempos de conducción en Redis no escaló: sus claves caían cada una en un slot distinto del clúster, así que lo que debía ser una lectura se convertía en decenas de viajes de red. No era un fallo del cliente ni de los ajustes. Era el diseño de las claves.
El servicio decide a qué repartidor se le ofrece cada pedido, y para eso necesita tiempos de conducción reales, no distancia en línea recta: alguien al otro lado del río parece cercano en un mapa y no lo está. Eso lo resuelve un motor de rutas, que es con diferencia la mayor fuente de latencia del sistema. Cuando ese motor va lento, el producto entero va lento.
Deduplicar antes de cachear
El volumen no se elige; la escala es un requisito. Lo que sí se puede aprovechar es que las estimaciones se repiten: dos repartidores a una manzana de distancia generan peticiones casi idénticas, y los restaurantes no se mueven. La caché tenía que ser compartida, porque el proceso que calcula una estimación rara vez es el que la necesita después.
Cachear coordenadas en bruto no sirve. Hay que pasarlas por H3, que divide el planeta en hexágonos con un identificador estable por celda y resolución. Origen y destino se ajustan a su hexágono y toda coordenada dentro de esas dos celdas colapsa en una sola clave. La resolución es un dial de precisión y va dentro de la propia clave, de modo que varias pueden convivir en la caché sin vaciarla. La clave era origen:destino:resolución, el valor la estimación, y la escritura se hacía con MSET.
Los 16.384 slots
No escaló. El clúster de Redis reparte el espacio de claves en 16.384 slots entre los nodos primarios, y un comando multikey como MSET solo es legal si todas las claves caen en el mismo. El autor lo descubrió mirando trazas: una sola lectura aparecía como decenas de spans MGET de una clave cada uno, y probablemente el colector de OpenTelemetry estaba descartando más, porque el recuento total de claves no cuadraba con los spans supervivientes. La métrica de latencia máxima de lectura se salía de cualquier peor caso previsto.
A qué slot va una clave no es configurable ni aleatorio: es CRC16(clave) mod 16.384. Dos claves que difieren en un carácter acaban en slots sin relación entre sí. Con un origen y un destino distintos en cada clave, la probabilidad de que dos compartieran slot era de una entre 16.384. El cliente no hacía nada raro: agrupa las claves por slot y manda cada grupo por su propio cable, que es exactamente lo correcto. No hay combinación de ajustes que haga rápido un MGET por clave. La enfermedad también estaba en la escritura, con un error CROSSSLOT o un SET y un viaje de red por clave.
El arreglo y su trampa
Redis tiene una salida para esto: las hash tags. Si una clave lleva texto entre llaves, Redis aplica el hash solo a lo que hay dentro de las llaves e ignora el resto. Así se decide a mano qué claves comparten slot. Doce claves con una etiqueta común pasan de doce viajes a tres, uno por nodo, corriendo a la vez.
La trampa está en qué se mete entre llaves. Lo tentador es etiquetar por hexágono de origen, para agrupar todas las rutas que salen de una zona. Eso construye un punto caliente: el centro a la hora de la cena es un hexágono, una etiqueta, un slot, un nodo, y ese nodo se incendia.
El caso tiene poco de exótico. Cualquier caché con claves compuestas sobre Redis en clúster hereda esta aritmética, y el número de round trips por operación lo decide el diseño de la clave antes de que se escriba una línea de lógica de negocio. No hay ajuste posterior que lo arregle.
