• CloudPouch NEW!
  • Akademia
  • Blog
  • O Serverless
  • O stronie

Budowa perfekcyjnie skalowalnych aplikacji serverless


alt text

Jedn─ů z korzy┼Ťci, o kt├│rej us┼éyszysz, gdy ludzie b─Öd─ů Ci─Ö przekonywa─ç do architektury serverless, jest to, ┼╝e ÔÇťsama si─Ö skaluje i nigdy nie musisz si─Ö o to martwi─çÔÇŁ.

Chciałbym, żeby to była prawda.

Ale tak nie jest.

Prawd─ů jest, ┼╝e dostawca chmury obs┼éuguje zdarzenia skalowania za Ciebie. I do tego ca┼ékiem nie┼║le. Dzieje si─Ö to automatycznie bez ┼╝adnej ingerencji z Twojej strony i skaluje si─Ö praktycznie do ka┼╝dego poziomu (zak┼éadaj─ůc, ┼╝e zmieni┼ée┼Ť limity us┼éug AWS).

To, co nie jest prawd─ů, to fakt, ┼╝e nie musisz si─Ö o to martwi─ç. Koniecznie musisz wzi─ů─ç pod uwag─Ö skal─Ö podczas projektowania aplikacji serverless.

Projektuj─ůc aplikacj─Ö, musisz zna─ç z grubsza stopie┼ä, w jakim b─Öd─ů nap┼éywa─ç ┼╝─ůdania.
Czy to 1 ┼╝─ůdanie na sekund─Ö? 10? 1,000? 100,000?

Dla ka┼╝dego rz─Ödu wielko┼Ťci, musisz rozwa┼╝y─ç, jak poradzisz sobie ze zwi─Ökszonym obci─ů┼╝eniem w ca┼éym systemie. Skalowanie nie odnosi si─Ö tylko do sposobu w jaki API Gateway obs┼éuguje ruch. To r├│wnie┼╝ zachowanie bazy danych, proces├│w backendowych i w┼éasnych API, kt├│re razem obs┼éuguj─ů ruch. Je┼Ťli co najmniej jeden z tych element├│w nie zostanie przeskalowany do odpowiedniego poziomu, napotkasz w─ůskie gard┼éo i zmniejszon─ů wydajno┼Ť─ç aplikacji.

Dzisiaj porozmawiamy o r├│┼╝nych sposobach budowania aplikacji w oparciu o przewidywan─ů skal─Ö (plus bonusowo r├│wnie┼╝ bezpiecze┼ästwa).

Nota: Nie ma standardowych nazw ani definicji bran┼╝owych dla r├│┼╝nych poziom├│w skali. Nazwy, kt├│rych b─Öd─Ö u┼╝ywa─ç, s─ů wymy┼Ťlone i nie maj─ů na celu odzwierciedlenia jako┼Ťci lub znaczenia oprogramowania.

Ma┼éa skala (1-999 ┼╝─ůda┼ä na sekund─Ö)

Obs┼éuguj─ůc system na ma┼é─ů skal─Ö, masz szcz─Ö┼Ťcie. Mo┼╝esz budowa─ç bez zbyt wielu rozterek architektonicznych. Teoretycznie wszystko powinno po prostu dzia┼éa─ç. Nie oznacza to jednak, ┼╝e bierzesz pierwszy z brzegu przyk┼éadowy projekt i deployujesz go na produkcj─Ö (nigdy nie powiniene┼Ť u┼╝ywa─ç POC na produkcji).

Oznacza to jednak, ┼╝e w wi─Ökszo┼Ťci sytuacji mo┼╝na zaprojektowa─ç aplikacj─Ö zgodnie ze standardowymi wzorcami dla architektur serverless.

Przy ma┼éej skali podstawowe bloki budowlane serverless s─ů Twoimi najlepszymi przyjaci├│┼émi i zaprowadz─ů Ci─Ö daleko. Ale bez wzgl─Ödu na to, jaki poziom skali planujesz, musisz pami─Öta─ç o sprawdzeniu limit├│w dla us┼éug, z kt├│rych b─Ödziesz korzysta─ç. Rozwa┼╝my nast─Öpuj─ůcy wzorzec dla interfejsu API na ma┼é─ů skal─Ö.

Struktura API dla małej skali

Struktura API dla małej skali

