Otimização da injeção de dados DynamoDB com capacidade limitada
Recentemente tivemos uma tarefa de injeção de dados. Isso significa carregar uma grande quantidade de arquivos fonte do S3, ler a informação e escrevê-la para a Amazon DynamoDB. Aqui estão algumas coisas que considerámos enquanto avançávamos com uma solução.
- O número total de arquivos fonte S3 era cerca de 1500 e cada arquivo continha 4 milhões de linhas, tivemos que escrever ao DynamoDB 6 bilhões de vezes.
- As escritas do Amazon DynamoDB são caras. O preço para (100 escritas/segundo é $3000/mês), portanto tivemos que controlar a taxa de escrita, porque qualquer coisa sobre a capacidade seria perdida.
- A nossa aplicação estaria a correr em cápsulas geridas pela Kubernetes. Queríamos ter a certeza de que cada cápsula seria totalmente utilizada.
- Cada aplicativo seria um programa Java multithreaded, isso tornaria as coisas mais complicadas, mas também ajudaria a tarefa a terminar o mais rápido possível.
Aqui está a relação entre a capacidade de escrita do DynamoDB, o número de cápsulas e o número de fios:
Writing Capacity(writes/second) = (number of threads) * (number of pods) * (number of rows each thread can handle per second)
É muito simples e precisamos de uma maneira de garantir que esta equação seja satisfeita. Biblioteca das goiabas (https://github.com/google/guava) vem-me à cabeça no início. Ele tem um limitador de taxa bem desenhado e pode ser chamado antes de cada escrita para a DynamoDB. Por exemplo, o DynamoDB:
int numberOfWritesPerSecond = 100;
RateLimiter limiter = RateLimiter.create(numberOfWritesPerSecond);
limiter.adquire()
updateDynamoDB();
Agora a equação torna-se mais simples:
Writing Capacity(writes/second) = (number of writes on Rate limiter)* (number of pods)
Parece bom, mas quando o testámos, teve problemas:
- Se estabelecêssemos 3000 capacidade de escrita na Amazon, 5 cápsulas em Kubernetes e déssemos 5 linhas a cada cápsula, uma única linha teria: 3000 / 5 / 5 = 120 (escritas por segundo) e um arquivo fonte só poderia ser processado por um thread, tínhamos 4 milhões de registros, então levaria 4 milhões / 240 = 33333 segundos ou 9 horas para terminar um arquivo.
- Na verdade é até mais de 9 horas porque para uma linha em arquivo, há 2 operações: leitura de S3 e escrita de dados para o Dynamo. A operação mais lenta limita o tempo real.
- Com um tempo de processamento tão longo, a ligação S3 poderia ficar sem tempo, o grupo de fios poderia ficar sem tempo, e/ou a Kubernetes poderia reiniciar. Podem acontecer demasiadas coisas para que o trabalho falhe.
- O aumento da capacidade de escrita nem sequer funcionou. A capacidade de escrita com um único fio foi limitada pela leitura de arquivos e velocidade de escrita Dynamo. Cada arquivo ainda tem uma grande possibilidade de falhar.
Aqui está como resolver problemas:
- Após cuidadosa observação do arquivo, descobrimos que podemos fazer uma pequena agregação sobre ele. A cada 4 filas, podemos combinar em uma fila e escrever ao DynamoDB juntos. 4 milhões de escritas de repente tornaram-se 1 milhão.
- Mudamos para a operação Dynamo assimétrica para superar a limitação da escrita Dynamo. Agora a única limitação estava na leitura de arquivos. Nós ajustamos o número de threads em uma cápsula para obter o melhor utilitário de CPU.
- Nós lemos um arquivo com multi-tarefas.
Depois de termos feito as 3 primeiras alterações, um ficheiro poderia ser terminado em 2 horas.
Conclusão
A partir desta tarefa, adquirimos um profundo entendimento sobre como controlar a aplicação através da produção de um cluster de contentores. A Kubernetes facilita a orquestração de contentores mas, ao mesmo tempo, traz mais complexidade à aplicação. A base de dados baseada na nuvem (Dynamo) reduz os custos de manutenção, mas requer trabalho extra para garantir que a aplicação está a funcionar correctamente sob as suas restrições.