{"id":37956,"date":"2019-10-31T22:20:47","date_gmt":"2019-10-31T19:20:47","guid":{"rendered":"https:\/\/prohoster.info\/blog\/newsql-nosql-acid\/"},"modified":"2019-10-31T22:20:47","modified_gmt":"2019-10-31T19:20:47","slug":"newsql-nosql-acid","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/newsql-nosql-acid","title":{"rendered":"NewSQL = NoSQL+ACID","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/973145efa566bca10ffe7ba95733a1d5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nDo niedawna w Odnoklassnikach przechowywano oko\u0142o 50 TB danych przetwarzanych w czasie rzeczywistym w SQL Server. Dla takiej obj\u0119to\u015bci zapewnienie szybkiego, niezawodnego i odpornego na awarie dost\u0119pu za pomoc\u0105 SQL DBMS jest praktycznie niemo\u017cliwe. Zazwyczaj w takich przypadkach wykorzystuje si\u0119 jedn\u0105 z baz NoSQL, ale nie wszystko mo\u017cna przenie\u015b\u0107 do NoSQL: niekt\u00f3re byty wymagaj\u0105 gwarancji transakcji ACID. <\/p>\n<p>To doprowadzi\u0142o nas do u\u017cycia bazy danych NewSQL, czyli DBMS, kt\u00f3ra zapewnia odporno\u015b\u0107 na awarie, skalowalno\u015b\u0107 i wydajno\u015b\u0107 system\u00f3w NoSQL, ale jednocze\u015bnie zachowuje klasyczne gwarancje ACID. Liczba dzia\u0142aj\u0105cych przemys\u0142owych system\u00f3w tej nowej klasy jest niewielka, dlatego sami wdro\u017cyli\u015bmy taki system i uruchomili\u015bmy go w produkcji. <\/p>\n<p>Jak to dzia\u0142a i co uda\u0142o si\u0119 osi\u0105gn\u0105\u0107 \u2014 czytaj dalej.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><br \/>\nDzi\u015b miesi\u0119czna widownia \u201eOdnoklassnik\u00f3w\u201d wynosi ponad 70 milion\u00f3w unikalnych odwiedzaj\u0105cych. My <noindex><a rel=\"nofollow\" href=\"https:\/\/www.similarweb.com\/top-websites\/category\/internet-and-telecom\/social-network\">jeste\u015bmy w pi\u0105tce<\/a><\/noindex> najwi\u0119kszych serwis\u00f3w spo\u0142eczno\u015bciowych na \u015bwiecie i w czo\u0142owej dwudziestce stron, na kt\u00f3rych u\u017cytkownicy sp\u0119dzaj\u0105 najwi\u0119cej czasu. Infrastruktura \u201eOK\u201d obs\u0142uguje bardzo du\u017ce obci\u0105\u017cenia: ponad milion \u017c\u0105da\u0144 HTTP\/s na fronty. Cz\u0119\u015bci parku serwer\u00f3w w liczbie ponad 8000 znajduj\u0105 si\u0119 blisko siebie \u2014 w czterech moskiewskich centrach danych, co pozwala na zapewnienie op\u00f3\u017anienia sieciowego poni\u017cej 1 ms mi\u0119dzy nimi.<\/p>\n<p>U\u017cywamy Cassandry od 2010 roku, zaczynaj\u0105c od wersji 0.6. Dzi\u015b w eksploatacji znajduje si\u0119 kilka dziesi\u0105tek klastr\u00f3w. Najszybszy klaster obs\u0142uguje ponad 4 miliony operacji na sekund\u0119, a najwi\u0119kszy przechowuje 260 TB. <\/p>\n<p>Jednak to wszystko s\u0105 zwyk\u0142e klastry NoSQL, u\u017cywane do przechowywania <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A1%D0%BE%D0%B3%D0%BB%D0%B0%D1%81%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D0%B2_%D0%BA%D0%BE%D0%BD%D0%B5%D1%87%D0%BD%D0%BE%D0%BC_%D1%81%D1%87%D1%91%D1%82%D0%B5\">s\u0142abo sp\u00f3jnych<\/a><\/noindex> danych. Chcieli\u015bmy jednak zast\u0105pi\u0107 g\u0142\u00f3wne sp\u00f3jne repozytorium, Microsoft SQL Server, kt\u00f3re by\u0142o u\u017cywane od powstania \u201eOdnoklassnik\u00f3w\u201d. Repozytorium sk\u0142ada\u0142o si\u0119 z ponad 300 maszyn SQL Server Standard Edition, na kt\u00f3rych znajdowa\u0142o si\u0119 50 TB danych \u2014 byt\u00f3w biznesowych. Dane te s\u0105 modyfikowane w ramach transakcji ACID i wymagaj\u0105 <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ACID\">wysokiej sp\u00f3jno\u015bci<\/a><\/noindex>.<\/p>\n<p>Aby rozdzieli\u0107 dane po w\u0119z\u0142ach SQL Server, u\u017cywali\u015bmy zar\u00f3wno partycjonowania pionowego, jak i poziomego. <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Partition_(database)\">partycjonowanie<\/a><\/noindex> (sharding). Historycznie u\u017cywali\u015bmy prostego schematu sharding danych: ka\u017cdej encji przypisywano token \u2014 funkcj\u0119 z ID encji. Encje z tym samym tokenem by\u0142y umieszczane na jednym serwerze SQL. Relacja typu master-detail by\u0142a realizowana w ten spos\u00f3b, \u017ceby tokeny g\u0142\u00f3wnego i podrz\u0119dnego rekordu zawsze si\u0119 zgadza\u0142y i znajdowa\u0142y si\u0119 na tym samym serwerze. W sieci spo\u0142eczno\u015bciowej niemal wszystkie rekordy s\u0105 tworzone w imieniu u\u017cytkownika \u2014 oznacza to, \u017ce wszystkie dane u\u017cytkownika w obr\u0119bie jednego funkcjonalnego podsystemu s\u0105 przechowywane na jednym serwerze. Innymi s\u0142owy, w transakcji biznesowej niemal zawsze bra\u0142y udzia\u0142 tabele jednego serwera SQL, co pozwala\u0142o na zapewnienie sp\u00f3jno\u015bci danych za pomoc\u0105 lokalnych transakcji ACID, bez konieczno\u015bci u\u017cywania <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Two-phase_commit_protocol\">wolnych i niesolidnych<\/a><\/noindex> rozproszonych transakcji ACID.<\/p>\n<p>Dzi\u0119ki shardingowi oraz w celu przyspieszenia dzia\u0142ania SQL:<\/p>\n<ul>\n<li>Nie u\u017cywamy ogranicze\u0144 Foreign key, poniewa\u017c przy sharding ID encji mo\u017ce znajdowa\u0107 si\u0119 na innym serwerze.<\/li>\n<li>Nie u\u017cywamy procedur sk\u0142adowanych i trigger\u00f3w z powodu dodatkowego obci\u0105\u017cenia procesora bazy danych.<\/li>\n<li>Nie u\u017cywamy JOIN\u00f3w z powodu wszystkich wy\u017cej wymienionych rzeczy i wielu przypadkowych odczyt\u00f3w z dysku.<\/li>\n<li>Poza transakcj\u0105, aby zredukowa\u0107 blokady, u\u017cywamy poziomu izolacji Read Uncommitted.<\/li>\n<li>Wykonujemy tylko kr\u00f3tkie transakcje (\u015brednio kr\u00f3tsze ni\u017c 100 ms).<\/li>\n<li>Nie u\u017cywamy wielowierszowych UPDATE i DELETE z powodu du\u017cej liczby blokad \u2014 aktualizujemy tylko po jednym rekordzie.<\/li>\n<li>Zapytania zawsze wykonujemy tylko na indeksach \u2014 zapytanie z planem pe\u0142nego przeszukiwania tabeli oznacza dla nas przeci\u0105\u017cenie bazy danych i jej awari\u0119.<\/li>\n<\/ul>\n<p>\nTe kroki pozwoli\u0142y wycisn\u0105\u0107 z serwer\u00f3w SQL niemal maksymaln\u0105 wydajno\u015b\u0107. Jednak pojawia\u0142o si\u0119 coraz wi\u0119cej problem\u00f3w. Przyjrzyjmy si\u0119 im.<\/p>\n<h2>Problemy z SQL<\/h2>\n<p><\/p>\n<ul>\n<li>Poniewa\u017c u\u017cywali\u015bmy w\u0142asnego sharding, dodawanie nowych shard\u00f3w by\u0142o wykonywane przez administrator\u00f3w r\u0119cznie. Przez ca\u0142y ten czas skalowalne repliki danych nie obs\u0142ugiwa\u0142y zapyta\u0144. <\/li>\n<li>W miar\u0119 wzrostu liczby rekord\u00f3w w tabeli spada pr\u0119dko\u015b\u0107 wstawiania i modyfikacji, a przy dodawaniu indeks\u00f3w do istniej\u0105cej tabeli pr\u0119dko\u015b\u0107 spada wielokrotnie, tworzenie i odbudowa indeks\u00f3w odbywa si\u0119 z przestojem.<\/li>\n<li>Obecno\u015b\u0107 w produkcji niewielkiej liczby Windows dla SQL Server utrudnia zarz\u0105dzanie infrastruktur\u0105.<\/li>\n<\/ul>\n<p>\nAle g\u0142\u00f3wnym problemem jest \u2014 <\/p>\n<h2>Odporno\u015b\u0107 na awarie<\/h2>\n<p>\nKlasyczny serwer SQL ma s\u0142ab\u0105 odporno\u015b\u0107 na awarie. Za\u0142\u00f3\u017cmy, \u017ce masz tylko jeden serwer baz danych i zawodzi on raz na trzy lata. W tym czasie strona internetowa przestaje dzia\u0142a\u0107 na 20 minut, co jest do zaakceptowania. Je\u015bli masz 64 serwery, to strona przestaje dzia\u0142a\u0107 raz co trzy tygodnie. A je\u015bli masz 200 serwer\u00f3w, to strona nie dzia\u0142a co tydzie\u0144. To jest problem. <\/p>\n<p>Co mo\u017cna zrobi\u0107, aby zwi\u0119kszy\u0107 odporno\u015b\u0107 na awarie serwera SQL? Wikipedia sugeruje nam zbudowa\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/High-availability_cluster\">wysokodost\u0119pny klaster<\/a><\/noindex>: w kt\u00f3rym w przypadku awarii kt\u00f3regokolwiek z komponent\u00f3w istnieje duplikat.<\/p>\n<p>To wymaga parku kosztownego sprz\u0119tu: liczne duplikacje, \u015bwiat\u0142owody, wsp\u00f3lne magazyny, a tak\u017ce w\u0142\u0105czenie rezerwy dzia\u0142a niepewnie: oko\u0142o 10% w\u0142\u0105cze\u0144 ko\u0144czy si\u0119 awari\u0105 w\u0119z\u0142a zapasowego, zale\u017cnie od g\u0142\u00f3wnego w\u0119z\u0142a. <\/p>\n<p>Ale g\u0142\u00f3wn\u0105 wad\u0105 takiego wysokodost\u0119pnego klastra jest zerowa dost\u0119pno\u015b\u0107 w przypadku awarii centrum danych, w kt\u00f3rym si\u0119 znajduje. \u201eOdnoklassniki\u201d maj\u0105 cztery centra danych i musimy zapewni\u0107 dzia\u0142anie przy ca\u0142kowitej awarii jednego z nich.<\/p>\n<p>W tym celu mogliby\u015bmy zastosowa\u0107 <noindex><a rel=\"nofollow\" href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms151196.aspx\">replikacj\u0119 Multi-Master<\/a><\/noindex> , wbudowan\u0105 w SQL Server. To rozwi\u0105zanie jest znacznie dro\u017csze z powodu koszt\u00f3w oprogramowania i cierpi na dobrze znane problemy z replikacj\u0105 \u2013 nieprzewidywalne op\u00f3\u017anienia transakcji przy replikacji synchronizacyjnej i op\u00f3\u017anienia w stosowaniu replikacji (a w konsekwencji utracone modyfikacje) przy asynchronicznej. Oczekuj\u0105ce <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/relational-databases\/replication\/transactional\/peer-to-peer-conflict-detection-in-peer-to-peer-replication\">r\u0119czne rozwi\u0105zywanie konflikt\u00f3w<\/a><\/noindex> czyni t\u0119 opcj\u0119 ca\u0142kowicie niepraktyczn\u0105 dla nas.<\/p>\n<p>Wszystkie te problemy wymaga\u0142y radykalnego rozwi\u0105zania i przyst\u0105pili\u015bmy do ich szczeg\u00f3\u0142owej analizy. Tutaj musimy zapozna\u0107 si\u0119 z tym, co g\u0142\u00f3wnie robi SQL Server \u2013 transakcjami.<\/p>\n<h2>Prosta transakcja<\/h2>\n<p>\nRozwa\u017cmy najprostsz\u0105, z punktu widzenia programisty SQL, transakcj\u0119: dodanie zdj\u0119cia do albumu. Albumy i zdj\u0119cia s\u0105 przechowywane w r\u00f3\u017cnych tabelach. Album ma licznik publicznych zdj\u0119\u0107. W takim przypadku transakcja dzieli si\u0119 na nast\u0119puj\u0105ce kroki: <\/p>\n<ol>\n<li>Blokujemy album po kluczu.<\/li>\n<li>Tworzymy wpis w tabeli zdj\u0119\u0107. <\/li>\n<li>Je\u015bli zdj\u0119cie ma publiczny status, zwi\u0119kszamy w albumie licznik publicznych zdj\u0119\u0107, aktualizujemy wpis i zatwierdzamy transakcj\u0119.<\/li>\n<\/ol>\n<p>\nLub w postaci pseudokodu:<\/p>\n<pre><code>TX.start(\"Albums\", id);\nAlbum album = albums.lock(id);\nPhoto photo = photos.create(\u2026);\n\nif (photo.status == PUBLIC) {\n    album.incPublicPhotosCount();\n}\nalbum.update();\n\nTX.commit();<\/code><\/pre>\n<p>\nWidz\u0119, \u017ce najcz\u0119stszy scenariusz transakcji biznesowej polega na odczytywaniu danych z bazy danych do pami\u0119ci serwera aplikacji, wprowadzaniu pewnych zmian i zapisywaniu nowych warto\u015bci z powrotem do bazy danych. Zazwyczaj w takiej transakcji aktualizujemy kilka encji, kilka tabel. <\/p>\n<p>Podczas wykonywania transakcji mo\u017ce doj\u015b\u0107 do konkurencyjnej modyfikacji tych samych danych z innego systemu. Na przyk\u0142ad, system antyspamowy mo\u017ce uzna\u0107, \u017ce u\u017cytkownik jest podejrzany, a zatem wszystkie zdj\u0119cia u\u017cytkownika nie powinny by\u0107 ju\u017c publiczne, powinny zosta\u0107 wys\u0142ane do moderacji, co oznacza, \u017ce trzeba zmieni\u0107 photo.status na inn\u0105 warto\u015b\u0107 i zaktualizowa\u0107 odpowiednie liczniki. Oczywi\u015bcie, je\u015bli ta operacja b\u0119dzie odbywa\u0107 si\u0119 bez gwarancji atomowo\u015bci i izolacji konkurencyjnych modyfikacji, jak w <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ACID\">ACID<\/a><\/noindex>, to rezultat nie b\u0119dzie tym, czego potrzebujemy \u2014 albo licznik zdj\u0119\u0107 poka\u017ce b\u0142\u0119dn\u0105 warto\u015b\u0107, albo nie wszystkie zdj\u0119cia zostan\u0105 wys\u0142ane do moderacji. <\/p>\n<p>Takiego kodu, manipuluj\u0105cego r\u00f3\u017cnymi encjami biznesowymi w ramach jednej transakcji, napisano w ca\u0142ej historii portalu Odnoklassniki bardzo du\u017co. Wed\u0142ug do\u015bwiadcze\u0144 migracji na NoSQL z <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Eventual_consistency\">Sp\u00f3\u017aniona sp\u00f3jno\u015b\u0107<\/a><\/noindex> wiemy, \u017ce najwi\u0119ksze trudno\u015bci (i wydatki czasowe) wynikaj\u0105 z konieczno\u015bci opracowywania kodu zapobiegaj\u0105cego naruszeniom sp\u00f3jno\u015bci danych. Dlatego g\u0142\u00f3wnym wymaganiem wobec nowego magazynu uznali\u015bmy zapewnienie dla logiki aplikacyjnej prawdziwych transakcji ACID. <\/p>\n<p>Inne, nie mniej wa\u017cne wymagania to:<\/p>\n<ul>\n<li>W przypadku awarii centrum danych zar\u00f3wno odczyt, jak i zapis do nowego magazynu powinny by\u0107 dost\u0119pne.<\/li>\n<li>Zachowanie obecnej pr\u0119dko\u015bci rozwoju. To znaczy, podczas pracy z nowym magazynem ilo\u015b\u0107 kodu powinna by\u0107 mniej wi\u0119cej taka sama, nie powinno by\u0107 potrzeby dopisywania czegokolwiek do magazynu, opracowywania algorytm\u00f3w rozwi\u0105zywania konflikt\u00f3w, utrzymywania wt\u00f3rnych indeks\u00f3w itp. <\/li>\n<li>Pr\u0119dko\u015b\u0107 dzia\u0142ania nowego magazynu powinna by\u0107 wystarczaj\u0105co wysoka zar\u00f3wno podczas odczytu danych, jak i przy przetwarzaniu transakcji, co w praktyce oznacza\u0142o niew\u0142a\u015bciwo\u015b\u0107 akademicko rygorystycznych, uniwersalnych, ale wolnych rozwi\u0105za\u0144, jak na przyk\u0142ad <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Two-phase_commit_protocol\">dwuetapowych zatwierdze\u0144<\/a><\/noindex>.<\/li>\n<li>Automatyczne skalowanie w czasie rzeczywistym. <\/li>\n<li>Wykorzystanie zwyk\u0142ych, tanich serwer\u00f3w, bez potrzeby zakupu egzotycznego sprz\u0119tu. <\/li>\n<li>Mo\u017cliwo\u015b\u0107 rozwoju przechowywania przez deweloper\u00f3w firmy. Innymi s\u0142owy, priorytetowo traktowano w\u0142asne lub oparte na otwartym kodzie rozwi\u0105zania, najlepiej w Java.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Rozwi\u0105zania, rozwi\u0105zania<\/h2>\n<p>\nAnalizuj\u0105c mo\u017cliwe rozwi\u0105zania, doszli\u015bmy do dw\u00f3ch potencjalnych wybor\u00f3w architektury:<\/p>\n<p>Pierwszy \u2014 wzi\u0105\u0107 dowolny serwer SQL i zaimplementowa\u0107 potrzebn\u0105 odporno\u015b\u0107 na awarie, mechanizm skalowania, odporny klaster, rozwi\u0105zanie konflikt\u00f3w oraz rozproszone, niezawodne i szybkie transakcje ACID. Oceniamy t\u0119 opcj\u0119 jako do\u015b\u0107 nietrywialn\u0105 i czasoch\u0142onn\u0105.<\/p>\n<p>Druga opcja \u2014 wzi\u0105\u0107 gotowe przechowywanie NoSQL z zaimplementowanym skalowaniem, odpornym klastrem, rozwi\u0105zaniem konflikt\u00f3w i zrealizowa\u0107 transakcje oraz SQL samodzielnie. Na pierwszy rzut oka realizacja SQL, nie m\u00f3wi\u0105c ju\u017c o transakcjach ACID, wydaje si\u0119 zadaniem na lata. Ale potem zrozumieli\u015bmy, \u017ce zbi\u00f3r mo\u017cliwo\u015bci SQL, kt\u00f3re wykorzystujemy w praktyce, daleki jest od ANSI SQL tak samo, jak <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Apache_Cassandra#Cassandra_Query_Language\">Cassandra CQL<\/a><\/noindex> jest dalekie od ANSI SQL. Przygl\u0105daj\u0105c si\u0119 bli\u017cej CQL, zrozumieli\u015bmy, \u017ce jest wystarczaj\u0105co bliskie temu, czego potrzebujemy.<\/p>\n<h2>Cassandra i CQL<\/h2>\n<p>\nCzym wi\u0119c interesuje Cassandra, jakie ma mo\u017cliwo\u015bci?<\/p>\n<p>Przede wszystkim mo\u017cna tworzy\u0107 tabele obs\u0142uguj\u0105ce r\u00f3\u017cne typy danych, mo\u017cna wykona\u0107 SELECT lub UPDATE po kluczu g\u0142\u00f3wnym.<\/p>\n<pre><code>CREATE TABLE photos (id bigint KEY, owner bigint,\u2026);\nSELECT * FROM photos WHERE id=?;\nUPDATE photos SET \u2026 WHERE id=?;<\/code><\/pre>\n<p>\nAby zapewni\u0107 sp\u00f3jno\u015b\u0107 danych replik, Cassandra wykorzystuje <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Quorum_(distributed_computing)\">podej\u015bcie kworumowe.<\/a><\/noindex>. W najprostszym przypadku oznacza to, \u017ce przy umieszczaniu trzech replik tego samego wiersza na r\u00f3\u017cnych w\u0119z\u0142ach klastra, zapis jest uznawany za udany, je\u015bli wi\u0119kszo\u015b\u0107 w\u0119z\u0142\u00f3w (tj. dwa z trzech) potwierdzi\u0142a powodzenie tej operacji zapisu. Dane wiersza s\u0105 uwa\u017cane za sp\u00f3jne, je\u015bli podczas odczytu zosta\u0142o przeskanowanych wi\u0119kszo\u015b\u0107 w\u0119z\u0142\u00f3w, kt\u00f3re potwierdzi\u0142y ich. Tak wi\u0119c, przy posiadaniu trzech replik, zapewniona jest pe\u0142na i natychmiastowa sp\u00f3jno\u015b\u0107 danych w przypadku awarii jednego z w\u0119z\u0142\u00f3w. Takie podej\u015bcie pozwoli\u0142o nam wdro\u017cy\u0107 jeszcze bardziej niezawodny schemat: zawsze wysy\u0142a\u0107 zapytania do wszystkich trzech replik, czekaj\u0105c na odpowied\u017a od dw\u00f3ch najszybszych. Op\u00f3\u017aniona odpowied\u017a trzeciej repliki w takim przypadku jest ignorowana. Opo\u017aniony w odpowiedzi w\u0119ze\u0142 mo\u017ce mie\u0107 powa\u017cne problemy \u2014 przerwy w dzia\u0142aniu, zbieranie pami\u0119ci w JVM, reclaim pami\u0119ci w j\u0105drze linux, awaria sprz\u0119tu, utrata po\u0142\u0105czenia z sieci\u0105. Jednak na operacje klienta i na dane to nie wp\u0142ywa.<\/p>\n<p>Podej\u015bcie, w kt\u00f3rym zwracamy si\u0119 do trzech w\u0119z\u0142\u00f3w, a otrzymujemy odpowied\u017a od dw\u00f3ch, nazywa si\u0119 <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Speculative_execution\">spekulacj\u0105<\/a><\/noindex>: zapytanie do zb\u0119dnych replik jest wysy\u0142ane jeszcze przed tym, jak \u201eodpadnie\u201d. <\/p>\n<p>Kolejn\u0105 z zalet Cassandry jest Batchlog \u2014 mechanizm, kt\u00f3ry gwarantuje ca\u0142kowite zastosowanie lub ca\u0142kowite niezastosowanie pakietu wprowadzanych przez Ciebie zmian. Pozwala to nam rozwi\u0105za\u0107 A w ACID \u2014 atomowo\u015b\u0107 z pude\u0142ka.<\/p>\n<p>\u0421\u0430\u043c\u043e\u0435 \u0431\u043b\u0438\u0437\u043a\u043e\u0435 \u043a \u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u044f\u043c \u0432 Cassandra \u2014 \u044d\u0442\u043e \u0442\u0430\u043a \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0435 &#171;<noindex><a rel=\"nofollow\" href=\"https:\/\/docs.datastax.com\/en\/cql\/3.3\/cql\/cql_using\/useInsertLWT.html\">transakcje lekkie<\/a><\/noindex>&#171;. \u041d\u043e \u043e\u0442 \u00ab\u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0445\u00bb ACID-\u0442\u0440\u0430\u043d\u0437\u0430\u043a\u0446\u0438\u0439 \u043e\u043d\u0438 \u0434\u0430\u043b\u0435\u043a\u0438: \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435, \u044d\u0442\u043e \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Compare-and-swap\">CAS<\/a><\/noindex> na danych tylko jednego rekordu, u\u017cywaj\u0105c konsensusu zgodnie z ci\u0119\u017ckim protoko\u0142em Paxos. Dlatego pr\u0119dko\u015b\u0107 takich transakcji nie jest wysoka. <\/p>\n<h2>Czego nam zabrak\u0142o w Cassandrze<\/h2>\n<p>\nTak wi\u0119c, musieli\u015bmy zaimplementowa\u0107 w Cassandrze prawdziwe transakcje ACID. Dzi\u0119ki kt\u00f3rym mogliby\u015bmy \u0142atwo wdro\u017cy\u0107 dwie inne wygodne funkcje klasycznych DBMS: sp\u00f3jne szybkie indeksy, kt\u00f3re pozwoli\u0142yby nam na wykonywanie selekcji danych nie tylko wg klucza podstawowego oraz zwyk\u0142y generator monotonicznych autoinkrementacyjnych ID.<\/p>\n<h4>C*One<\/h4>\n<p>\nTak powsta\u0142a nowa baza danych <b>C*One<\/b>, sk\u0142adaj\u0105ca si\u0119 z trzech typ\u00f3w w\u0119z\u0142\u00f3w serwerowych:<\/p>\n<ul>\n<li>Magazyny \u2014 (prawie) standardowe serwery Cassandry, odpowiedzialne za przechowywanie danych na lokalnych dyskach. W miar\u0119 wzrostu obci\u0105\u017cenia i obj\u0119to\u015bci danych ich liczba mo\u017ce by\u0107 \u0142atwo skalowana do dziesi\u0105tek i setek.<\/li>\n<li>Koordynatory transakcji \u2014 zapewniaj\u0105 wykonanie transakcji. <\/li>\n<li>Klienci \u2014 serwery aplikacji realizuj\u0105ce operacje biznesowe i inicjuj\u0105ce transakcje. Takich klient\u00f3w mo\u017ce by\u0107 tysi\u0105ce.<\/li>\n<\/ul>\n<p>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/f3809d89404ae596c62b642dbdb9e431.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSerwery wszystkich typ\u00f3w s\u0105 cz\u0119\u015bci\u0105 wsp\u00f3lnego klastra, u\u017cywaj\u0105 wewn\u0119trznego protoko\u0142u wiadomo\u015bci Cassandra do komunikacji ze sob\u0105 oraz <noindex><a rel=\"nofollow\" href=\"https:\/\/en.wikipedia.org\/wiki\/Gossip_protocol\">protok\u00f3\u0142 gossip.<\/a><\/noindex> do wymiany informacji o klastrze. Dzi\u0119ki Heartbeat serwery dowiaduj\u0105 si\u0119 o wzajemnych awariach, utrzymuj\u0105 jednolit\u0105 struktur\u0119 danych \u2014 tabele, ich struktur\u0119 i replikacj\u0119; schemat partycjonowania, topologi\u0119 klastra itp.<\/p>\n<h4>Klienci<\/h4>\n<p>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/80f83358f215cf59db3b657ba1083420.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nZamiast standardowych sterownik\u00f3w u\u017cywa si\u0119 trybu Fat Client. Taka noda nie przechowuje danych, ale mo\u017ce pe\u0142ni\u0107 rol\u0119 koordynatora wykonania zapyta\u0144, czyli Klient sam pe\u0142ni funkcj\u0119 koordynatora swoich zapyta\u0144: pyta repliki magazynu i rozwi\u0105zuje konflikty. To nie tylko bardziej niezawodne i szybsze ni\u017c standardowy sterownik, wymagaj\u0105cy komunikacji zdalnym koordynatorem, ale tak\u017ce pozwala zarz\u0105dza\u0107 przekazywaniem zapyta\u0144. Poza otwart\u0105 na kliencie transakcj\u0105 zapytania s\u0105 skierowane do magazyn\u00f3w. Je\u015bli jednak klient otworzy\u0142 transakcj\u0119, to wszystkie zapytania w ramach transakcji s\u0105 kierowane do koordynatora transakcji.<br \/>\n<img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/709bf227eeb8f71abeff703a72780efd.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<h2>Koordynator transakcji C*One<\/h2>\n<p>\nKoordynator \u2014 to, co wdro\u017cyli\u015bmy dla C*One od podstaw. Odpowiada za zarz\u0105dzanie transakcjami, blokadami i kolejno\u015bci\u0105 zastosowania transakcji.<\/p>\n<p>Dla ka\u017cdej obs\u0142ugiwanej transakcji koordynator generuje znacznik czasu: ka\u017cdy kolejny jest wi\u0119kszy ni\u017c w przypadku poprzedniej transakcji. Poniewa\u017c w systemie Cassandra mechanizm rozwi\u0105zywania konflikt\u00f3w opiera si\u0119 na znacznikach czasu (spo\u015br\u00f3d dw\u00f3ch konfliktowych rekord\u00f3w aktualny uznawany jest za ten z p\u00f3\u017aniejszym znacznikiem czasu), konflikt zawsze zostanie rozwi\u0105zany na korzy\u015b\u0107 kolejnej transakcji. W ten spos\u00f3b zrealizowali\u015bmy <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%A7%D0%B0%D1%81%D1%8B_%D0%9B%D1%8D%D0%BC%D0%BF%D0%BE%D1%80%D1%82%D0%B0\">zegar Lamporta<\/a><\/noindex> \u2014 tani spos\u00f3b na rozwi\u0105zywanie konflikt\u00f3w w systemie rozproszonym.<\/p>\n<h2>Blokady<\/h2>\n<p>\nAby zapewni\u0107 izolacj\u0119, postanowili\u015bmy u\u017cy\u0107 najprostszej metody \u2014 pesymistycznych blokad na podstawie klucza g\u0142\u00f3wnego rekordu. Innymi s\u0142owy, w transakcji rekord nale\u017cy najpierw zablokowa\u0107, a dopiero potem przeczyta\u0107, zmodyfikowa\u0107 i zapisa\u0107. Tylko po udanym zatwierdzeniu zapis mo\u017ce zosta\u0107 odblokowany, aby konkurencyjne transakcje mog\u0142y z niego skorzysta\u0107.<\/p>\n<p>Realizacja takiej blokady jest prosta w \u015brodowisku nienarodowym. W systemie rozproszonym istniej\u0105 dwie g\u0142\u00f3wne drogi: mo\u017cna albo zrealizowa\u0107 rozproszon\u0105 blokad\u0119 w klastrze, albo rozdzieli\u0107 transakcje w taki spos\u00f3b, aby transakcje dotycz\u0105ce jednego zapisu zawsze by\u0142y obs\u0142ugiwane przez tego samego koordynatora.<\/p>\n<p>Poniewa\u017c w naszym przypadku dane s\u0105 ju\u017c rozproszone w grupach lokalnych transakcji w SQL, zdecydowano si\u0119 przypisa\u0107 koordynatorom grupy lokalnych transakcji: jeden koordynator wykonuje wszystkie transakcje z tokenem od 0 do 9, drugi \u2014 z tokenem od 10 do 19, i tak dalej. W rezultacie ka\u017cdy z instancji koordynatora staje si\u0119 mistrzem grupy transakcji. <\/p>\n<p>Wtedy blokady mog\u0105 by\u0107 zrealizowane w postaci prostej HashMap w pami\u0119ci koordynatora.<\/p>\n<h2>Awaria koordynator\u00f3w<\/h2>\n<p>\nPoniewa\u017c jeden koordynator wy\u0142\u0105cznie obs\u0142uguje grup\u0119 transakcji, bardzo wa\u017cne jest szybkie okre\u015blenie faktu jego awarii, aby ponowna pr\u00f3ba wykonania transakcji zmie\u015bci\u0142a si\u0119 w czasie oczekiwania. Aby by\u0142o to szybkie i niezawodne, zastosowali\u015bmy pe\u0142no\u0142\u0105czny kworumowy protok\u00f3\u0142 heartbeat:<\/p>\n<p>W ka\u017cdym centrum danych znajduje si\u0119 co najmniej dwie w\u0119z\u0142y koordynatora. Okresowo ka\u017cdy koordynator wysy\u0142a wiadomo\u015b\u0107 heartbeat do pozosta\u0142ych koordynator\u00f3w, informuj\u0105c ich o swoim dzia\u0142aniu, a tak\u017ce o tym, z jakich koordynator\u00f3w w klastrze otrzyma\u0142 ostatnio wiadomo\u015bci heartbeat. <\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/5c337e53815cad0a2071b160d98d42fa.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOtrzymuj\u0105c podobne informacje od innych w ramach ich wiadomo\u015bci heartbeat, ka\u017cdy koordynator decyduje dla siebie, kt\u00f3re w\u0119z\u0142y klastra dzia\u0142aj\u0105, a kt\u00f3re nie, kieruj\u0105c si\u0119 zasad\u0105 kworum: je\u015bli w\u0119ze\u0142 X otrzyma\u0142 od wi\u0119kszo\u015bci w\u0119z\u0142\u00f3w w klastrze informacj\u0119 o normalnym odbiorze wiadomo\u015bci z w\u0119z\u0142a Y, to znaczy, \u017ce Y dzia\u0142a. I odwrotnie, jak tylko wi\u0119kszo\u015b\u0107 poinformuje o utracie wiadomo\u015bci z w\u0119z\u0142a Y, to znaczy, \u017ce Y uleg\u0142 awarii. Ciekawe, \u017ce je\u015bli kworum poinformuje w\u0119ze\u0142 X, \u017ce nie otrzymuje od niego wi\u0119cej wiadomo\u015bci, to sama w\u0119ze\u0142 X uzna siebie za uszkodzon\u0105.<\/p>\n<p>Heartbeat-y s\u0105 wysy\u0142ane z du\u017c\u0105 cz\u0119stotliwo\u015bci\u0105, oko\u0142o 20 razy na sekund\u0119, z okresem 50 ms. W Javie trudno jest zagwarantowa\u0107 odpowied\u017a aplikacji w ci\u0105gu 50 ms z powodu por\u00f3wnywalnego czasu trwania pauz spowodowanych przez zbieracza \u015bmieci. Uda\u0142o nam si\u0119 osi\u0105gn\u0105\u0107 taki czas reakcji, u\u017cywaj\u0105c zbieracza \u015bmieci G1, kt\u00f3ry pozwala okre\u015bli\u0107 cel dotycz\u0105cy d\u0142ugo\u015bci pauz GC. Jednak czasami, do\u015b\u0107 rzadko, pauzy zbieracza przekraczaj\u0105 50 ms, co mo\u017ce prowadzi\u0107 do fa\u0142szywego wykrycia awarii. Aby temu zapobiec, koordynator nie zg\u0142asza awarii zdalnego w\u0119z\u0142a przy utracie pierwszego heartbeat-u od niego, tylko je\u015bli zgin\u0119\u0142o kilka pod rz\u0105d. W ten spos\u00f3b uda\u0142o nam si\u0119 osi\u0105gn\u0105\u0107 wykrywanie awarii w\u0119z\u0142a koordynatora w 200 ms. <\/p>\n<p>Jednak to za ma\u0142o, aby szybko zrozumie\u0107, kt\u00f3ry w\u0119ze\u0142 przesta\u0142 dzia\u0142a\u0107. Trzeba co\u015b z tym zrobi\u0107. <\/p>\n<h2>Rezerwa<\/h2>\n<p>\nKlasyczny schemat zak\u0142ada w przypadku awarii g\u0142\u00f3wnego w\u0119z\u0142a uruchomienie wybor\u00f3w nowego za pomoc\u0105 jednego z<noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_Raft\"> nowoczesnych<\/a><\/noindex> <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%9F%D0%B0%D0%BA%D1%81%D0%BE%D1%81\">uniwersalnych<\/a><\/noindex> algorytm\u00f3w. Jednak takie algorytmy maj\u0105 dobrze znane problemy ze zbie\u017cno\u015bci\u0105 w czasie i d\u0142ugo\u015bci\u0105 samego procesu wyboru. Uda\u0142o nam si\u0119 unikn\u0105\u0107 takich dodatkowych op\u00f3\u017anie\u0144 dzi\u0119ki schematowi zast\u0119powania koordynator\u00f3w w pe\u0142ni po\u0142\u0105czonej sieci:<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/5e2db258fa2200026390a73a414dfe81.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nZa\u0142\u00f3\u017cmy, \u017ce chcemy wykona\u0107 transakcj\u0119 w grupie 50. Z g\u00f3ry zdefiniujmy schemat zast\u0119powania, czyli kt\u00f3re w\u0119z\u0142y b\u0119d\u0105 realizowa\u0107 transakcje grupy 50 w przypadku awarii g\u0142\u00f3wnego koordynatora. Naszym celem jest zachowanie funkcjonalno\u015bci systemu w przypadku awarii datacentrum. Okre\u015blimy, \u017ce pierwszym rezerwowym b\u0119dzie w\u0119ze\u0142 z innego datacentrum, a drugim rezerwowym \u2014 w\u0119ze\u0142 z trzeciego. Ten schemat jest wybierany raz i nie zmienia si\u0119, dop\u00f3ki nie zmieni si\u0119 topologia klastra, co zdarza si\u0119 bardzo rzadko. Kolejno\u015b\u0107 wyboru nowego aktywnego g\u0142\u00f3wnego w\u0119z\u0142a w przypadku awarii starego b\u0119dzie zawsze taka: aktywnym g\u0142\u00f3wnym w\u0119z\u0142em zostanie pierwszy rezerwowy, a je\u015bli i on przestanie funkcjonowa\u0107 \u2014 drugi rezerwowy. <\/p>\n<p>Ten schemat jest niezawodniejszy ni\u017c uniwersalny algorytm, poniewa\u017c do aktywacji nowego g\u0142\u00f3wnego w\u0119z\u0142a wystarczy stwierdzenie faktu awarii starego.<\/p>\n<p>Ale jak klienci zrozumiej\u0105, kt\u00f3ry z mistrz\u00f3w aktualnie pracuje? W ci\u0105gu 50 ms niemo\u017cliwe jest rozes\u0142anie informacji do tysi\u0119cy klient\u00f3w. Mo\u017ce zdarzy\u0107 si\u0119 sytuacja, w kt\u00f3rej klient wysy\u0142a zapytanie o otwarcie transakcji, nie wiedz\u0105c jeszcze, \u017ce ten mistrz ju\u017c nie funkcjonuje, a zapytanie utknie na przekroczeniu limitu czasu. Aby temu zapobiec, klienci spekulatywnie wysy\u0142aj\u0105 zapytanie o otwarcie transakcji jednocze\u015bnie do mistrza grupy oraz obu jego rezerw, ale na to zapytanie odpowie tylko ten, kto jest aktywnym mistrzem w danym momencie. Ca\u0142\u0105 p\u00f3\u017aniejsz\u0105 komunikacj\u0119 w ramach transakcji klient b\u0119dzie prowadzi\u0142 tylko z aktywnym mistrzem.<\/p>\n<p>Rezerwowi mistrzowie umieszczaj\u0105 otrzymane zapytania dotycz\u0105ce nie swoich transakcji w kolejce nowo powsta\u0142ych transakcji, gdzie pozostaj\u0105 przez pewien czas. Je\u015bli aktywny mistrz umiera, nowy mistrz przetwarza zapytania o otwarcie transakcji ze swojej kolejki i odpowiada klientowi. Je\u015bli klient ju\u017c zd\u0105\u017cy\u0142 otworzy\u0107 transakcj\u0119 ze starym mistrzem, to druga odpowied\u017a jest ignorowana (i oczywi\u015bcie taka transakcja si\u0119 nie zako\u0144czy i zostanie powt\u00f3rzona przez klienta).<\/p>\n<h2>Jak dzia\u0142a transakcja<\/h2>\n<p>\nZa\u0142\u00f3\u017cmy, \u017ce klient wys\u0142a\u0142 koordynatorowi zapytanie o otwarcie transakcji dla jakiego\u015b bytu z danym kluczem g\u0142\u00f3wnym. Koordynator blokuje ten byt i umieszcza go w tabeli blokad w pami\u0119ci. W razie potrzeby koordynator odczytuje ten byt z magazynu i zapisuje uzyskane dane w stanie transakcji w pami\u0119ci koordynatora.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/246b4a5c68a4cc886b87af784c16b9dc.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nGdy klient chce zmieni\u0107 dane w transakcji, wysy\u0142a do koordynatora zapytanie o modyfikacj\u0119 bytu, a ten umieszcza nowe dane w tabeli stanu transakcji w pami\u0119ci. Na tym zapis jest zako\u0144czony \u2014 zapis w magazynie nie jest dokonywany.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/4400a91af30eb71134978ff27474f745.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nGdy klient \u017c\u0105da w ramach aktywnej transakcji swoich zmienionych danych, koordynator post\u0119puje nast\u0119puj\u0105co: <\/p>\n<ul>\n<li>je\u015bli ID jest ju\u017c w transakcji, to dane s\u0105 pobierane z pami\u0119ci; <\/li>\n<li>je\u015bli ID nie ma w pami\u0119ci, to brakuj\u0105ce dane s\u0105 odczytywane z w\u0119z\u0142\u00f3w magazynowych, \u0142\u0105czone z tymi, kt\u00f3re ju\u017c s\u0105 w pami\u0119ci, a wynik przekazywany klientowi. <\/li>\n<\/ul>\n<p>\nW ten spos\u00f3b klient mo\u017ce odczyta\u0107 w\u0142asne zmiany, a inni klienci tych zmian nie widz\u0105, poniewa\u017c s\u0105 one przechowywane tylko w pami\u0119ci koordynatora, w w\u0119z\u0142ach Cassandra jeszcze ich nie ma.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/c93fd7e8393f431a976f1f56b95e227c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKiedy klient przesy\u0142a commit, stan, kt\u00f3ry istnia\u0142 w pami\u0119ci serwisu, jest zapisywany przez koordynatora w logged batch, a nast\u0119pnie w tej formie logged batch wysy\u0142any jest do magazyn\u00f3w Cassandra. Magazyny podejmuj\u0105 wszystkie niezb\u0119dne dzia\u0142ania, aby ten pakiet zosta\u0142 atomowo (w ca\u0142o\u015bci) zastosowany, a nast\u0119pnie zwracaj\u0105 odpowied\u017a do koordynatora, kt\u00f3ry zwalnia blokady i potwierdza pomy\u015blno\u015b\u0107 transakcji klientowi.<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/ab20487ba53ed88e2f560a2249b5d6f9.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAby cofn\u0105\u0107 operacj\u0119, koordynatorowi wystarczy jedynie zwolni\u0107 pami\u0119\u0107 zaj\u0119t\u0105 przez stan transakcji.<\/p>\n<p>W wyniku powy\u017cej opisanych usprawnie\u0144 zrealizowali\u015bmy zasady ACID:<\/p>\n<ul>\n<li><b>Atomowo\u015b\u0107<\/b>. To gwarancja, \u017ce \u017cadna transakcja nie b\u0119dzie cz\u0119\u015bciowo zarejestrowana w systemie, zostan\u0105 wykonane albo wszystkie jej podoperacje, albo \u017cadna. U nas ten zasad przestrzegany jest dzi\u0119ki logged batch w Cassandra.<\/li>\n<li><b>Sp\u00f3jno\u015b\u0107<\/b>. Ka\u017cda udana transakcja z definicji rejestruje tylko dopuszczalne wyniki. Je\u015bli po otwarciu transakcji i wykonaniu cz\u0119\u015bci operacji oka\u017ce si\u0119, \u017ce wynik jest niedopuszczalny, nast\u0119puje cofn\u0105\u0107.<\/li>\n<li><b>Izolacja<\/b>. Podczas wykonywania transakcji r\u00f3wnoleg\u0142e transakcje nie powinny wp\u0142ywa\u0107 na jej wynik. Konkurencyjne transakcje s\u0105 izolowane przy u\u017cyciu pesymistycznych blokad na koordynatorze. Dla odczyt\u00f3w poza transakcj\u0105 przestrzegana jest zasada izolacji na poziomie Read Committed.<\/li>\n<li><b>Odporno\u015b\u0107<\/b>. Niezale\u017cnie od problem\u00f3w na dolnych poziomach \u2014 awaria zasilania, awaria sprz\u0119tu \u2014 zmiany wprowadzone w pomy\u015blnie zako\u0144czonej transakcji powinny pozosta\u0107 zachowane po wznowieniu funkcjonowania. <\/li>\n<\/ul>\n<p><\/p>\n<h2>Odczyt wed\u0142ug indeks\u00f3w<\/h2>\n<p>\nWe\u017amy prost\u0105 tabel\u0119: <\/p>\n<pre><code>CREATE TABLE photos (\nid bigint primary key,\nowner bigint,\nmodified timestamp,\n\u2026)<\/code><\/pre>\n<p>\nMa ona ID (klucz g\u0142\u00f3wny), w\u0142a\u015bciciela i dat\u0119 modyfikacji. Musimy wykona\u0107 bardzo proste zapytanie \u2014 wybra\u0107 dane wed\u0142ug w\u0142a\u015bciciela z dat\u0105 modyfikacji \u201ew ci\u0105gu ostatnich 24 godzin\u201d. <\/p>\n<pre><code>SELECT *\nWHERE owner=?\nAND modified&gt;?<\/code><\/pre>\n<p>\nAby takie zapytanie dzia\u0142a\u0142o szybko, w klasycznej bazie danych SQL nale\u017cy stworzy\u0107 indeks na kolumnach (owner, modified). Mo\u017cemy to zrobi\u0107 do\u015b\u0107 \u0142atwo, poniewa\u017c teraz mamy gwarancje ACID!<\/p>\n<h2>Indeksy w C*One<\/h2>\n<p>\nIstnieje tabela \u017ar\u00f3d\u0142owa ze zdj\u0119ciami, w kt\u00f3rej ID rekordu jest kluczem g\u0142\u00f3wnym. <\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/9f5d7c5f66882666a7ae8219f710beeb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDla indeksu C*One tworzy now\u0105 tabel\u0119, kt\u00f3ra jest kopi\u0105 tabeli \u017ar\u00f3d\u0142owej. Klucz zgadza si\u0119 z wyra\u017ceniem indeksowym, do kt\u00f3rego dodatkowo wchodzi klucz g\u0142\u00f3wny rekordu z tabeli \u017ar\u00f3d\u0142owej:<\/p>\n<p><img decoding=\"async\" alt=\"NewSQL = NoSQL+ACID\" src=\"\/wp-content\/uploads\/2019\/09\/ce6df1c8407bca7ddc61fd88141455e6.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTeraz zapytanie dotycz\u0105ce \u201ew\u0142a\u015bciciela za ostatnie 24 godziny\u201d mo\u017cna przepisa\u0107 jako wyb\u00f3r z innej tabeli:<\/p>\n<pre><code>SELECT * FROM i1_test\nWHERE owner=?\nAND modified&gt;?<\/code><\/pre>\n<p>\nSp\u00f3jno\u015b\u0107 danych w tabeli \u017ar\u00f3d\u0142owej photos oraz indeksowej i1 jest automatycznie utrzymywana przez koordynatora. Na podstawie samej struktury danych, przy otrzymaniu zmiany, koordynator generuje i zapami\u0119tuje zmian\u0119 nie tylko w tabeli g\u0142\u00f3wnej, ale tak\u017ce w kopiach. \u017badne dodatkowe dzia\u0142ania na tabeli indeksu nie s\u0105 wykonywane, logi nie s\u0105 odczytywane, blokady nie s\u0105 stosowane. Oznacza to, \u017ce dodawanie indeks\u00f3w prawie nie zu\u017cywa zasob\u00f3w i praktycznie nie wp\u0142ywa na szybko\u015b\u0107 stosowania modyfikacji.<\/p>\n<p>Dzi\u0119ki ACID uda\u0142o nam si\u0119 zaimplementowa\u0107 indeksy \u201ejak w SQL\u201d. Posiadaj\u0105 one sp\u00f3jno\u015b\u0107, mog\u0105 by\u0107 skalowane, dzia\u0142aj\u0105 szybko, mog\u0105 by\u0107 z\u0142o\u017cone i wbudowane w j\u0119zyk zapyta\u0144 CQL. W celu wsparcia indeks\u00f3w nie trzeba wprowadza\u0107 zmian w kodzie aplikacyjnym. Wszystko jest proste, jak w SQL. I co najwa\u017cniejsze, indeksy nie wp\u0142ywaj\u0105 na szybko\u015b\u0107 wykonywania modyfikacji w tabeli \u017ar\u00f3d\u0142owej transakcji.<\/p>\n<h2>Co uzyskali\u015bmy<\/h2>\n<p>\nOpracowali\u015bmy C*One trzy lata temu i uruchomili\u015bmy w eksploatacji przemys\u0142owej. <\/p>\n<p>Co wi\u0119c uzyskali\u015bmy w rezultacie? Sp\u00f3jrzmy na to na przyk\u0142adzie podsystemu przetwarzania i przechowywania zdj\u0119\u0107, kt\u00f3ry jest jednym z najwa\u017cniejszych typ\u00f3w danych w sieci spo\u0142eczno\u015bciowej. Chodzi nie o same zdj\u0119cia, lecz o wszelk\u0105 metainformacj\u0119. Obecnie w \u201eOdno\u015bnikach\u201d znajduje si\u0119 oko\u0142o 20 miliard\u00f3w takich zapis\u00f3w, system obs\u0142uguje 80 tysi\u0119cy zapyta\u0144 na sekund\u0119, do 8 tysi\u0119cy transakcji ACID na sekund\u0119 zwi\u0105zanych z modyfikacj\u0105 danych. <\/p>\n<p>Gdy u\u017cywali\u015bmy SQL z wsp\u00f3\u0142czynnikiem replikacji = 1 (ale w RAID 10), metainformacja zdj\u0119\u0107 by\u0142a przechowywana w wysoko dost\u0119pnej klastrze z 32 maszyn z Microsoft SQL Server (plus 11 rezerwowych). Dodatkowo wydzielono 10 serwer\u00f3w do przechowywania kopii zapasowych. \u0141\u0105cznie 50 kosztownych maszyn. System dzia\u0142a\u0142 przy nominalnym obci\u0105\u017ceniu, bez zapasu.<\/p>\n<p>Po migracji do nowego systemu uzyskali\u015bmy wsp\u00f3\u0142czynnik replikacji = 3 \u2014 po jednej kopii w ka\u017cdym centrun danych. System sk\u0142ada si\u0119 z 63 w\u0119z\u0142\u00f3w przechowuj\u0105cego Cassandra oraz 6 maszyn koordynacyjnych, co daje \u0142\u0105cznie 69 serwer\u00f3w. Te maszyny s\u0105 jednak znacznie ta\u0144sze, ich ca\u0142kowity koszt to oko\u0142o 30% kosztu systemu na SQL. Przy tym obci\u0105\u017cenie utrzymuje si\u0119 na poziomie 30%.<\/p>\n<p>Wprowadzenie C*One znacznie zredukowa\u0142o op\u00f3\u017anienia: operacja zapisu w SQL zajmowa\u0142a oko\u0142o 4,5 ms. W C*One \u2014 oko\u0142o 1,6 ms. Czas trwania transakcji wynosi \u015brednio mniej ni\u017c 40 ms, komit wykonywany jest w 2 ms, a czas odczytu i zapisu wynosi \u015brednio 2 ms. 99. percentyl \u2014 zaledwie 3-3,1 ms, liczba timeout\u00f3w zmniejszy\u0142a si\u0119 100 razy \u2014 wszystko to dzi\u0119ki szerokiemu zastosowaniu spekulacji. <\/p>\n<p>Na dzie\u0144 dzisiejszy z eksploatacji wycofano wi\u0119kszo\u015b\u0107 w\u0119z\u0142\u00f3w SQL Server, a nowe produkty s\u0105 opracowywane wy\u0142\u0105cznie z wykorzystaniem C*One. Dostosowali\u015bmy C*One do pracy w naszej chmurze. <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/company\/odnoklassniki\/blog\/346868\/\">one-cloud<\/a><\/noindex>, co pozwoli\u0142o przyspieszy\u0107 wdra\u017canie nowych klastr\u00f3w, upro\u015bci\u0107 konfiguracj\u0119 i zautomatyzowa\u0107 eksploatacj\u0119. Bez dost\u0119pu do kodu \u017ar\u00f3d\u0142owego by\u0142oby to znacznie trudniejsze i mniej stabilne. <\/p>\n<p>Obecnie pracujemy nad przeniesieniem innych naszych magazyn\u00f3w do chmury \u2014 ale to ju\u017c zupe\u0142nie inna historia.<br \/>\n<br \/>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/odnoklassniki\/blog\/417593\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445 \u0432 \u0440\u0435\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u0445\u0440\u0430\u043d\u0438\u043b\u043e\u0441\u044c \u0432 SQL Server. \u0414\u043b\u044f \u0442\u0430\u043a\u043e\u0433\u043e \u043e\u0431\u044a\u0435\u043c\u0430 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0431\u044b\u0441\u0442\u0440\u044b\u0439 \u0438 \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439, \u0434\u0430 \u0435\u0449\u0435 \u0438 \u0443\u0441\u0442\u043e\u0439\u0447\u0438\u0432\u044b\u0439 \u043a \u043e\u0442\u043a\u0430\u0437\u0443 \u0426\u041e\u0414 \u0434\u043e\u0441\u0442\u0443\u043f, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f SQL \u0421\u0423\u0411\u0414, \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e. \u041e\u0431\u044b\u0447\u043d\u043e \u0432 \u0442\u0430\u043a\u0438\u0445 \u0441\u043b\u0443\u0447\u0430\u044f\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u043e\u0434\u043d\u043e \u0438\u0437 NoSQL-\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449, \u043d\u043e \u043d\u0435 \u0432\u0441\u0451 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u0432 NoSQL: \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":28484,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-37956","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/newsql-nosql-acid\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47NewSQL = NoSQL+ACID | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/newsql-nosql-acid\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T19:20:47+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T19:20:47+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47NewSQL = NoSQL+ACID | ProHoster","description":"Do niedawna w Odnoklassnikach przetwarzano oko\u0142o 50 TB danych.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/newsql-nosql-acid","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47NewSQL = NoSQL+ACID | ProHoster","og:description":"\u0414\u043e \u043d\u0435\u0434\u0430\u0432\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0432 \u041e\u0434\u043d\u043e\u043a\u043b\u0430\u0441\u0441\u043d\u0438\u043a\u0430\u0445 \u043e\u043a\u043e\u043b\u043e 50 \u0422\u0411 \u0434\u0430\u043d\u043d\u044b\u0445, \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c\u044b\u0445.","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/newsql-nosql-acid","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T19:20:47+00:00","article:modified_time":"2019-10-31T19:20:47+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"37956","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-23 19:55:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 01:17:23","updated":"2026-01-23 19:55:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/37956","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=37956"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/37956\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/28484"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=37956"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=37956"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=37956"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}