Spotify nos cuenta cómo diagnosticar errores en cadena
La tragedia no es siempre el final de todas las batallas: se deben correr riesgos con inteligencia para poder innovar. En opinión de Nick Rockwell, CTO de The New York Times, «asumir riesgos positivos» es adentrarse en el terreno de lo desconocido y fracasar podría ser beneficioso si aprendes algo que te sirva en el futuro.
En esta serie de artículos, te he hemos contado cómo recuperarse tras una interrupción del servicio (Seth Vargo, de HashiCorp) o cómo fracasar rápido y encontrar soluciones más rápido todavía (Kenton Jacobsen, de Vogue). Hemos descubierto que los errores inesperados surgen incluso de los cambios más ordinarios (como reiniciar una base de datos o cambiar de backend). Sin embargo, los equipos más avezados ya han implantado herramientas y procesos que les permiten resolverlos al instante. En este artículo, te contamos cómo Niklas Gustavsson, Principal Engineer de Spotify, logró detectar contenido en directo (en producción y accesible para usuarios finales) pero no reproducible, tras lo que debería haber sido un cambio ordinario. También te contamos qué lecciones aprendió Niklas y qué herramienta de depuración prefiere.
Spotify distribuye contenido de audio para sistemas heredados (es decir, usuarios de clientes Spotify antiguos). Y lo hace mediante un nginx, o «almacenamiento para producción», que está ligeramente personalizado. Su funcionamiento es el siguiente: un cliente interactúa con un punto de acceso (un servicio perimetral que gestiona autenticaciones y enruta contenidos), el cual a su vez interactúa con un almacenamiento para producción, que a su vez interactúa con un proxy, que interactúa con un almacenamiento maestro (es decir, el origen de todos los archivos de audio).
Este año, el almacenamiento de los backends se trasladó a Google Cloud Storage (GCS), lo cual incluía todos los archivos de audio. Una vez concluido el traslado de enormes volúmenes de datos a GCS, Spotify se pasó a GCS como nuevo origen redireccionando el proxy. Y este cambio debería haber sido sencillo, ya que Google ofrece las mismas API que S3, proveedor con el que trabajaba antes Spotify.
Spotify puso a prueba estos nuevos sistemas. Sin embargo, tras completar el cambio, recibió notificaciones de que en Australia y Nueva Zelanda su servicio mostraba contenido que, aunque era nuevo, no se podía reproducir. Como es lógico, artistas y discográficas «se pusieron nerviosos» ante este panorama. Niklas creó un informe de incidentes para hacer un seguimiento del problema. A continuación, recurrió a ngrep, su herramienta de depuración favorita. Esta detecta el tráfico de red en bruto que accede al servidor y que sale de este, y captura cualquier tráfico en tiempo real que responda a un patrón determinado. Funciona un poco como la versión habitual de grep con los archivos de tu sistema local.
Gracias a ngrep, descubrió que a las respuestas procedentes de GCS les faltaba el encabezado de respuesta Accept-Ranges: bytes
. Y que era este detalle lo que diferenciaba a GCS de S3, a pesar de que las peticiones dirigidas a GCS no utilizaban rangos. Por tanto, había motivos para realizar una investigación más profunda.
Aunque nginx no realiza peticiones de rangos a GCS, las aplicaciones cliente Spotify y el punto de acceso entre los clientes y nginx sí las realizan. La forma predeterminada que nginx tiene de saber si puede responder a peticiones de rangos es confirmar que el objeto almacenado en caché tenga el encabezado de respuesta Accept-Ranges
. Al faltar el encabezado en este caso, nginx respondía a las peticiones de rango con un código de estado HTTP 200 y la totalidad del objeto solicitado, en lugar de un código de estado HTTP 206 y parte del contenido correspondiente al rango solicitado. Se trataba de una respuesta de nginx que, a pesar de entrar dentro de los patrones de comportamiento permitidos y de cumplir con todos los requisitos, difería de cómo se venían realizando las respuestas antes. Y eso provocaba problemas posteriormente.
Responder con el archivo de música completo reveló dos errores posteriormente: el primero, que el punto de acceso no se había diseñado para gestionar respuestas no parciales y enviaba una respuesta al cliente con un encabezado Content-Length
incorrecto; el segundo, que la aplicación cliente no tenía capacidad para gestionar encabezados Content-Length
incorrectos y ello impedía reproducir canciones.
Por fortuna, el equipo fue capaz de resolver los problemas configurando nginx de modo que aceptara peticiones de rangos y respondiera con contenido parcial (como hacía antes); incluso en los casos en que al contenido recibido anteriormente le faltara el encabezado Accept-Ranges
. Al examinar con nginx los archivos guardados en caché, el equipo pudo detectar a cuáles les faltaba el encabezado de respuesta y purgarlos del sistema.
Revisión de la cadena
Tras el incidente, Niklas y su equipo revisaron el resto de las partes de su cadena para asegurarse de que todo estuviera solucionado. Reconfigurar nginx suprimió la dependencia del encabezado Accept-Ranges
. Además, como medida complementaria —recordemos que, de manera predeterminada, GCS no permite configurar el encabezado—, se configuró el proxy que hay entre nginx y GCS de tal modo que añadiera el encabezado en todo momento.
También se solucionó el error del punto de acceso, lo que le permitió gestionar adecuadamente la totalidad de respuestas a las peticiones de rangos. Aunque esto supone que el punto de acceso deja de responder al cliente con encabezados Content-Length incorrectos, Spotify trabaja en solucionar el cliente también, de modo que en el futuro pueda gestionar este tipo de errores excepcionales. (Lo cual le permitiría fallar «de manera ordenada, en lugar de hacerlo de forma caótica»).
Presta atención a próximos artículos, donde te seguiremos contando el resultado de otras batallas.