Optimización de la inyección de datos DynamoDB con capacidad limitada
Recientemente tuvimos una tarea de inyección de datos. Esto significa cargar una gran cantidad de archivos fuente desde S3, leer la información y escribirla en Amazon DynamoDB. Aquí hay algunas cosas que consideramos como hemos llegado-up con una solución.
- El número total de archivos fuente S3 fue de aproximadamente 1500 y cada archivo contenía 4 millones de filas, tuvimos que escribir a DynamoDB 6 mil millones veces.
- Amazon DynamoDB escribe son caros. La tasación para (100 escribe/segundo es $3000/month), así que tuvimos que controlar tarifa de escritura, porque cualquier cosa sobre capacidad sería perdida.
- Nuestra aplicación se ejecuta en pods administrados por Kubernetes. Queríamos asegurarnos de que cada cápsula sería utilizada plenamente.
- Cada aplicación sería un programa Java multi-threaded, esto haría las cosas más complicadas, pero también ayudar a la tarea terminar tan pronto como sea posible.
Aquí está la relación entre la capacidad de escritura DynamoDB, el número de Pod, y el número de subprocesos:
Writing Capacity(writes/second) = (number of threads) * (number of pods) * (number of rows each thread can handle per second)
Es muy sencillo y necesitamos una forma de asegurarnos de que esta ecuación esté satisfecha. Biblioteca de guayaba (https://github.com/google/guava) viene a la mente al principio. Tiene un limitador bien diseñado de la tarifa y se puede llamar antes de cada escritura a DynamoDB. Por ejemplo:
int numberOfWritesPerSecond = 100;
RateLimiter limitador = RateLimiter. Create (numberOfWritesPerSecond);
limitador. adquirir ()
updateDynamoDB ();
Ahora la ecuación se vuelve más simple:
Writing Capacity(writes/second) = (number of writes on Rate limiter)* (number of pods)
Se ve bien, pero cuando lo probamos, tuvo problemas:
- Si establecemos 3000 capacidad de escritura en Amazon, 5 vainas en Kubernetes y dio cada vaina 5 hilos, un solo hilo tendría: 3000/5/5 = 120 (Escrituras por segundo) y un archivo de origen sólo podría ser procesado por un subproceso, teníamos 4 millones de registros , por lo que tomaría 4 millones/240 = 33333 segundos o 9 horas para terminar un archivo.
- En realidad es incluso más de 9 horas porque para una fila en el archivo, hay 2 operaciones: lectura de S3 y escritura de datos a Dynamo. La operación más lenta limita el tiempo real.
- Con un tiempo de procesamiento tan largo, la conexión S3 podría pasar el tiempo, el grupo de subprocesos podría reiniciarse y/o Kubernetes. Muchas cosas podrían ocurrir para que el trabajo falle.
- Aumentar la capacidad de escritura ni siquiera funcionó. La capacidad de rosca única ha sido limitada por la lectura de archivos y la velocidad de escritura del dínamo. Cada archivo todavía tiene una alta posibilidad de fallar.
He aquí cómo resolver problemas:
- Después de observar cuidadosamente el archivo, encontramos que podemos hacer una pequeña agregación sobre él. Cada 4 filas se podían combinar en una fila y escribir a DynamoDB juntos. 4 millones escrituras se convirtieron repentinamente 1 millón.
- Cambiamos a la operación de dínamo asincrónica para superar la limitación de escritura Dynamo. Ahora la única limitación estaba en la lectura de archivos. Hemos afinado el número de subprocesos en una cápsula para obtener la mejor utilidad de CPU.
- Leemos un archivo con multi-threads.
Después de que hicimos los primeros 3 cambios, un archivo podría ser acabado en el plazo de 2 horas.
Conclusión
A partir de esta tarea, adquirimos un profundo entendimiento sobre cómo controlar el rendimiento de las aplicaciones en un clúster de contenedores. Kubernetes hace que la orquestación de contenedores sea más fácil, pero al mismo tiempo aporta más complejidad a la aplicación. La base de datos basada en la nube (Dynamo) reduce el costo de mantenimiento, pero requiere trabajo adicional para asegurarse de que la aplicación está funcionando correctamente bajo sus restricciones.