Dla takiej architektury, limity us┼éug, kt├│re b─Öd─ů Cie interesowa┼éy to:

  • Maksymalna wsp├│┼ébie┼╝no┼Ť─ç funkcji Lambda (concurrent executions) - domy┼Ťlnie: 1000
  • Rozmiar Capacity unit w DynamoDB
  • Maksymalna ilo┼Ť─ç uruchomie┼ä maszyny stan├│w (start execution) w us┼éudze Step Functions - domy┼Ťlnie: 1300 na sekund─Ö w niekt├│rych regionach, 800 w innych

Istniej─ů inne limity dla us┼éug, kt├│re ta architektura zu┼╝ywa, ale przy tej skali nie natrafimy na nie.

Je┼Ťli osi─ůgniemy szczyt naszej skali i / lub nasz ┼Ťredni czas wykonania funkcji Lambda jest d┼éu┼╝szy ni┼╝ sekunda, dobrym rozwi─ůzaniem mo┼╝e okaza─ç si─Ö za┼╝─ůdanie zwi─Ökszenia limitu concurrent executions us┼éugi Lambda. Je┼Ťli tw├│j ┼Ťredni czas wykonania jest bardzo niski, rz─Ödu 200ms i mniej, oznacza to, ┼╝e wszystko jest w porz─ůdku.

Je┼Ťli zaczniesz regularnie osi─ůga─ç 70-80% limitu us┼éugi, powiniene┼Ť poprosi─ç o zwi─Ökszenie.

W przypadku DynamoDB masz kilka opcji. Mo┼╝na u┼╝y─ç mechanizmu provisioned throughput, kt├│ry ustawia konkretn─ů liczb─Ö odczyt├│w i zapis├│w na sekund─Ö dla tej bazy lub mo┼╝na u┼╝y─ç trybu on-demand, kt├│ry skaluje si─Ö sam, je┼Ťli masz zmienne lub nieznane obci─ů┼╝enia.

Je┼Ťli korzystasz z trybu on-demand, nie musisz si─Ö martwi─ç o skalowanie. DynamoDB b─Ödzie skalowa─ç si─Ö automatycznie. Ale je┼Ťli u┼╝ywasz provisioned throughput, musisz upewni─ç si─Ö, ┼╝e ustawi┼ée┼Ť przepustowo┼Ť─ç, kt├│rej naprawd─Ö potrzebujesz.

W przypadku Step Functions musisz zachowa─ç ostro┼╝no┼Ť─ç co do liczby uruchamianych standardowych maszyn stan├│w. Domy┼Ťlna liczba w takim przypadku wynosi 1 300 na sekund─Ö z dodatkowymi 500 (tryb burst) w us-east-1, us-west-1 i eu-west-1. Je┼Ťli Twoja aplikacja dzia┼éa poza tymi regionami, domy┼Ťlnie jest ograniczona do 800 uruchomie┼ä na sekund─Ö.

Nale┼╝y zauwa┼╝y─ç, ┼╝e ten limit dotyczy tylko uruchamiania nowych wykona┼ä maszyny stan├│w w okresie jednej sekundy. Mo┼╝esz mie─ç do 1 miliona dzia┼éaj─ůcych jednocze┼Ťnie wywo┼éa┼ä, zanim Twoje nowe ┼╝─ůdania zaczn─ů by─ç przycinane (throttling). Ale w tej skali prawdopodobnie nie musimy si─Ö o to martwi─ç.

┼Ürednia skala (1 000-9 999 ┼╝─ůda┼ä na sekund─Ö)

Nast─Öpny poziom skali zdecydowanie wymaga pewnych rozwa┼╝a┼ä projektowych. Je┼Ťli spodziewasz si─Ö sta┼éego obci─ů┼╝enia rz─Ödu 1k - 10k ┼╝─ůda┼ä na sekund─Ö, musisz zaplanowa─ç odpowiedni─ů odporno┼Ť─ç na b┼é─Ödy (fault tolerance). W tej skali, je┼Ťli 99,9% Twoich ┼╝─ůda┼ä zako┼äczy si─Ö powodzeniem, oznacza to, ┼╝e patrzysz na 86 400 do 864 000 niepowodze┼ä dziennie. Tak wi─Öc odporno┼Ť─ç na b┼é─Ödy i redundancja maj─ů szczeg├│lne wa┼╝ne miejsce na tym poziomie.

