Mamy taki dość typowy problem:
Jak zarządzać pamięcią w systemie bazy danych, aby:
- Wykonać jak najwięcej zapytań równocześnie, maksymalizując możliwości sprzętu (który wiemy, że obecnie idzie masowo w równoległość - wiele rdzeni, wiele dysków itp)
- Nie wyjechać poza dostępną pamięć w żadnym momencie (nie ubić serwera przez OOM).
Założenia:
- Zapytania mogą wyciągać lub zmieniać dowolną ilość danych.
- Klient może przysłać dowolną ilość zapytań w jednostce czasu i może zlecać je równolegle (mamy bardzo szybkiego klienta, a wąskim gardłem jest baza); próba wykonania wszystkich na raz jednak może skończyć się bardzo źle (OOM).
- Nie da się w niezawodny sposób zawczasu przewidzieć ile pamięci będzie niezbędne na realizację zapytania do końca. Zapytanie może się wydawać niewinne (np. SELECT... LIMIT 1), a tu nagle dostajemy z dysku pojedynczą komórkę, w którą ktoś wepchnął 100 MB XML :D
- Całkowita pamięć robocza jest oczywiście ograniczona i jej rozmiar jest znany. Wyczerpanie tej pamięci nie destabilizuje systemu (system ma rezerwę na inne rzeczy), ale nie można tego limitu przekroczyć.
Kilka pomysłów:
1a. Wykonywać wszystko jak leci jak najszybciej się da, bez limitów. Jak klient zlecił 10000 równoległych SELECT * FROM bez WHERE, to robimy, a co tam. Jak się skończy limit pamięci roboczej na zapytania, alokacja kończy się błędem i pechowe zapytanie zostaje zabite i zrolowane, a klient dostaje błąd "server overloaded". Klient (sterownik) wykrywa takie sytuacje i próbuje powtórnie, po jakimś czasie.
1b. (niepoprawne, ale może rokuje) Wykonywać wszystko jak leci, a jak się skończy pamięć to... wstrzymać wykonywanie zapytania, które zażądało za dużej ilości pamięci i kontynuować pozostałe (może zaraz coś się zwolni). Jednak tu się pojawia wcale niełatwy problem jak uniknąć permanentnego zakleszczenia systemu. Bo co jak będziemy mieć 100 zapytań "w trakcie", każde na 50% wykonania i skończy się pamięć, a każde będzie potrzebowało jeszcze trochę?
-
Podzielić dostępną pamięć M na N równych slotów (M i N konfigurowalne). Każde zapytanie dostaje swój slot. Jak się zmieści, to sukces. Jak się nie zmieści, to klient dostaje błędem "query working memory exceeded". Na wejściu do systemu kolejka o ograniczonym rozmiarze dla zapytań oczekujących na wolny slot. Długość kolejki raportowana do klienta na bieżąco, pozwala spowolnić strumień zapytań. Jeśli klient ignoruje grzeczne informacje o długość kolejki i wysyła za szybko dajemy mu w łeb wyjątkiem "server overloaded".
-
Jak w punkcie 2, ale dzielimy pamięć M na dwie pule M1 i M2 każda odpowiednio po N1 i N2 slotów. N1 >> N2. Jeśli zapytanie w trakcie wykonania okaże się za duże, to czeka na większy slot z drugiej puli. Tych dużych slotów jest odpowiednio mniej więc musi być przed nimi kolejna kolejka. Zaleta: jak klient wysyła małe zapytania, to ma niezłą równoległość, ale okazyjnie może wysłać większe zadanie i nie dostanie od razu "query working memory exceeded". Małe zapytania będą wykonywane szybciej niż duże. Można uogólnić do dowolnej liczby pul.
-
Zrobić twardy limit, że nie wrzucisz do jednej komórki 100 MB XMLa. Nie, bo nie, i już. Twarde limity na wszystko. Zaprojektować system tak, aby każde zapytanie wymagało nie więcej niż X bajtów pamięci roboczej, niezależnie od ilości wysyłanych / zwracanych wierszy. Może działać nieźle w połączeniu z pomysłem 2.
Różne systemy baz danych mają różne podejścia, ale jakbyście projektowali zupełnie nowy system, to jaką strategię byście wybrali?
A może jeszcze jakieś inne pomysły?
BTW: To nie musi być baza danych. Problem jest bardzo ogólny i pasuje do każdego równoległego systemu, który dostaje jakieś zadania na wejściu, ale nie wie dokładnie jakich są rozmiarów dopiero dopóki ich nie zrobi.