Suscríbete para recibir notificaciones de nuevas publicaciones:

Super Slurper es cinco veces más rápido con Workers, Durable Objects y Queues

2025-04-10

8 min de lectura
Esta publicación también está disponible en English, 繁體中文, Français, Deutsch, 日本語, 한국어 y 简体中文.

Super Slurper es la herramienta de migración de datos de Cloudflare diseñada para facilitar las transferencias de datos a gran escala entre los proveedores de almacenamiento de objetos en la nube y Cloudflare R2. Desde su lanzamiento, miles de desarrolladores han utilizado Super Slurper para mover petabytes de datos de AWS S3, Google Cloud Storage y otros servicios compatibles con S3 a R2.

Pero comprendimos que podíamos hacerlo aún más rápido. Hemos rediseñado integralmente Super Slurper utilizando nuestra plataforma para desarrolladores (basándonos en nuestras soluciones Cloudflare Workers, Durable Objects y Queues) y hemos multiplicado por cinco la velocidad de transferencia. En esta publicación, analizaremos la arquitectura original, los cuellos de botella identificados relacionados con el rendimiento, cómo los resolvimos y el impacto real de estas mejoras.

Arquitectura inicial y cuellos de botella relacionados con el rendimiento

En un principio, Super Slurper compartía su arquitectura con SourcingKit, una herramienta creada para la importación masiva de imágenes desde AWS S3 a Cloudflare Images. SourcingKit se implementaba en Kubernetes y se ejecutaba junto con el servicio Images. Cuando empezamos a desarrollar Super Slurper, lo dividimos en su propio espacio de nombres Kubernetes e introdujimos algunas API nuevas para facilitar su uso en el caso del almacenamiento de objetos. Esta configuración funcionaba bien y ayudó a miles de desarrolladores a trasladar los datos a R2.

Sin embargo, no estaba exenta de desafíos. SourcingKit no estaba diseñado para gestionar el volumen necesario en el caso de las transferencias a gran escala, de varios petabytes. SourcingKit, y por extensión Super Slurper, operaban en clústeres de Kubernetes ubicados en uno de nuestros centros de datos principales. Esto significaba que tenía que compartir recursos informáticos y ancho de banda con el plano de control, los análisis y otros servicios de Cloudflare. A medida que aumentaba el número de migraciones, resultaba evidente que estas limitaciones de los recursos eran un cuello de botella.

Para un servicio que transfiere datos entre proveedores de almacenamiento de objetos, el trabajo es sencillo: enumerar los objetos del origen, copiarlos en el destino, y así repetidamente. Así es exactamente como funcionaba en un principio Super Slurper. Enumerábamos los objetos del bucket de origen, enviábamos esa lista a una cola basada en Postgres (pg_queue), y luego realizábamos la extracción de esta cola a un ritmo constante para copiar los objetos. Dada la escala de las migraciones de almacenamiento de objetos, el uso de ancho de banda inevitablemente iba a ser alto. Esto dificultaba la escalabilidad.

Para resolver las limitaciones de ancho de banda que suponía operar únicamente en nuestro centro de datos principal, incorporamos Cloudflare Workers en nuestra arquitectura. En lugar de encargarnos de la copia de datos en nuestro centro de datos principal, empezamos a llamar a un Worker para que hiciera la copia propiamente dicha:

Conforme crecía el uso de Super Slurper, también lo hacía nuestro consumo de recursos de Kubernetes. Durante las transferencias de datos, se dedicaba una cantidad significativa de tiempo a la espera de la E/S de la red o el almacenamiento, no a tareas con un gran uso de los recursos informáticos. Por lo tanto, no necesitábamos más memoria o más CPU, sino más simultaneidad.

Para satisfacer la demanda, seguimos aumentando el número de réplicas. Pero al final nos topamos con un obstáculo. Al ejecutar decenas de pods cuando queríamos un volumen mucho mayor, nos encontrábamos con desafíos relacionados con la escalabilidad.

Decidimos replantear todo el enfoque desde los principios básicos, en lugar de basarnos en la arquitectura que habíamos heredado. En aproximadamente una semana, desarrollamos una prueba de concepto aproximada utilizando Cloudflare Workers, Durable Objects y Queues. Enumeramos los objetos del bucket de origen, los enviamos a una cola y luego consumimos los mensajes de la cola para iniciar las transferencias. Esto parece muy similar a lo que hacíamos en la implementación original, pero trabajar en nuestra plataforma para desarrolladores nos permitió escalar automáticamente a un volumen mucho mayor que antes.

  • Cloudflare Queues: permite transferencias asíncronas de objetos y escala automáticamente según el número de objetos de la migración.

  • Cloudflare Workers: ejecuta tareas informáticas ligeras sin la sobrecarga de Kubernetes y optimiza la ubicación global donde se ejecutará cada parte del proceso para reducir la latencia y mejorar el rendimiento.

  • Durable Objects (DO) con el soporte de SQLite: funciona como una base de datos totalmente distribuida, eliminando las limitaciones de una única instancia de PostgreSQL.

  • Hyperdrive: proporciona acceso rápido a los datos históricos de los trabajos desde la base de datos PostgreSQL original, manteniéndola como un almacén de archivo.