Chocia┼╝ zawsze powiniene┼Ť projektowa─ç tak, aby ponawia─ç pr├│by, staje si─Ö to szczeg├│lnie wa┼╝ne, gdy rozmawiamy o skalowaniu. Zarz─ůdzanie ponawianiem pr├│b (retries) i odporno┼Ťci─ů na b┼é─Ödy w tej skali szybko staje si─Ö niemo┼╝liwym zadaniem dla ludzi, wi─Öc automatyzacja procesu jest kluczow─ů cz─Ö┼Ťci─ů Twojego sukcesu.

Zobaczmy, jak zmieni┼é si─Ö nasz diagram architektury po przej┼Ťciu do ┼Ťredniej skali.

Struktura API dla ┼Ťredniej skali

Struktura API dla ┼Ťredniej skali

Architektura zosta┼éa nieco zmodyfikowana. Nadal mamy endpointy, kt├│re ┼é─ůcz─ů si─Ö z us┼éugami Lambda i DynamoDB, ale nie ┼é─ůczymy si─Ö ju┼╝ bezpo┼Ťrednio z us┼éug─ů Step Functions. Zamiast tego umieszczamy przed ni─ů kolejk─Ö SQS, aby dzia┼éa┼éa jako bufor. To nieumy┼Ťlnie skutkuje tym, ┼╝e endpoint zamienia si─Ö w asynchroniczny.

Funkcja Lambda batchowo pobiera ┼╝─ůdania uruchomie┼ä z kolejki, sprawdza przepustowo┼Ť─ç w Step Functions i rozpoczyna wykonywanie. Je┼Ťli brak przepustowo┼Ťci, ┼╝─ůdania wracaj─ů do kolejki, aby ponowi─ç pr├│b─Ö p├│┼║niej.

Po zako┼äczeniu pracy maszyna stanu wysy┼éa zdarzenie przez EventBridge, aby powiadomi─ç wywo┼éuj─ůcego o zako┼äczeniu operacji.

Dla takiej architektury i skali limity, na kt├│re nale┼╝y zwr├│ci─ç uwag─Ö, to:

  • Wsp├│┼ébie┼╝no┼Ť─ç funkcji Lambda (concurrent executions) - musisz poprosi─ç o zwi─Ökszenie, aby uwzgl─Ödni─ç przepustowo┼Ť─ç
  • EventBridge PutEvents - domy┼Ťlnie 10k na sekund─Ö, ale w niekt├│rych regionach tak niskie jak 600 na sekund─Ö

Zgodnie z dokumentacj─ů, wsp├│┼ébie┼╝no┼Ť─ç funkcji Lambda mo┼╝na zwi─Ökszy─ç do dziesi─ůtek tysi─Öcy, wi─Öc jeste┼Ťmy tutaj bezpieczni i nie musimy si─Ö martwi─ç o dodatkowy klej, kt├│ry dodali┼Ťmy mi─Ödzy SQS i Step Functions.

Wraz ze zwi─Ökszeniem liczby funkcji Lambda w tym projekcie, musimy ustawi─ç zarezerwowan─ů wsp├│┼ébie┼╝no┼Ť─ç (reserved concurrency) dla funkcji o ni┼╝szym priorytecie. Wsp├│┼ébie┼╝no┼Ť─ç zarezerwowana stanowi sk┼éadow─ů ca┼ékowitej wsp├│┼ébie┼╝no┼Ťci wszystkich funkcji Lambda na koncie AWS. Ustawia si─Ö j─ů dla konkretnej funkcji, kt├│ra b─Ödzie mog┼éa by─ç skalowana maksymalnie do ustawionej warto┼Ťci. Zapobiega to niepotrzebnemu zu┼╝ywaniu wsp├│┼ébie┼╝no┼Ťci przez funkcje o niskim priorytecie. U┼╝ycie zarezerwowanej wsp├│┼ébie┼╝no┼Ťci nadal umo┼╝liwia skalowanie funkcji do 0, gdy nie s─ů u┼╝ywane.

