Dlaczego używa się baz danych, a nie bardziej wydajnych rozwiązań?

0

Aplikacja wyborcza jako strona internetowa. Wszyscy użytkownicy mają móc czytać wyniki wyborów w kraju, województwie, okręgu, powiecie, gminie, obwodzie. Zalogowani mają móc je zmieniać. Zrobić w Django, dane trzymać w bazie danych.

Oczywiście zadanie akademickie. Ale ono sprawiło, że zacząłem się zastanawiać – po co w ogóle używać bazy danych, jeśli dane zmieszczą się w RAMie? (a tu się mieszczą bez problemu).

Podobno serwery WWW spędzają zazwyczaj ~90% czasu na czekaniu na IO. Czyli, m.in, na bazach danych. Zatem byłoby wskazane je optymizować. Tymczasem baza danych oferuje nam: • Czytanie z dysku pesymistycznie za każdym jednym odczytem; • pesymistycznie liniową złożoność zapytania o jeden wiersz dowolnej tabeli!

Załóżmy np, że mamy obwód i chcemy wiedzieć, w jakiej znajduje się gminie (czyli przechodzimy do klucza obcego). Złożoność: O(liczba gmin), bo trzeba przejść się po tabeli z gminami: SELECT * FROM gmina WHERE kod_gminy = obwód.kod_gminy A powinno być O(1). Załóżmy, że mamy gminę i chcemy wiedzieć, jakie znajdują się w niej obwody. Złożoność: O(liczba obwodów w kraju), bo trzeba przejść się po tabeli z obwodami: SELECT * FROM obwód WHERE kod_gminy = gmina.kod_gminy A powinno być: O(liczba obwodów w gminie).

Dwie kontrpropozycje do baz danych:

  1. Program w C lub C++ trzymający te dane w RAMie i odpowiadający na zapytania serwera WWW jakby był NoSQLowoą bazą danych. Np. mając obwód pytanie o gminę jest tutaj trywialne: miast SQLowego przeglądania tabeli z gminami przechodzimy po wskaźniku:
class Obwód {
  /* ... */
  Gmina *gmina;
  /* ... */
public:
  /* ... */
  Gmina &weź_gminę() {
    return *gmina;
  }
  /* ... */
};

Zapytanie o obwody w gminie:

class Gmina {
  /* ... */
  std::vector<Obwód*> obwody;
  /* ... */
public:
  /* ... */
  std::vector<Obwód*> const& weź_obwody() {
    return obwody;
  }
  /* ... */
};

Jeśli obwody (obiekty, nie wskaźniki) trzymane są w tablicy posortowanej gminami, to można nawet tak: std::vector<Obwód>::const_iterator obwody_początek, obwody_koniec

Oczywiście pojawia się problem że dane muszą przetrwać restart maszyny. Świetnie zatem: przy każdym zapisie serializujemy zmieniony obiekt do formatu binarnego i zapisujemy go na dysk. Albo: Co godzinę i na żądanie serializujemy i zapisujemy na dysk wszystko.

  1. Jeśli nie wystarczy RAMu: Trzeba wszystko trzymać na dysku i czytać z dysku. Ale tutaj chyba czysty system plików będzie wydajniejszy. Mamy np. folder Obwody i w nim jeden plik na obwód. W tym pliku ścieżka do pliku z gminą (a więc wzięcie gminy: znów O(1)). I w folderze Gminy każdy plik z gminą będzie zawierał ścieżki do plików z odnośnymi obwodami.

Przyznaję, benchmarków jeszcze nie robiłem, ale odnoszę być może błędne wrażenie, że każde z tych rozwiązań (a już w szczególności to pierwsze) będzie szybsze od bazy danych.

Czemu więc jednak używa się baz danych?

0

Jak zasilanie padnie to wszystkie dane szlag trafi :D
Nie mówiąc o transakcjach, a to tego jak będziesz się z takimś czymś łączył?

0
scibi92 napisał(a):

Jak zasilanie padnie to wszystkie dane szlag trafi :D

Pisałem przecież:

Oczywiście pojawia się problem że dane muszą przetrwać restart maszyny. Świetnie zatem: przy każdym zapisie serializujemy zmieniony obiekt do formatu binarnego i zapisujemy go na dysk. Albo: Co godzinę i na żądanie serializujemy i zapisujemy na dysk wszystko.

Następna sprawa:

scibi92 napisał(a):

Nie mówiąc o transakcjach, a to tego jak będziesz się z takimś czymś łączył?

No faktycznie, trzeba to wszystko dzielnie implementować. ALe jest to oczywiście do zrobienia.

