Cuando lo "más nuevo" te hace ir más lento
Conviva, la plataforma de analítica que según datos de la propia compañía monitorea cerca de cinco billones de eventos digitales al día, reemplazó mmap por io_uring en su motor de consultas escrito en Rust. La promesa era saltarse la página de caché del kernel y alcanzar el techo del hardware. El resultado: el nuevo motor terminó 60% más lento que el original (21,8 segundos frente a 13,6 segundos en una consulta en frío de 14 días de datos). Esta es la primera parte del post-mortem que el equipo de ingeniería publicó en su blog técnico.
El problema original: mmap no escalaba bajo carga concurrente
Conviva procesa eventos de plataformas de streaming y editores digitales — entre sus clientes históricos figuran HBO, ESPN, NBC, Disney y Hulu, según la ficha de la compañía en Wikipedia — y almacena los datos en archivos Arrow IPC de 3 a 5 GB sobre NVMe local. El motor de consultas, construido sobre DataFusion, Arrow, Rust, Rayon y Tokio, mapeaba originalmente los archivos en memoria con mmap para aprovechar el acceso aleatorio de copia cero que ofrece el formato Arrow.
Bajo carga ligera funcionaba bien: consultas servidas en segundos. El problema apareció con concurrencia real. Los números que el equipo reporta en el artículo original:
👥 ¿Quieres ir más allá de la noticia?
En nuestra comunidad discutimos las tendencias, compartimos oportunidades y nos ayudamos entre emprendedores. Sin humo, solo acción.
👥 Unirme a la comunidad- p95 saltó de ~30 s a 150 s+ bajo carga concurrente
- La caché de páginas del sistema se redujo — cada pod consumía más RAM como asignaciones privadas y menos como caché compartida
- Millones de page faults por segundo
- 100% de contención de lock a nivel de kernel según
perf record - 1 pod ganaba a 4 pods por 41% en consultas de 14 días: todos los pods peleaban por la misma caché compartida
El diagnóstico: la página de caché de mmap es estado compartido implícito entre todos los procesos del host. Cada pod pierde control sobre el recurso que más impacta la latencia de lectura.
Por qué io_uring parecía la respuesta
El hardware de Conviva (caja de 192 cores, ~750 GB de RAM, 32 NVMe en RAID-0) tenía un techo de ~21 GB/s según pruebas con fio. mmap apenas entregaba 3,44 GB/s en producción: 16% del techo. La distancia parecía ganable con iouring, la interfaz de I/O asíncrono de Linux, especialmente combinada con ODIRECT para evitar la caché del kernel.
El plan fue concreto: usar la crate compio (un wrapper nativo de Rust sobre iouring), leer con ODIRECT, decodificar Arrow en línea y coordinar con Tokio. Una capa intermedia — el Batch Materialization Layer (BMT en sus logs) — expone dos operaciones: prefetch(batch, columns) para lanzar lecturas concurrentes y materialize(batch, column) para devolver los bytes ya en caché.
Lo que salió mal en la primera iteración
El equipo disparó las ~40 lecturas de columnas a la vez desde el primer momento — un future de compio por columna, todos enviados en paralelo. Los resultados en frío sobre la caja Linux de producción:
- Major faults: 3.647 (io_uring) frente a 128.957 (mmap) — 35× menos
- Minor faults: 8,6 millones (io_uring) frente a ~1 millón (mmap) — 8× más
- Tiempo total de consulta: 21,8 s frente a 13,6 s — 60% más lento
Resolvieron el problema que io_uring prometía resolver (los major faults colapsaron), pero intercambiaron una clase de fallo por otra. Lo que el equipo de Conviva describe como "el meandro": tres ajustes obvios que no llegaron al salto de rendimiento prometido.
Primer ajuste: activar O_DIRECT. Sin caché del kernel, lectura directa por DMA al buffer de la aplicación. Mejora modesta: de 21,8 s a ~19 s.
Segundo ajuste: construir arrow::Buffer directamente desde los bytes de io_uring. El path por defecto (Buffer::from_slice_ref) hace un memcpy que dispara un page fault menor por cada página de 4 KiB del destino. Saltarse el memcpy bajó de 19 s a ~16 s, pero dejó código "lo bastante feo para que cualquier revisor lo marcara", según el propio equipo.
Tercera iteración pendiente: un pool de buffers pre-faulteados donde io_uring escribe y Arrow copia desde allí. Lo dejaron registrado como trabajo futuro, sin completar.
Aún con esos ajustes, 16 s contra 13,6 s del baseline. Las promesas de los blogs sobre io_uring no se cumplieron en su arquitectura.
La lección que no estaba en los papers
El artículo de Conviva no termina con una victoria: termina con el equipo reconociendo que su Batch Materialization Layer "hacía cinco trabajos en una capa", que los 40 SQEs concurrentes fueron probablemente el problema real, y que necesitan "datos sobre qué están haciendo las submissions de io_uring momento a momento" antes de seguir. La Parte 2 promete el rediseño arquitectónico y la historia de gestión de memoria que terminó importando tanto como el I/O.
Tres cosas llaman la atención del proceso:
- Probaron primero en macOS. Sabían que kqueue no es iouring, pero les sirvió para validar la abstracción de compio y detectar regresiones obvias antes de pedir tiempo en las cajas Linux de producción. El frío allí ya mostraba la forma: 235 major faults con iouring contra 16.103 con mmap.
- Midieron con perf y bpftrace, no con corazonadas. El análisis off-CPU vía bpftrace mostró que 30,9% del tiempo era futex y 29,3% era preempted — el kernel estaba preemptando sus workers con sus propios hilos de readahead. El I/O de disco apenas representaba 6,9% del tiempo perdido.
- El benchmark inicial fue honesto sobre su propia limitación. Compararon tiempo total, no solo throughput, y reconocieron que "el internet está lleno de posts declarando que io_uring gana a mmap. Nosotros teníamos una implementación que había ido hacia atrás".
Qué significa esto para tu startup
Para un founder técnico, o para un CTO que está decidiendo migrar una pieza de infraestructura, este caso de Conviva es un recordatorio de tres cosas que no son obvias hasta que te pasan:
- El benchmark de tu laptop nunca prueba correctness, pero sí puede detectar regresiones obvias antes de quemar tiempo en producción. Conviva habría llegado a la conclusión de "60% más lento" sin haber tocado la caja de 192 cores si hubiera confiado más en su prueba de macOS.
- Antes de migrar, mide dónde está realmente tu cuello de botella. Conviva descubrió vía bpftrace que el I/O de disco era solo 6,9% del tiempo perdido; 60% era futex y preempted. Migrar a io_uring sin haber visto eso era tratar el síntoma equivocado.
- "Más moderno" no es lo mismo que "mejor para tu caso". La arquitectura de Arrow IPC con mmap estaba perfectamente adaptada al formato. El problema no era la tecnología: era la concurrencia y la presión de memoria compartida entre pods.
Dos acciones concretas que puedes implementar esta semana:
- Audita una decisión técnica reciente con bpftrace o perf. Si tomaste una decisión de arquitectura en los últimos seis meses basándote solo en benchmarks sintéticos, replica una traza off-CPU de 60 segundos en producción. Lo que mides suele no ser lo que creías que medías — y es exactamente lo que le pasó al equipo de Conviva.
- Construye un "criterio de no-migración" antes de migrar. Define de antemano qué métrica tendría que mejorar la nueva tecnología para justificar el cambio, y qué nivel de regresión aceptas mientras iteras. Sin ese criterio escrito, las migraciones se justifican a sí mismas con mejoras incrementales que no compensan el coste de reescritura.
Conviva presentará la Parte 2 — la solución que encontraron — en P99 CONF los días 21 y 22 de octubre de 2026. Mientras tanto, el caso completo vale una lectura: el post original incluye el código y los perfiles que respaldan cada número.
Fuentes
- We Replaced MMAP with Io_uring in Our Rust Query Engine. It Got Slower — Conviva Engineering
- Conviva — Wikipedia
👥 ¿Quieres ir más allá de la noticia?
En nuestra comunidad discutimos las tendencias, compartimos oportunidades y nos ayudamos entre emprendedores. Sin humo, solo acción.
👥 Unirme a la comunidad