Z drugiej strony, dostarczona wsp├│┼ébie┼╝no┼Ť─ç (provisioned concurrency) utrzymuje zadeklarowan─ů liczb─Ö rozgrzanych funkcji Lambda, w ten spos├│b chroni─ůc nas przed cold startami. Jest to szczeg├│lnie wa┼╝ne dla uzyskania jak najni┼╝szego czasu reakcji. Warto zauwa┼╝y─ç, ┼╝e funkcja Lambda mo┼╝e si─Ö skalowa─ç ponad t─Ö warto┼Ť─ç, ale wtedy mamy do czynienia z cold startem.

W tym momencie warto porozmawia─ç o podej┼Ťciu single table design w DynamoDB i o tym, jak tw├│j model danych jest szczeg├│lnie wa┼╝ny w ┼Ťredniej (i du┼╝ej) skali. W single table design wszystkie typy encji danych zapisujemy w pojedynczej tabeli, a rozr├│┼╝niamy je za pomoc─ů r├│┼╝nych kluczy partycji. Pozwala to na szybki i ┼éatwy dost─Öp do danych przy minimalnym op├│┼║nieniu w us┼éudze DynamoDB.

Jednak DynamoDB ma limit 3000 jednostek odczytu read capacity units (RCU) i 1000 jednostek zapisu write capacity units (WCU) na partycj─Ö.

Je┼Ťli Tw├│j model danych nie dystrybuuje ┼╝─ůda┼ä r├│wnomiernie pomi─Ödzy partycjami tabeli DynamoDB, to utworzysz gor─ůc─ů partycj─Ö (hot partition), a Twoje ┼╝─ůdania do bazy zaczn─ů by─ç przycinane (throttling). W ┼Ťredniej skali lub wy┼╝szej spos├│b zapisywania danych ma kluczowe znaczenie dla skalowalno┼Ťci. Pami─Ötaj wi─Öc, aby zaprojektowa─ç model danych w spos├│b, kt├│ry umo┼╝liwia ┼éatwy sharding przy zapisie, aby partycje danych by┼éy zr├│┼╝nicowane.

Wiele do rozwa┼╝enia, gdy osi─ůgniemy drugi poziom skali. Ale jest jeszcze wi─Öcej do wyja┼Ťnienia, gdy osi─ůgniemy ostateczny poziom skali.

Du┼╝a skala (ponad 10 000 ┼╝─ůda┼ä na sekund─Ö)

Justin Pirtle wyg┼éosi┼é na AWS re:Invent 2021 prelekcj─Ö na temat projektowania aplikacji serverless dla hiperskali. W swojej prezentacji opowiada o najlepszych praktykach dla aplikacji dzia┼éaj─ůcych w naprawd─Ö du┼╝ej skal─Ö. Najwa┼╝niejsze czynniki? Cache`owanie, batch`owanie, i kolejkowanie.

Maj─ůc na uwadze te czynniki, przyjrzyjmy si─Ö, jak zmienia si─Ö nasza architektura w stosunku do modelu w ma┼éej skali.

Struktura API dla du┼╝ej skali

Struktura API dla du┼╝ej skali

W przypadku takiej architektury w du┼╝ym stopniu polegamy na przetwarzaniu asynchronicznym. Poniewa┼╝ prawie wszystkie wywo┼éania API powoduj─ů kolejkowanie, oznacza to, ┼╝e wi─Ökszo┼Ť─ç wywo┼éa┼ä b─Ödzie polega─ç na przetwarzaniu wsadowym w tle. API Gateway ┼é─ůczy si─Ö bezpo┼Ťrednio z SQS, co powoduje, ┼╝e funkcja Lambda pobiera partie wiadomo┼Ťci do przetworzenia (batching).

Po zako┼äczeniu przetwarzania uruchamia zdarzenie, aby powiadomi─ç o zako┼äczeniu przetwarzania wywo┼éuj─ůcego. Alternatywnie mo┼╝na zastosowa─ç podej┼Ťcie oparte na modelu zadania, aby umo┼╝liwi─ç osobie wywo┼éuj─ůcej wys┼éanie zapytania o aktualizacj─Ö stanu.

Je┼Ťli wyst─ůpi b┼é─ůd podczas przetwarzania jednego lub wi─Öcej element├│w w partii (batch), mo┼╝na ustawi─ç w┼éa┼Ťciwo┼Ť─ç BisectBatchOnFunctionError w mapowaniu ┼║r├│d┼éa zdarze┼ä, aby podzieli─ç parti─Ö i ponowi─ç pr├│b─Ö. Pozwala to uzyska─ç jak najwi─Öcej poprawnie przetworzonych wiadomo┼Ťci.