3

O, to zrób tak:

  1. Wybierz gminę, w której średnie dochody mężczyzn netto w 2 kwartale 2016 roku, pomniejszone o składki zus nie przekroczyły 4000 zł.
  2. Zrób to samo z 30 różnych komputerów.
  3. Pokaż schemat encji i powiedz nowo zatrudnionemu programiście żeby zrobił punkt 1 w 10 minut.
  4. Dodaj mechanizm transakcji.

Generalnie bazy SQL są lubiane za transakcyjność, acid i standard (każdy to zna). Wiele narzędzi pozwala cacheować wyniki, więc to co się da, można łatwo przyspieszyć. Tego czego się nie da, i tak nie zrobisz w kodzie.
Jeżeli jednak chcesz super prędkości, a nie zależy Ci na spójności to wybierasz bazę noSQL. W małym rozwiązaniu, szybkie i przyjemne. W dużym, możliwość replikacji i klastrowania.

Z mojego doświadczenia wynajdywanie koła od nowa zawsze kończy się porażka. Wszelkiego rodzaju frameworki wewnętrzne firm czy nawet narzędzia po jakimś czasie stają się stare, nie ma kasy na ich rozwój i masz 100% pewności że nowy pracownik nie będzie go znał. Do tego nauka wcale nie musi być łatwa i przyjemna, bo czasem nie ma pełnej dokumentacji, małych zmian się nie opisuje i jest to rozwijane na 10 różnych koncepcji.

0

Wiesz, moze piszmy w asm bo bedzie dzialac szybciej?

To ze ktos nie potrafi zrobic czegos w bazie danych nie oznacza ze sa one wolne

Kazde narzedzie powstalo do jakiegos zadania.

A teraz z tym Twoim C++ rozwiazaniem, napisz do wszystkich chmur taki program (a na dobra sprawe baze danych...) ktory kazda chmura bedzie wspierac.

Temat powinien bardziej isc do perelek / flame / offtopic a nie do baz danych.

BTW jakbys nie wiedzial, bazy danych sa pisane w C++...

0

A tak btw. to słyszałeś o indeksach?

0

Dlaczego praca na kolekcjach nie wchodzi w grę? Mnie wydaje się to być najprostsze, jakie są powody by tego nie czynić?

Bo to są standardowe kolekcje, które chociażby nie obsłużą transakcji. Nie możesz włożyć czegoś do jednej kolekcji tak, żeby nie było widoczne w innym wątku. No i jak użyć takiej kolekcji na innym komputerze?

1

Bo baza oferuje ci persystencje, niezawodność, replikację, mirroring, klastrowanie, shardowanie, spójność danych, transakcje, backupy, jest konfigurowalne, etc. Do tego jest niezależna od logiki programu. Pozwala na szybsze tworzenie aplikacji.

0

Do czytania bez znaczenia chyba
Oczywiście że ma znaczenie. Poczytaj o I w ACID.
Wyobraź sobie że mam 10 zł na koncie.
t1 - dodaje mi 20 zł (razem 30), ale jeszcze tego nie potwierdza (commit).
t2 - zabiera mi 20 zł.
t1 - stwierdza że jednak nie, i cofa transakcję.

Przejdź ten scenariusz z widocznym odczytem niezakomitowanych transakcji i z niewidocznym.

Ale ogólnie do tematu powiem tak. Doświadczenie polega na tym, że widzi się rzeczy których mniej doświadczeni nie widzą. Ja widzę wiele problemów które pomijają juniorzy i są tacy co wiedzą o rzeczach których ja nie wiem. Myślę że nie przekonamy Cię, ale sam do tego kiedyś dojdziesz. Jeżeli chcesz, to napisz taką bazę sam, ale w taki sposób żeby można ją było wykorzystać w innych aplikacjach. W pewnym momencie zrozumiesz o co nam chodzi, ale wcale nie stracisz czasu, bo podciągniesz programowanie.

0

Odnoszę wrażenie, że Twoje pytanie właściwie brzmi "czemu używa się baz danych SQL?"
U mnie w firmie używamy bazy danych typu ISAM autorskiego rozwiązania, właśnie ze względu na wydajność. Wszystko jest ładowane do RAM-u podczas startu systemu i dostęp jest tak jak dostęp do tablicy w C++. Pobieranie powiązanych danych z kilku tabel i transakcyjność są jednak upierdliwe, dlatego do zadań typu generowanie statystyk, raportów używamy SQL. Czyli krótka odpowiedź brzmi wydajność to nie wszystko.

