- recall es un servicio que ofrece bots para reuniones a cientos de empresas y opera una infraestructura a gran escala en AWS
- Para ofrecer un servicio eficiente en costos, buscaban aprovechar al máximo el rendimiento del hardware
- Durante los últimos años, como la disponibilidad de GPUs de los proveedores cloud ha sido inestable, procesaban el video en CPU en lugar de GPU
- Al perfilar los bots que usan Chromium headless, descubrieron que la mayor parte del tiempo de CPU no se iba en el procesamiento de video (codificación/decodificación), sino en las funciones de copia de memoria
__memmove_avx_unaligned_erms y __memcpy_avx_unaligned_erms
memmove y memcpy son funciones de la biblioteca estándar de C (glibc) para copiar bloques de memoria
memmove maneja algunos casos especiales relacionados con la copia de memoria en rangos superpuestos, pero ambas pueden clasificarse como funciones de “copia de memoria”
- El sufijo
avx_unaligned_erms indica que están optimizadas para sistemas con soporte de Advanced Vector Extensions (AVX) y también para accesos a memoria no alineados
erms significa Enhanced REP MOVSB/STOSB, una optimización para mover memoria rápidamente en procesadores Intel modernos. Puede entenderse como “una implementación más rápida para ciertos procesadores”
- Según el perfilado, quien más llamaba a estas funciones era el cliente WebSocket en Python que recibía los datos
- Después venía la implementación de WebSocket de Chromium, que enviaba los datos
Problemas de WebSocket
- Usaban un servidor WebSocket local para enviar datos de video sin procesar desde el entorno JS de Chromium hacia el codificador
- Un stream de video raw de 1080p a 30fps requiere un ancho de banda alto, de más de 93 MB por segundo
- El uso de WebSocket generaba mucho costo computacional, principalmente por la fragmentación y el masking
- Fragmentación: la implementación de WebSocket de Chromium fragmenta en múltiples frames los mensajes mayores a 131 KB. Un frame de video raw de más de 3 MB terminaba dividido en más de 24 frames separados
- Masking: por motivos de seguridad, WebSocket aplica masking a todos los frames enviados del cliente al servidor. Con volúmenes de más de 100 MB por segundo, esto se vuelve un overhead significativo
Búsqueda de alternativas
- Como con las APIs del navegador era difícil implementar algo con mucho mejor rendimiento que WebSocket, decidieron hacer un fork de Chromium e implementar una función personalizada
- Consideraron 3 alternativas: raw TCP/IP, Unix Domain Socket y Shared Memory
- TCP/IP: evita los problemas de fragmentación/masking de WebSocket, pero el tamaño máximo de paquete es pequeño, así que la fragmentación sigue existiendo. Además, hay overhead por la copia hacia espacio de kernel
- Unix Domain Socket: permite saltarse por completo el stack de red, pero aun así requiere copiar datos entre espacio de usuario y espacio de kernel
- Shared Memory: memoria a la que varios procesos pueden acceder al mismo tiempo. Chromium puede escribir directamente en la memoria compartida y el codificador leerla de inmediato, sin copias intermedias
Implementación de transporte basada en memoria compartida
- Implementaron la memoria compartida con forma de ring buffer para leer y escribir datos de manera continua
- Requisitos: lock-free, múltiples productores/un solo consumidor, tamaño de frame variable, lectura zero-copy, compatibilidad con sandbox y señalización de baja latencia
- Evaluaron implementaciones existentes de ring buffer, pero como ninguna cumplía todos los requisitos, decidieron crear la suya
- Para soportar lectura zero-copy, dividieron los punteros en tres: write, peek y read
- Para garantizar seguridad entre hilos, usaron operaciones atómicas y, para avisar cuando había datos nuevos o espacio libre, usaron named semaphores
- Con la implementación del ring buffer basado en memoria compartida y otras optimizaciones, lograron reducir hasta en 50% el uso de CPU de los bots. Al final, ahorraron más de un millón de dólares al año en costos de AWS.
3 comentarios
Comentarios de Hacker News
Esta es la historia típica de una startup que elige un atajo "suficientemente bueno" y luego optimiza más adelante.
Hay comentarios de sorpresa por el alto ancho de banda de los datos de video en bruto.
Hay opiniones de que no fue un problema de AWS, sino de desperdiciar ciclos de CPU.
Señalan que el MTU y el MSS de las redes TCP/IP son pequeños en comparación con el tamaño de los frames de video.
Hay quien opina que usando Mojo de Chromium no tendrían que preocuparse por código específico de cada plataforma.
Hay comentarios de que el problema no era la red, sino la falta de comprensión sobre los códecs de video.
Elogian la transparencia y dicen que también les gustaría ver transparencia en el precio del producto.
Explican que el masking del protocolo WebSocket fue un intento de resolver problemas de intermediarios.
Señalan que es extraño transmitir datos de video sin comprimir.
Comentan que les sorprendió el enfoque inicial de transmitir video en bruto por WebSocket.
Desde el principio lo desarrollaron mal...
"Dicen que el enfoque inicial de transmitir video sin procesar por WebSocket es sorprendente." Coincido con eso.