Wprowadzili┼Ťmy r├│wnie┼╝ DynamoDB Accelerator (DAX) przed nasz─ů tabel─ů, aby dzia┼éa┼é jak cache. Pomaga to utrzyma─ç RCU na niskim poziomie, a tak┼╝e zapewnia mikrosekundowe op├│┼║nienia w przypadku trafie┼ä (warto┼Ť─ç obecna w pami─Öci podr─Öcznej).

Wszystkie limity us┼éug z poprzednich poziom├│w skali maj─ů zastosowanie na tym poziomie, plus kilka dodatkowych:

  • ┼╗─ůdania na sekund─Ö do API Gateway requests per second ÔÇö domy┼Ťlnie: 10k na sekund─Ö dla wszystkich endpoint├│w w regionie
  • Przej┼Ťcia mi─Ödzy stanami maszyny stan├│w w us┼éudze Step Functions state transitions ÔÇö 5k na sekund─Ö w niekt├│rych regionach, w innych 800 na sekund─Ö

Przy du┼╝ej skali, architektoniczne rozwa┼╝ania wchodz─ů na wy┼╝szy poziom. Poniewa┼╝ istnieje tak wiele limit├│w us┼éug, kt├│re nale┼╝y kontrolowa─ç i zwi─Öksza─ç, dobrym pomys┼éem jest rozdzielenie mikroserwis├│w na w┼éasne konta AWS. Izolowanie us┼éug na ich w┼éasnych kontach zapobiegnie niepotrzebnym sporom o zasoby. Prawda, b─Ödziesz mie─ç wi─Öcej kont do zarz─ůdzania, ale zaplanowane przepustowo┼Ťci stan─ů si─Ö znacznie ┼éatwiejsze do osi─ůgni─Öcia, gdy┼╝ r├│┼╝ne komponenty Twojego systemu nie b─Öd─ů konkurowa─ç mi─Ödzy sob─ů w ramach tego samego limitu jednego konta AWS.

Us┼éuga API Gateway ma mi─Ökki limit ┼╝─ůda┼ä na sekund─Ö. Domy┼Ťlnie jest to 10k i sk┼éadaj─ů si─Ö na to wszystkie interfejsy API REST, HTTP i WebSocket na koncie w okre┼Ťlonym regionie. Dlatego dobrze jest odizolowa─ç swoje us┼éugi i w┼éasne API umieszczaj─ůc je w oddzielnych kontach. Limit ten musi zosta─ç zwi─Ökszony przy du┼╝ej skali.

Us┼éuga Step Functions ma interesuj─ůcy limit state transitions wynosz─ůcy 5k przej┼Ť─ç stanu na sekund─Ö we wszystkich standardowych maszynach stanu. Je┼Ťli wi─Öc masz ponad 5000 standardowych przep┼éyw├│w pracy dzia┼éaj─ůcych jednocze┼Ťnie, oczekuj throttling`u , je┼Ťli ka┼╝dy z nich przechodzi w inny stan co sekund─Ö.

Je┼Ťli to mo┼╝liwe, zmie┼ä standardowe maszyny stanu na ekspresowe. S─ů one przeznaczone dla du┼╝ych obci─ů┼╝e┼ä przetwarzania zdarze┼ä i skaluj─ů si─Ö o rz─Ödy wielko┼Ťci wy┼╝ej ni┼╝ standardowe. Nie ma limitu przej┼Ť─ç stanu w przypadku ekspresowych maszyn stan├│w.

Je┼Ťli nie mo┼╝na zmieni─ç typu maszyny stan├│w, nale┼╝y samodzielnie przechwytywa─ç i ponawia─ç pr├│by przej┼Ťcia stanu w maszynach stan├│w.

Oczywi┼Ťcie aplikacja, kt├│ra skaluje si─Ö do takiego poziomu, b─Ödzie kosztowa─ç znaczn─ů ilo┼Ť─ç pieni─Ödzy. Oznacza to, ┼╝e nale┼╝y wykorzysta─ç ka┼╝d─ů okazj─Ö, aby zoptymalizowa─ç wydajno┼Ť─ç aplikacji.

Je┼Ťli to mo┼╝liwe, ┼é─ůcz bezpo┼Ťrednio us┼éugi zamiast u┼╝ywa─ç do tego Lambdy. Prze┼é─ůcz swoje funkcje, aby korzysta─ç z architektury arm64. Stosuj batch`owe przetwarzanie, gdy tylko jest to mo┼╝liwe.