Czytanie z dysku pesymistycznie za każdym jednym odczytem;
• pesymistycznie liniową złożoność zapytania o jeden wiersz dowolnej tabeli!

Słabo się znam na bazach SQL, ale wydaje mi się, że te współczesne to jednak takie głupie nie są. Umieją cachować, mają swoje indeksy itd.

0
kmph napisał(a):

Oczywiście pojawia się problem że dane muszą przetrwać restart maszyny. Świetnie zatem: przy każdym zapisie serializujemy zmieniony obiekt do formatu binarnego i zapisujemy go na dysk. Albo: Co godzinę i na żądanie serializujemy i zapisujemy na dysk wszystko.

To droga do nikąd - jest do tego sprawdzony wzorzec - event sourcing.

Polecam wykład @jarekr000000 :
oraz wykłady Graga Young'a, np:

0

Ja dodam jeszcze od siebie, że:

  1. RAM jest kosztowny i mały w porównaniu do przestrzeni storage, dlatego często nie da się trzymać całości baz danych w RAM
  2. Po drugie: po to wymyślono cache. Serwer baz danych tak się konfiguruje, żeby optymalnie wykorzystywał dostępną pamięć i trzyma w pamięci to co powinien.
  3. Np. w MySQL istnieje typ MEMORY do trzymania całej tabeli w pamięci - więc nie trzeba wymyślać koła od nowa
  4. To, że ty nie potrafisz czegoś zrobić optymalnie w relacyjnej bazie danych, nie znaczy, że ktoś inny nie zrobi tego nawet lepiej niż Twój program w C++. Ogólnie technologie się dobiera do rozwiązania, a relacyjne bazy danych się spełniają w wielu i są bardzo uniwersalne.

Reszta powodów już została wymieniona: niezawodność, transakcje, replikacja, spójność danych itd. itp.

0

Wspomnę jeszcze że na dobrze poindeksowanej bazie danych kupa zapytań nie uderza do dysku (chyba że już przy samym odczycie danych) tylko operuje na samych danych dostępnych w strukturze indeksu. Tak więc takie naiwne zapytanie:

SELECT foo_id, MAX(whatever)
FROM bar
GROUP BY foo_id;

Które zwykle dostałoby od plannera plan zawierający sequential scan, przy użyciu takiego prostego indeksu:

CREATE INDEX bar_foo_max_id_by_whatever ON bar(foo_id, whatever);

Z automatu dostanie od plannera index only scan. To oznacza też że używając baz SQLowych zyskujesz deklaratywność, bo nie potrzebujesz przepisać całego twojego kawałka kodu żeby używał b-trees. Oczywiście samo optymalizowanie zapytań to też pełnoprawna zabawa sama w sobie, bo taki dajmy na to postgres w powyższym nie użyje tego indeksu (trzeba pisać rekurencyjne zapytanie żeby to obejść, po prostu nie zaimplementowali takiego trawersu indeksu).

1

Trzymanie bazy danych w ramie to nie taki głupi pomysł, głupim pomysłem jest próba implementacji tego samemu skoro istnieją już na rynku rozwiązania to oferujące.
Np SQL Server ma tabele które mogą być w całości trzymane w ramie z opcją na persystencje ich na dysku, wprawdzie ciągle są pewne ograniczenia w stosunku do zwykłych, ale podobno da się ich już używać komercyjnie od wersji 2016 (z wcześniejszymi wersjami podobno było sporo problemów). W internecie można nawet natrafić na artykuł gdzie pewna firma bukmacherska zastąpiła klaster serwerów nosqlowych obsługujący sesje ich użytkowników i cache, jedyną instancją sql server'a, właśnie z użyciem tabel w ramie.

Edytka:
wspomniany artykuł: https://blogs.msdn.microsoft.com/sqlcat/2016/10/26/how-bwin-is-using-sql-server-2016-in-memory-oltp-to-achieve-unprecedented-performance-and-scale/

i tabelka z artykułu obrazująca różnice w wydajności:

Version SessionState Performance Technology Bottleneck
SQL Server 2012 12 000 batch requests/sec SQL Server Interpreted T-SQL Latch contentions
SQL Server 2014 300 000 batch requests/sec Memory-Optimized Table, Interpreted T-SQL, Handling of Split LOBs CPU
SQL Server 2016 1 200 000 batch requests/sec Memory-Optimized Table with LOB support, Natively Compiled stored procedures CPU
0

poza tym @kmph istnieją tez cache różnego rodzaju w aplikacjach

1 użytkowników online, w tym zalogowanych: 0, gości: 1