Realizamos algunas pruebas y descubrimos que nuestra prueba de concepto era más lenta que la implementación original en el caso de las transferencias pequeñas (de algunos centenares de objetos). Sin embargo, igualaba y a la larga superaba el rendimiento de la implementación original al escalar las transferencias a millones de objetos. Esa fue la señal que necesitábamos para dedicar el tiempo necesario para llevar nuestra prueba de concepto a producción.

Eliminamos las soluciones temporales de nuestra prueba de concepto, mejoramos la estabilidad y encontramos nuevas formas de escalar las transferencias para aumentar aún más la simultaneidad. Tras algunas iteraciones, logramos un resultado con el que estábamos satisfechos.

Nueva arquitectura: Workers, Queues y Durable Objects

Capa de proceso: gestión del flujo de migración

Nuestra capa de proceso consta de colas, consumidores y Workers. El proceso es el siguiente:

Inicio de una migración

Cuando un cliente inicia una migración, esta comienza con una solicitud que se envía a nuestro API Worker. Este Worker toma los detalles de la migración, los almacena en la base de datos y añade un mensaje a la cola de lista para iniciar el proceso.

Enumerar los objetos del bucket de origen

El consumidor de la cola de lista es donde las cosas empiezan a mejorar. Extrae mensajes de la cola, recupera listas de objetos del bucket de origen, aplica los filtros necesarios y almacena los metadatos importantes en la base de datos. A continuación, crea nuevas tareas poniendo en cola los mensajes de transferencia de objetos en la cola de transferencia.

Inmediatamente ponemos en cola nuevos lotes de trabajo, maximizando la simultaneidad. Un mecanismo de limitación integrado evita que añadamos más mensajes a nuestras colas en caso de errores inesperados, como la caída de sistemas dependientes. Esto nos ayuda a garantizar la estabilidad y evita la sobrecarga durante las interrupciones.

Transferencias eficientes de objetos

Los Workers consumidores de la cola de transferencia extraen de la cola los mensajes de la transferencia de objetos y garantizan que cada objeto se procese solo una vez, bloqueando la clave del objeto en la base de datos. Cuando finaliza la transferencia, el objeto se desbloquea. En el caso de objetos más grandes, los dividimos en fragmentos manejables y los transferimos como cargas de varias partes.

Gestión eficaz de los errores

En cualquier sistema distribuido, los errores son inevitables, y teníamos que asegurarnos de tener esto en cuenta. Implementamos reintentos automáticos en caso de errores transitorios, para evitar la interrupción del flujo de la migración debido a un problema. Pero si los reintentos no resuelven el problema, el mensaje pasa a la cola de mensajes fallidos (DLQ), donde se registra para su posterior revisión y resolución.

Finalización de trabajos y gestión del ciclo de vida

Una vez que se han enumerado todos los objetos y que las transferencias están en curso, el consumidor de la cola del ciclo de vida lo supervisa todo. Controla las transferencias en curso, asegurándose de que ningún objeto quede atrás. Cuando se completan todas las transferencias, el trabajo se marca como finalizado y el proceso de migración concluye.

Capa de base de datos: almacenamiento duradero y recuperación de los datos heredados

Cuando desarrollamos nuestra nueva arquitectura, sabíamos que necesitábamos una solución eficaz que gestionara conjuntos de datos masivos y al mismo tiempo garantizara la recuperación de los datos históricos de los trabajos. Ahí es donde entra en juego nuestra combinación de Durable Objects (DO) e Hyperdrive.

Durable Objects

Asignamos a cada cuenta un Durable Object dedicado para el seguimiento de los trabajos de migración. El DO de cada trabajo almacena detalles esenciales, como los nombres de los buckets, las opciones de usuario y el estado del trabajo. Esto garantiza una buena organización y una gestión fácil. Para admitir migraciones grandes, también hemos añadido un DO de lotes que gestiona todos los objetos en cola de la transferencia, almacenando su estado de transferencia, las claves de objeto y los metadatos adicionales.

A medida que las migraciones se escalaban a miles de millones de objetos, tuvimos que ser creativos con el almacenamiento. Implementamos una estrategia de fragmentación para distribuir las cargas de solicitudes, evitando cuellos de botella y sorteando el límite de almacenamiento de 10 GB de SQLite DO. A medida que se transfieren los objetos, limpiamos sus detalles, optimizando el espacio de almacenamiento durante el proceso. ¡Es sorprendente la cantidad de almacenamiento que pueden requerir mil millones de claves de objeto!

Hyperdrive

Puesto que estábamos reconstruyendo un sistema con un historial de años de migraciones, necesitábamos una forma de preservar y acceder a todos los detalles de las migraciones anteriores. Hyperdrive sirve de puente a nuestros sistemas heredados, lo que permite una recuperación eficaz de los datos históricos de los trabajos desde nuestra base de datos central PostgreSQL. No es solo un mecanismo de recuperación de datos, sino un archivo para escenarios de migración complejos.

