Migracja danych do Comos DB

2021-08-09

Całkiem niedawno temu zmagałem się z zadaniem polegającym na migracji pewnej ilości danych (przyjmijmy, że setki tysięcy dokumentów) do Cosmos DB, czyli Azure’owej bazy dokumentowej. Okazuje się, że strategia na “hurrra!” nie zadziała - jeśli będziemy wrzucać dokumenty jeden po drugim, po prostu przy którymś obrocie pętli, kod się nam wykrzaczy wyjątkiem. Co w takiej sytuacji należy zrobić?

Bulk SDK

W najprostszym podejściu, mając kolekcję danych (dokumentów) do wrzucenia do Cosmosa, możemy po prostu przeiterować po nich, dla każdego wywołując operację upsert. Łatwo się domyślić, że będzie to jednak niewydajne, zwłaszcza, że każda operacja będzie oddzielnym requestem. Na początku z pomocą przychodzi “bulk support”, wprowadzony w wersji 3.4.0 SDK. Gdy ustawimy AllowBulkExecution = true w CosmosClientOptions klient (stworzony z użyciem takiego obiektu opcji) będzie miał nową właściwość: będzie kolejkować ileś-tam pojedynczych upsertów w jeden request po sieci do Azure’a. A w kodzie - wystarczy, że odpalimy wszystkie upserty jako taski i poczekamy na wszystkie, jak w poniższym przykładzie:

List<Task> tasks = new List<Task>();
foreach (var itemToInsert in itemsToInsert)
{
    tasks.Add(container.UpsertItemAsync(itemToInsert, partitionKey));
}

await Task.WhenAll(tasks);

To robi dużą różnicę - cały proces przyspiesza, bo robimy po prostu mniej requestów sieciowych. Jak dokładnie to jest rozdzielane przez SDK i jaka jest pojemność “bufora”, tego autorzy nie SDK nie zdradzają. Jednakże taka zamiana z “dużo mały kroczków” na “mało wielkich kroków” (upraszczam, rzecz jasna) może być niewystarczająca. Niby o tym piszą w dokumentacji, ale nie zwrócłem na to uwagi na początku…

Zwiększ throughput

Bulk API generuje jednak dodatkowy problem - skoro szybciej pakujemy dane do chmury, to innymi słowy przystawiamy do Azure’a grubszą rurę. Tyle, że Cosmos DB to nie papier i wszystkiego nie przyjmie. Komunikacja z Cosmos DB jest bowiem ograniczona przez ustawiony na instancji throughput. Troughput (przepustowość) to ta magiczna wartość, liczona w jeszcze bardziej magicznej jednostce RU, za którą płacimy - a dokładniej: płacimy za rezerwację danej przepustowości. Troughput może być ustawiony na sztywno albo autoskalować się w pewnym przedziale. W drugiej opcji polega to na tym, że określamy maksymalną przepustowość, a chmura sama skaluje ją pomiędzy 10% a 100& maksymalnego limitu (dzięki temu w czasie mniejszego obciążenia można sporo zaoszczędzić). W skrócie zatem - jeśli odpalimy bulk API bez zmiany przepustowości, to jest bardzo prawdopodobne, że ona się wyczerpie (no chyba, że mamy ustawiony bardzo duży ten górny limit, no ale to ma sens tylko wtedy gdy mamy naprawdę ogromną amplitudę ruchu). Rozwiązaniem jest po prostu czasowe podniesienie tego limitu - ja się nie patyczkowałem i dałem x10. To oczywiście podbije nam koszty, bo w czasie migrowania danych, bulk API będzie starał się pakować, ile się da - a więc przy autoskalowaniu przepustowości na pewno zostanie ona podniesiona w stronę 100%, ale dzięki temu z jednej strony przyspieszymy cały proces, a z drugiej - unikniemy odrzuconych upsertów. Trzeba tylko wiedzieć, że sam proces podnoszenia górnego limitu przepustowości może trwać nawet godzinę (sic!) oraz pamiętać, żeby po całej operacji przywrócić wartość przepustowości - w przeciwnym wypadku będzie ona “wisieć” na za dużej wartości (przykładowo: jeśli mieliśmy dotychzas górny limit na 4000 RU, to przepustowość skalowała się między 400 a 4000 RU; jeśli zostawimy throughput na tymczasowej wartości np. x10, to zostaniemy na 4000-40000 RU, a więc najniższa wartość będzie taka, jak górny limit normalnego trybu…).


Robert Skarżycki - zdjęcie profilowe

Pisanina, której autorem jest Robert Skarżycki - programista .NET, mąż szczęśliwej żony, rodzic
moje bio
mój Twitter
mój LinkedIn
moje szkolenia i warsztaty

© 2022, Built with Gatsby & passion