Podsumowanie

Rozmiar ma znaczenie.

Ilo┼Ť─ç ruchu obs┼éugiwanego przez Twoj─ů aplikacj─Ö ma bezpo┼Ťredni wp┼éyw na spos├│b zaprojektowania architektury. Stw├│rz architektur─Ö pod skal─Ö, kt├│r─ů b─Ödziesz mia┼é w najbli┼╝szej przysz┼éo┼Ťci, a nie skal─Ö, kt├│r─ů b─Ödziesz mia┼é za 10 lat.

Serverless nie jest srebrn─ů kul─ů. Nie rozwi─ůzuje wszystkich naszych problem├│w tylko dlatego, ┼╝e piszemy logik─Ö biznesow─ů w funkcji Lambda.

To, ┼╝e us┼éugi serverless mog─ů si─Ö skalowa─ç, nie oznacza, ┼╝e b─Öd─ů si─Ö skalowa─ç.

Jako architekt rozwi─ůza┼ä Twoim zadaniem jest upewnienie si─Ö, ┼╝e wszystkie komponenty aplikacji s─ů zaprojektowane tak, aby skalowa─ç si─Ö razem. Nie chcesz, aby komponent na wej┼Ťciu skalowa┼é si─Ö znacznie szybciej/wy┼╝ej ni┼╝ komponent, kt├│ry nast─Öpnie przetwarzania te dane. To zbuduje stale rosn─ůce zaleg┼éo┼Ťci w ┼╝─ůdaniach, kt├│rych nigdy nie b─Ödziesz w stanie skonsumowa─ç. Znajd┼║ r├│wnowag─Ö.

Obserwuj limity us┼éug. Zaprojektuj aplikacj─Ö, kt├│ra samoistnie ponawia ┼╝─ůdania. Zautomatyzuj wszystko. Wypatruj b┼é─Öd├│w i uchybie┼ä. Bez wzgl─Ödu na skal─Ö, musisz by─ç na bie┼╝─ůco z aplikacj─ů i dok┼éadnie wiedzie─ç, jak dzia┼éa w dowolnym momencie. Pomo┼╝e Ci to wprowadzi─ç odpowiednie modyfikacje (je┼Ťli to konieczne) i dokona─ç optymalizacji, kt├│re zar├│wno zwi─Öksz─ů wydajno┼Ť─ç, jak i obni┼╝─ů koszty.

Kiedy czujesz, ┼╝e zbudowa┼ée┼Ť aplikacj─Ö, kt├│ra skaluje si─Ö do po┼╝─ůdanego poziomu, wykonaj testy obci─ů┼╝eniowe. Upewnij si─Ö, ┼╝e robi to, co powinna.

Powodzenia. Projektowanie aplikacji na du┼╝─ů skal─Ö to fajne i wyj─ůtkowe wyzwanie. W niekt├│rych przypadkach chodzi zar├│wno o infrastruktur─Ö, jak i logik─Ö biznesow─ů.

Miłego kodowania!

Autor: Allen Helton is a serverless cloud architect focused on proper API development, serverless, and AWS. His focus mainly revolves around AWS serverless, APIs, and leadership. He builds serverless applications for a living and loves sharing his knowledge and expertise with anyone who will listen.

T┼éumaczenie: Pawe┼é Zubkiewicz za zgod─ů autora.



Cze┼Ť─ç

Nazywam si─Ö Pawe┼é Zubkiewicz i ciesz─Ö si─Ö, ┼╝e tu jeste┼Ť!
Od ponad 16 lat profesjonalnie tworz─Ö oprogramowanie, a od 2016 roku pasjonuje si─Ö Serverless.
T─ů stron─Ö stworzy┼éem z my┼Ťl─ů o Tobie i o nas wszystkich, kt├│rzy uwa┼╝aj─ů, ┼╝e trend serverless trwale zmieni spos├│b tworzenia oprogramowania.
Wi─Öcej o tej stronie...

Kategorie

Pobierz bezpłatny PDF

Poradnik 12 Rzeczy o Serverless

Wybrane artykuły