20 DevOps Concepts You Must Know (film, 18 minutes)
In the latest video from Coding Chef, the critical topic of application scaling is discussed. The author starts by explaining the problem faced by any owner of a popular application - as the number of users grows, the amount of RAM on the database server becomes insufficient. The solution to this problem is known as vertical scaling, which, over time, can also become inadequate, leading to the need for horizontal scaling. This approach involves adding new database servers and distributing data among them, increasing security, because in case of failure of one server, the application can still function using the other instances.
Another important topic covered in the video is cloud computing, which has become a key element for startups lacking vast financial resources. The cloud allows for renting machines and servers, significantly simplifying database management without the need to invest in additional hardware. The largest providers such as Amazon AWS, Google Cloud Platform, and Microsoft Azure offer versatile solutions with virtually unlimited capabilities. With the cloud, businesses can easily scale their systems to adapt to the needs of a growing user traffic.
However, cloud service providers alone are not enough. As an application starts to draw a larger number of users, there's a need to create multiple application instances, which involves using a load balancer. This additional server directs traffic to various application instances, preventing overload. The author also points out the benefits of employing mechanisms such as caching and Content Delivery Network (CDN), which speed up data transmission to users located in different regions of the world.
In the later parts, the author discusses various aspects of data transmission through communication protocols such as HTTP and HTTPS and explains the significance of JSON as a data exchange format. He also uncovers issues related to APIs, REST APIs, and microservices architecture, which deserve detailed analysis. Microservices allow the development of applications in independent modules; however, they require thoughtful automation of processes and protection against loss of communication between services. The discussed also include the ACID principles and their counterparts in NoSQL databases, highlighting their benefits and usefulness in modern applications.
Finally, it's worth looking at the statistics for this video. Currently, Coding Chef has 30,956 views and 1,863 likes. This shows that the topic of application scaling and database optimization is still at the forefront among the issues addressed in the programming community. We encourage everyone to subscribe to Coding Chef to stay updated on IT innovations.
Toggle timeline summary
-
Discusses potential scenarios for app owners facing rapid user growth.
-
Highlights the daily registration of thousands of new users.
-
Mentions the need to expand the database due to size limitations.
-
Introduces vertical scaling as a common initial solution.
-
Describes the limitations of continually adding RAM to a single server.
-
Presents horizontal scaling as an alternative.
-
Explains the safety advantages of horizontal scaling.
-
Details risks associated with single server failures in vertical scaling.
-
Notes the ability to save data to other instances during a failure.
-
Introduces cloud computing as a viable option for startups.
-
Describes how cloud services work, emphasizing outsourcing management.
-
Mentions major cloud providers like Amazon, Google, and Microsoft.
-
Points out the lack of limitations from major cloud providers.
-
Stresses the importance of scaling applications to handle high traffic.
-
Explains the need for load balancers in distributed systems.
-
Describes the round robin method for distributing requests.
-
Discusses the potential inefficiencies of an application under heavy load.
-
Introduces caching as a method to alleviate database loads.
-
Explains different caching locations, such as server-side and browser-side.
-
Notes the need for faster data delivery across global users.
-
Introduces Content Delivery Networks (CDN) as a solution.
-
Discusses the importance of IP addresses for communication.
-
Explains the Domain Name System (DNS) and its importance in web navigation.
-
Details the hierarchical structure of DNS queries.
-
Explains how authoritative DNS servers respond to queries.
-
Discusses communication protocols, specifically HTTP and HTTPS.
-
Explains the concept of encryption and secure data transmission.
-
Introduces JSON as a popular data format for communication.
-
Defines API and its role in application communication.
-
Describes the importance of consistent standards in data exchange.
-
Outlines the principles of RESTful APIs.
-
Highlights the significance of Docker in modern application development.
Transcription
Przypuśćmy, że jesteś właścicielem aplikacji, która w zawrotnym tempie zaczyna zdobywać popularność. Codziennie rejestruje się tysiąca nowych użytkowników głodnych wrażeń. Okazuje się wtedy, że baza danych, którą posiadacie, staje się zbyt mała, dlatego musisz ją jakoś rozszerzyć. Pierwsze rozwiązanie, które przychodzi na myśl, to dodanie większej ilości pamięci RAM do istniejącego serwera z bazą danych, tak zwane skalowanie wertykalne. Wrzucasz więc tę pamięć, ale po jakimś czasie znowu zaczyna brakować miejsca, bo użytkowników ciągle przybywa, więc znowu musisz zwiększyć pamięć swojej bazy danych. No ale nie możesz przecież w nieskończoność dokładać pamięci do tego jednego serwera, bo takie serwery nie istnieją. Musisz więc znaleźć inne rozwiązanie i w tym momencie na białym koniu wjeżdża skalowanie horyzontalne, które różni się od skalowania wertykalnego tym, że zamiast dokładać coraz więcej i więcej pamięci do jednej maszyny, stawiasz kolejne serwery z bazami danych i dzielisz dane pomiędzy te kilka baz. Zaletą takiego podejścia jest też bezpieczeństwo, bo zauważ, że w przypadku skalowania wertykalnego, gdzie mamy jedną instancję serwera, którą rozszerzamy, to w przypadku awarii, kiedy taka baza danych padnie, to i cała aplikacja staje się nieużywalna, przez co tracisz użytkowników. Z kolei skalując się horyzontalnie, jeśli którejś z instancji baz danych padnie, to przez ten okres możesz zapisywać dane do innej instancji. Jednak aby utrzymać wiele serwerów w ciągłej pracy, po pierwsze potrzebujesz dużego pomieszczenia w bezpiecznym miejscu, a po drugie kilku ludzi, którzy na bieżąco będą monitorować co się z nimi dzieje i zarządzać całym tym systemem. Jeśli twoja aplikacja przynosi miliardy zysku, to nie będziesz miał z tym problemu, ale jeśli jesteście startupem, to najpewniej będziesz musiał skorzystać ze słynnej chmury obliczeniowej. Chmura to nic innego jak wynajmowanie maszyn, serwerów i innych zasobów przez internet od zewnętrznej firmy, gdzie właśnie ta firma jest odpowiedzialna za bezpieczeństwo i manualną obsługę swoich serwerów, a ty za odpowiednią opłatą martwisz się jedynie o swoje systemy, które stoją na tych serwerach. Najpopularniejszymi dostawcami chmury obliczeniowej są Amazon ze swoim AWS, Google i jego Google Cloud Platform oraz Microsoft będące właścicielami Azure. Te molochy nie mają praktycznie żadnych ograniczeń, dzięki czemu za pomocą paru kliknięć na ładnej stronie utworzysz nową instancję bazy danych, serwera czy czego tam aktualnie potrzebujesz. A skoro twoja baza danych już jest zabezpieczona na duży ruch dzięki skalowaniu horyzontalnemu, to prawdopodobnie to samo będziesz musiał zastosować przez samych instancjach aplikacji, które też mają ograniczoną pulę wątków i zapytań, które mogą na dany moment obsłużyć. Ale skoro postawisz wiele instancji aplikacji na różnych serwerach, to każda z nich będzie miała inny adres IP. Jak zatem wykonywać request do twojej aplikacji? No będziesz musiał skorzystać z load balancera, czyli kolejnego serwera, który metaforycznie postawisz przed serwerami z aplikacjami i to do tego load balancera będziesz kierować cały ruch. Będziesz miał więc tylko jeden adres tego właśnie load balancera, a on otrzymywany za pytania będzie rozdzielał pomiędzy instancje twoich aplikacji za pomocą jakiegoś algorytmu, tak aby nie obciążać jednej instancji aplikacji, a mniej więcej współmiernie każdą z nich. Takim najprostszym algorytmem jest round robin, który po kolei wysyła request do każdej instancji, np. mając postawione trzy aplikacje, pierwszy request może pójść do pierwszej aplikacji, kolejny do drugiej, trzeci request do trzeciej instancji i czwarty znowu do tej pierwszej i tak w kółko. Ale mimo takiej skalowalności twoja aplikacja może być wciąż niewydajna, bo wyobraź sobie, że jesteś właścicielem Netflixa, wypuszczacie nowy serial i tysiące użytkowników chce go obejrzeć w przeciągu godziny, a więc aplikacja musi tysiąckrotnie odpytywać bazę danych i wysyłać ten sam serial w odpowiedzi, co jest bezsensowne, bo przecież to są ciągle te same dane. W tym wypadku wykorzystasz caching, czyli dodatkową pamięć na przechowywanie odpytywanych danych i caching można stosować na wiele sposobów, np. bezpośrednio w pamięci podręcznej komputera, pamięci przeglądarki czy po stronie serwera. Dzięki temu odciążasz bazę danych, bo najpierw, kiedy leci zapytanie do serwera o jakiś zasób, sprawdzana jest, czy ten zasób nie znajduje się już przypadkiem w cachu. Jeśli tak, to jest od razu zwracany, a jeśli nie, to dopiero wtedy męczymy bazę danych. Ale co z tego, że będziesz miał caching, skoro serwery aplikacji są postawione w USA, a serial chcą obejrzeć również użytkownicy z Azji, Australii i Ameryki Południowej. Musisz więc znaleźć inne rozwiązanie, które znacznie przyspieszy przesłanie danych na tak odległe końce, dlatego zgłębmy CDN, czyli Content Delivery Network. Krótko mówiąc, CDN to rozproszona sieć serwerów w różnych zakątkach świata odpowiedzialnych za caching danych. Działa to tak, że mamy główny serwer np. w USA, który następnie kopiuje zdjęcia, filmy, czy nawet pliki HTML i Javascriptowe do tych właśnie serwerów, dzięki czemu użytkownicy z dowolnego miejsca na Ziemi mają źródło danych znacznie bliżej siebie. Ale aby komunikacja pomiędzy Twoim komputerem a serwerem mogła się odbyć, potrzebne są adresy IP, czyli unikalny adres przydzielany urządzeniom w sieci komputerowej, składający się z cyfr i umożliwiający identyfikację danego urządzenia. Adres IP może być publiczny lub prywatny, co najczęściej wykorzystuje się w lokalnych sieciach firmowych albo domowych. Jednak chcąc wejść na stronę np. Google.com nie wpisujesz adresu IP serweru w Google, ale domenę. Zatem jak to się dzieje, że mimo niepodawania adresu IP, przeglądarka wie na jaką stronę Cię przekierować? No odpowiedzialny jest za to system DNS, czyli Domain Name System, który mapuje adresy IP na czytelne nazwy domen. Działa to mniej więcej tak jak książka telefoniczna, w której po numerze telefonu możesz znaleźć przypisaną do tego numeru osobę. Serwery DNS tłumaczą żądania nazw zwane zapytaniami na adresy IP, kontrolując do którego serwera dotrze użytkownik końcowy po wpisaniu nazwy domeny w przeglądarce. Czyli kiedy chcesz wyszukać jakąś stronę i wpisujesz jej adres URL w przeglądarce, ta przeglądarka wysyła zapytanie do serwera DNS. Jeśli ten serwer przechowuje w keszu adres IP domeny, o którą pytamy, to zwraca go i strona się ładuje, ale jeśli nie ma, wtedy wysyła zapytanie dalej do innych serwerów DNS z zachowaniem odpowiedniej hierarchii. Zacznę od roota, czyli najwyżej ułożonego serwera przechowującego informacje o lokalizacjach serwerów TLD, czyli serwerów przechowujących top-level domains, czyli główne domeny takich jak com, org, pl itd. Następnie root kieruje zapytanie do odpowiedniego serwera nazw domenowych, a ten do authoritative servers, gdzie każda domena musi być powiązana z co najmniej jednym takim serwerem autoratywnym, przechowującym informacje o konkretnych rekordach DNS dla tej domeny, adresach IP itd. Końcowo serwer DNS, który obsługuje zapytaną domenę, zwraca jej adres IP lub błąd, gdy żaden adres nie odpowiada domenie. Ale sama nazwa domeny nie wystarczy, żeby przekierować cię do danej strony, potrzebny jest też protokół komunikacyjny, np. HTTP, w którym użytkownik określa, jakiej metody HTTP chce użyć, jakie zasoby chce uzyskać, gdzie request ma być wysłany oraz w jakim formacie akceptuje odpowiedź. Przed wysłaniem danych następuje jeszcze połączenie TCP pomiędzy przeglądarką i serwerem, do którego zapytanie ma być wysłane, a następnie request jest wysyłany do serwera, który go analizuje zgodnie z otrzymanymi danymi, po czym wysyła odpowiedź zwrotną HTTP z odpowiednim statusem oraz z zasobem, o który pytał klient. Natomiast główną wadą tego protokołu jest to, że dane są wysyłane czystym, niezaszyfrowanym tekstem, przez co każdy może odczytać te dane i wykraść hasła czy numery kart kredytowych. Dlatego stosuje się protokół HTTPS, który poza tym, co oferuje HTTP, jest też szyfrowany za pomocą protokołu TLS, na co wskazuje ta kłódka w pasku przeglądarki. Szyfrowanie odbywa się za pomocą tzw. handshake, czyli ustalenia pomiędzy klientem i serwerem, jak te dane mają być szyfrowane. Czyli przed wysłaniem danych klient dogaduje się z serwerem, jakiej wersji szyfrowania użyją, tak aby obaj byli kompatybilni z tą wersją. Następnie serwer wysyła klientowi certyfikat zawierający m.in. klucz publiczny, którego klient używa do tzw. szyfrowania asymetrycznego, w którym dane szyfrowane za pomocą klucza publicznego mogą być odszyfrowane tylko za pomocą klucza prywatnego, po czym wysyłany jest request właśnie z zaszyfrowanymi danymi tym kluczem publicznym. A jak już mowa o przesyłaniu danych, to przejdźmy do JSON-a, czyli lekkiego formatu danych, w jakim te dane mogą być wysłane. Jest to aktualnie jeden z najpopularniejszych formatów dzięki swojej uniwersalności i czytelności. Dane reprezentuje się w nim poprzez parę kluczy wartość, za pomocą których reprezentować można obiekty, tablice czy primitywy, takie jak liczby albo napisy. Jeśli pisałeś w javascriptie, to nie będziesz miał żadnych problemów ze zrozumieniem struktury JSON-a, bo to właśnie na JS jest ona bazowana, a jak nigdy nie pisałeś w javascriptie, to i tak zrozumiesz go w 3 minuty, co pokażę na poniższym przykładzie, w którym zdefiniowałem użytkownika z e-mailem, EID oraz tablicą wypożyczonych tytułów książek. A aby wiedzieć, gdzie w ogóle ten request z danymi wysłać, korzysta się z API, czyli Application Programming Interface, czyli interfejsu programistycznego aplikacji, a mówiąc ludzkim językiem, zbioru jakichś zasad, reguł określających w jaki sposób programy mają się ze sobą komunikować. A po co to? Żyjemy we współczesnym świecie, gdzie istnieją miliony różnych urządzeń, które wymieniają się danymi na wszelaki sposób. Teraz wyobraź sobie, że wszystkie te urządzenia posługują się różnymi zasadami wymiany tych danych. Łączenie się z takimi urządzeniami i integracja ich byłaby dużym wyzwaniem, dlatego stosuje się właśnie API, a m.in. WebAPI, z którego zazwyczaj będziesz korzystać jako programista, to rodzaj sieciowego API zdefiniowanego za pomocą endpointów, czyli punktów końcowych, które użytkownik bądź inna aplikacja może odpytać, aby uzyskać jakieś dane. Np. chcąc wyciągnąć listę użytkowników Twittera, endpoint mógłby wyglądać następująco. Skoro już wiemy czym jest API i WebAPI, możemy przejść do definicji RestAPI, gdzie RestAPI to styl architektoniczny określający reguły mówiące o tym, jak API powinno być zbudowane, aby aplikacje były spójne, a komunikacja m.in. łatwa. I te zasady to m.in. jednolity interfejs komunikacyjny oznaczający, że pomiędzy klientem a serwerem dane powinny być wymieniane w tym samym formacie i zakresie, niezależnie od urządzenia z jakiego korzysta użytkownik. KlientServer to druga zasada mówiąca o rozdzieleniu pomiędzy klientem i serwerem, co ułatwia i zwiększa możliwości skalowania oraz niezależnego ich rozwijania. Bezstanowość, czyli stateless to trzecia zasada oznaczająca, że serwer nie powinien przechowywać danych klienta koniecznych do poprawnego działania aplikacji, tylko to klient przy każdym zapytaniu jest odpowiedzialny za wysłanie kompletu informacji, np. tokenu JWT, aby serwer mógł autoryzować użytkownika i sprawdzić, czy ma dostęp do danych, o które pyta. Cachability to czwarta zasada RESTA, która jest sposobem na zmniejszenie obciążenia serwera używając zasad cacha, o których już mówiłem. Piąta zasada mówi o odseparowaniu warstwy, czyli klienta nie interesuje to z jakimi zewnętrznymi serwisami łączy się serwer, klient chce tylko uzyskać odpowiedź, ale nie ma pojęcia o tym, co się dzieje pod spodem, aby tę odpowiedź uzyskać. I ostatnia, opcjonalna zasada mówi o możliwości wysłania kodu, np. JS, który może być wykonany po stronie klienta. Natomiast architektura API to nie wszystko, bo ważniejsza jest architektura systemu, do której możemy wykorzystać dockera, czyli narzędzie służące do konteneryzacji, za pomocą którego można w łatwy sposób tworzyć odizolowane środowiska. Wyobraź sobie, że pracujecie w zespole dziesięcioosobowym, a na waszą aplikację składa się baza danych Postgres, baza Redis do cachowania i oprócz tego trzy inne systemy. Ile by było problemów, gdyby każdy z waszego zespołu musiał lokalnie zainstalować każdy z tych systemów, po czym okazałoby się, że u jednych działa, a u innych nie, bo macie komputery z różnymi systemami i architekturami, a dodatkowo bazy, które stawiacie mają różne wersje. Między innymi dlatego warto używać dockera, gdzie za pomocą jednego pliku konfiguracyjnego możecie zdefiniować obrazy, jakich potrzebujecie, gdzie obraz zawiera wszystkie potrzebne biblioteki, kod i narzędzia potrzebne do utworzenia kontenera. A kontener to już właśnie to odizolowane środowisko, w którym możesz pracować. Docker może być też bardzo przydatny przy tworzeniu aplikacji mikroserwisowej, czyli aplikacji podzielonej na wiele małych serwisów odpowiedzialnych za pojedyncze części. Tak jak kod w aplikacji dzielisz na funkcje, gdzie każda funkcja jest odpowiedzialna za jedną rzecz, ale w całości tworzą całą aplikację, tak i te aplikacje można podzielić na mniejsze aplikacje komunikujące się między sobą, np. przy pomocy REST API. A więc jeden mikroserwis może być odpowiedzialny za logowanie, a inny za procesowanie płatności, cały czas wymieniając między sobą dane, aby zachować spójność. Zaletą mikroserwisów jest to, że każdy taki miniserwis możesz rozwijać niezależnie w różnych technologiach, np. logowanie w Pythonie, a procesowanie płatności w Java. Trzeba jednak pamiętać, że nie wszystko jest takie kolorowe, a stworzenie dobrej, utrzymywalnej i stabilnej aplikacji mikroserwisowej wymaga sporo pracy, doświadczenia i automatyzacji procesów, czyli Continuous Integration i Continuous Deployment. CI służy do regularnego integrowania kodu źródłowego we wspólnym repozytorium, więc kiedy piszesz kod na osobnym branczu, który pushujesz do repozytorium zdalnego, to za pomocą wcześniej zdefiniowanych pipeline'ów może zostać uruchomiony proces budowania kodu oraz uruchomienie testów, dzięki czemu można uniknąć sporo błędów jeszcze przed zmerżowaniem kodu do głównego brancza. CD to z kolei automatyczny proces deployowania albo inaczej wdrażania kodu na środowisko produkcyjne czy przedprodukcyjne też za pomocą odpowiednio zdefiniowanych pipeline'ów, co możesz zrobić za pomocą GitHub Actions, GitLab CI CD czy Jenkins. Oprócz automatyzacji procesów przy budowie aplikacji mikroserwisowej musisz też zadbać o system, który będzie chronił Cię przed błędami komunikacji pomiędzy tymi serwisami, bo wyobraź sobie, że mikroserwis A do wykonania swojej akcji musi się skomunikować z mikroserwisem B, który z jakichś powodów przestał działać i nie odpowiada. Wówczas mikroserwis A i inne serwisy używające serwisu B muszą zostać o tym poinformowane. Pomóc w tym może Ci Circuit Breaker, czyli wzorzec projektowy, który zapobiega nieskończonemu powtarzaniu operacji, które z góry skazane są na porażkę, bo odpytywany serwis leży. Circuit Breaker inspiruje się układem elektrycznym i jeśli wszystko działa tak jak powinno, to jest on w stanie otwartym, przepuszczając żądania do mikroserwisów, stale je monitorując. Natomiast jeśli zauważy, że coś nie działa, np. odpowiedź na żądanie trwa zbyt długo, albo któryś ze serwisów sypie błędami, wtedy przechodzi w stan zamknięty, blokując ruch do niedziałającego serwisu, a zamiast tego zwraca jakąś domyślną, wcześniej ustaloną wartość. Po jakimś czasie przechodzi w stan półotwarty, przepuszczając jedno lub kilka żądań, czym sprawdza, czy niedziałający serwis już się ogarnął i jeśli tak, to znowu się otwiera. Dużym wyzwaniem jest też obsługa bazy danych nie tylko w aplikacjach mikroserwisowych, ale ogólnie wszystkich aplikacjach korzystających głównie z baz relacyjnych, czyli baz opartych na tabelach składających się z kolumn i wierszy, a mówię tutaj o zbiorze właściwości AC, reprezentującego zbiór zasad będących gwarantem poprawnego przetwarzania transakcji. Transakcja to z kolei zbiór kilku operacji, które albo muszą się wykonać wszystkie, albo żadna się nie wykona. Na przykład operacja bankowa, gdzie klient A przesyła pieniądze klientowi B, czyli pierwsza operacja to odjęcie pieniędzy z konta klienta A, a druga operacja to dodanie tych pieniędzy na konto klienta B. Gdyby któraś z tych operacji się wywaliła, to druga też musi się wywalić, bo nie może być tak, że klientowi A pobierze pieniądze, a B ich nie otrzyma. Zatem ACID dzieli się na poszczególne reguły. Atomicity, czyli atomowość, oznaczające to, o czym mówiłem przed chwilą, że albo wszystkie transakcje się wykonają, albo żadna. Konsystencji, czyli spójność mówiąca o tym, że baza danych po wykonaniu transakcji musi zachować spójność danych, czyli ich poprawność. Izolacja oznacza, że każda transakcja powinna być niezależna od innych transakcji i dopóki zmiany w jednej transakcji nie będą zatwierdzone i zapisane, to nie będą widoczne dla innych, a ostatnia zasada durability, czyli trwałość, oznacza, że po zakończeniu transakcji i zapisaniu danych, te dane muszą być przechowywane w bezpiecznym miejscu, aby były trwałe i nieodwracalne. No chyba, że korzystasz z baz NoSQL, które mogą, ale nie muszą być kompatybilne z zasadami ACID, a dane w bazach NoSQL nie są przechowywane w tabelach, ale np. w formacie dokumentów jak w MongoDB czy w postaci grafu jak w Neo4j. Dzięki swojej elastyczności są łatwiejsze w skalowaniu, dzięki czemu mogą się nadać do aplikacji przechowujących ogromną ilość danych, np. baza grafowa może być użyta jako przedstawienie sieci społecznościowej we Facebooku, gdzie węzłami są użytkownicy, a łączące ich krawędzie to relacje, jakie między tymi użytkownikami zachodzą. Swoją drogą, jak już mówimy o Facebooku, to zapewne korzystają tam ze systemów kolejkowych, czyli systemów odciążających aplikacje czytające dane zwane konsumerami, które mogą nie nadążać przy ogromnej ilości tych danych. Kolejki mogą przechowywać te dane i wiadomości wysyłane przez subskrybentów, czyli aplikacje, które łączą się do tej kolejki i w odpowiednim czasie, jeśli konsumer będzie gotowy na odbiór tych danych, wysłać mu je. Jeśli wytrwałeś do tego momentu i coś pożytecznego wyniosłeś z tego filmu, to zostaw łapkę w górę i suba, joł!