Resultados: Super Slurper transfiere ahora los datos a R2 hasta 5 veces más rápido

Entonces, después de todo eso, ¿realmente hemos logrado nuestro objetivo de acelerar las transferencias?

Ejecutamos una migración de prueba de 75 000 objetos de AWS S3 a R2. Con la implementación original, la transferencia tardaba 15 minutos y 30 segundos. Tras nuestras mejoras de rendimiento, la misma migración se completó en solo 3 minutos y 25 segundos.

Cuando las migraciones de producción empezaron a utilizar el nuevo servicio en febrero, en algunos casos observamos mejoras aún mayores, sobre todo relacionadas con la distribución de los tamaños de los objetos. Super Slurper existe desde hace unos dos años. Sin embargo, la mejora del rendimiento le ha permitido mover muchos más datos: el 35 % de todos los objetos copiados por Super Slurper corresponde solo a los últimos dos meses.

Desafíos

Uno de los mayores desafíos a los que nos enfrentamos con la nueva arquitectura fue la gestión de los mensajes duplicados. Se podían generar duplicados en un par de escenarios:

  • Queues proporciona una entrega al menos una vez, lo que significa que los consumidores pueden recibir el mismo mensaje más de una vez para garantizar la entrega.

  • Los errores y los reintentos también podrían crear aparentes duplicados. Por ejemplo, si una solicitud a un Durable Object falla después de que el objeto ya se haya transferido, el reintento podría volver a procesar el mismo objeto.

Si no se gestiona correctamente, el mismo objeto podría transferirse varias veces. Para resolver este problema, implementamos varias estrategias para garantizar que cada objeto se contabilizara con precisión y solo se transfiriera una vez:

  1. Dado que la enumeración es secuencial (p. ej., para obtener el segundo objeto de la lista, necesitas el token de continuación del primer objeto), asignamos un ID de secuencia a cada operación de enumeración. Esto nos permite detectar elementos duplicados de la lista y evitar el inicio simultáneo de varios procesos. Esto es especialmente útil porque no esperamos a que se completen las operaciones de la base de datos y de la cola para listar el siguiente lote. Si el segundo elemento de la lista falla, podemos volver a intentarlo, y si el tercer elemento ya se ha iniciado, podemos evitar los reintentos innecesarios.

  2. Cuando comienza la transferencia de un objeto, este se bloquea, lo que impide que se realicen transferencias paralelas del mismo objeto. Una vez transferido con éxito, el objeto se desbloquea eliminando su clave de la base de datos. Si más tarde vuelve a aparecer un mensaje para ese objeto, podemos asumir con seguridad que si la clave ya no existe significa que ya se ha transferido.

  3. Dependemos de las transacciones de la base de datos para garantizar la precisión de nuestro recuento. Si un objeto no se desbloquea, su recuento no cambia. Del mismo modo, si no se puede añadir una clave de objeto a la base de datos, el recuento no se actualiza y se volverá a intentar la operación más tarde.

  4. Como última medida de seguridad, comprobamos si el objeto ya existe en el bucket de destino y si se ha publicado después del inicio de nuestra migración. Si es así, asumimos que nuestro proceso (u otro) lo ha transferido y lo omitimos de forma segura.

¿Cuál será el próximo avance de Super Slurper?

Siempre estamos explorando cómo mejorar la velocidad, la escalabilidad y la facilidad de uso de Super Slurper. Esto es solo el principio.

  • Recientemente hemos lanzado la capacidad de migrar desde cualquier proveedor de almacenamiento compatible con S3.

  • Actualmente, las migraciones de datos siguen estando limitadas a 3 migraciones simultáneas por cuenta, pero queremos aumentar ese límite. Esto permitirá que los prefijos de objetos se dividan en migraciones independientes y se ejecuten en paralelo, lo que aumentará enormemente la velocidad de migración de un bucket. Si quieres más información sobre Super Slurper y sobre cómo migrar datos del almacenamiento de objetos existente a R2, consulta nuestra documentación.

P.D.: Como parte de esta actualización, hemos simplificado mucho la interacción con la API, por lo que ahora las migraciones se pueden gestionar mediante programación.

Protegemos redes corporativas completas, ayudamos a los clientes a desarrollar aplicaciones web de forma eficiente, aceleramos cualquier sitio o aplicación web, prevenimos contra los ataques DDoS, mantenemos a raya a los hackers, y podemos ayudarte en tu recorrido hacia la seguridad Zero Trust.

Visita 1.1.1.1 desde cualquier dispositivo para empezar a usar nuestra aplicación gratuita y beneficiarte de una navegación más rápida y segura.

Para saber más sobre nuestra misión para ayudar a mejorar Internet, empieza aquí. Si estás buscando un nuevo rumbo profesional, consulta nuestras ofertas de empleo.
Developer WeekR2 Super SlurperCloudflare WorkersDurable ObjectsCloudflare QueuesQueues

Síguenos en X

Cloudflare|@cloudflare

Publicaciones relacionadas