Ottimizzazione dell'iniezione dati DynamoDB con capacità limitata
Recentemente abbiamo avuto un compito di iniezione di dati. Questo significa caricare una grande quantità di file sorgente da S3, leggere le informazioni e scriverle su Amazon DynamoDB. Qui ci sono alcune cose che abbiamo considerato mentre cercavamo una soluzione.
- Il numero totale di file sorgente S3 era di circa 1500 e ogni file conteneva 4 milioni di righe, abbiamo dovuto scrivere su DynamoDB 6 miliardi di volte.
- Le scritture di Amazon DynamoDB sono costose. Il prezzo per (100 scrivere/secondo è di $3000/mese), quindi abbiamo dovuto controllare il tasso di scrittura, perché tutto ciò che supera la capacità andrebbe perso.
- La nostra applicazione verrebbe eseguita in baccelli gestiti da Kubernetes. Volevamo essere sicuri che ogni pod fosse pienamente utilizzato.
- Ogni app sarebbe un programma Java multi-thread, questo renderebbe le cose più complicate, ma aiuterebbe anche a finire il lavoro il più presto possibile.
Ecco la relazione tra la capacità di scrittura di DynamoDB, il numero di pod e il numero di thread:
Writing Capacity(writes/second) = (number of threads) * (number of pods) * (number of rows each thread can handle per second)
È molto semplice e abbiamo bisogno di un modo per assicurarci che questa equazione sia soddisfatta. Biblioteca Guava (https://github.com/google/guava) viene in mente all'inizio. Ha un limitatore di velocità ben progettato e può essere chiamato prima di ogni scrittura su DynamoDB. Per esempio:
int numberOfWritesPerSecond = 100;
RateLimiter limitatore = RateLimiter.create(numberOfWritesPerSecond);
limiter.acquire()
updateDynamoDB();
Ora l'equazione diventa più semplice:
Writing Capacity(writes/second) = (number of writes on Rate limiter)* (number of pods)
Sembra buono, ma quando l'abbiamo testato, ha avuto dei problemi:
- Se impostiamo 3000 capacità di scrittura su Amazon, 5 baccelli in Kubernetes e diamo ad ogni baccello 5 fili, un solo filo avrà: 3000 / 5 / 5 = 120 (scrive al secondo) e un file sorgente poteva essere elaborato da un solo thread, avevamo 4 milioni di record, quindi ci sarebbero voluti 4 milioni / 240 = 3333333 secondi o 9 ore per finire un file.
- In realtà è anche più lungo di 9 ore perché per una riga in un file ci sono 2 operazioni: la lettura da S3 e la scrittura dei dati su Dynamo. L'operazione più lenta limita il tempo effettivo.
- Con un tempo di elaborazione così lungo, la connessione S3 potrebbe andare in time out, il pool di fili potrebbe andare in time out, e/o Kubernetes potrebbe riavviarsi. Troppe cose potrebbero causare il fallimento del lavoro.
- L'aumento della capacità di scrittura non ha nemmeno funzionato. La capacità del singolo thread è stata limitata dalla lettura dei file e dalla velocità di scrittura Dynamo. Ogni file ha ancora un'alta possibilità di fallire.
Ecco come risolvere i problemi:
- Dopo un'attenta osservazione del dossier, abbiamo scoperto che possiamo fare una piccola aggregazione su di esso. Ogni 4 file può essere combinato in una sola riga e scritto su DynamoDB insieme. 4 milioni di scritte sono diventate improvvisamente 1 milione.
- Siamo passati all'operazione Dynamo async Dynamo per superare il limite della scrittura Dynamo. Ora l'unica limitazione era sulla lettura dei file. Abbiamo sintonizzato il numero di thread in un pod per ottenere la migliore utilità della CPU.
- Leggiamo un file con più fili.
Dopo aver effettuato le prime 3 modifiche, un file potrebbe essere finito entro 2 ore.
Conclusione
Da questo compito, abbiamo acquisito una profonda comprensione su come controllare il throughput di un cluster di container. Kubernetes rende più facile l'orchestrazione dei container, ma allo stesso tempo porta più complessità all'applicazione. Il database basato su cloud (Dynamo) riduce i costi di manutenzione, ma richiede un lavoro extra per garantire che l'applicazione funzioni correttamente sotto i suoi vincoli.