Hierarchia pamięci
Typowy komputer posiada kilka komponentów mogących przechowywać dane. Różnią się one nie tylko pojemnością,
czasem dostępu, ale także kosztem. Najbardziej pojemne urządzenia są najwolniejsze i vice versa.
- Pamięć podręczna (cache)
- Pamięć wirtualna
- Pamięć drugorzędowa
- Pamięć trzeciorzędowa
Cache
Pojemność cache'u nie przekracza 1MB, jednak czas dostępu do danych jest na poziomie
szybkości procesora. Jak działa cache? Jeśli maszyna wykonuje rozkaz, szuka danych w cache'u i jeśli ich
tam nie znajduje – kopiuje je z pamięci głównej. Wszelkie zmiany dokonane w cache'u muszą znaleźć
odzwierciedlenie w pamięci głównej.
Pamięć główna
Każdy bajt może być uzyskany w tym samym okresie czasu. Typowy czas dostępy waha się od 10 – 100
nanosekund. Wszystko co dzieje się w komputerze – wykonywanie instrukcji, manipulacja danymi, etc. odbywa
się za pośrednictwem pamięci głównej.
Pamięć wirtualna
Większość maszyn używa 32-bitowej przestrzeni adresowej – czyli mamy 2^32 adresów – 4 bilony różnych
adresów. Ponieważ każdy bajt ma własny adres, to typowa pojemność pamięci wirtualnej wynosi 4 GB. Ze
względu na swoje duże rozmiary przechowywana jest na dyskach – podzielonych na bloki – pamięć ma strony –
tego samego rozmiaru. Jak to jest wykorzystywane w systemach baz danych? Zarządzają one swoimi danymi
przez pamięć wirtualną – system operacyjny dostarcza im potrzebnych danych za pomocą mechanizmu
stronicowania. „Pamięciowe systemy baz danych” – jak większość aplikacji – są w pełni użyteczne jeśli
dane są małe na tyle, że nie ma potrzeby wymiany danych. Przy 32-bitowej przestrzeni adresowej aplikacje
mogą trzymać na raz nie więcej niż 4 GB danych. Dlatego wielkie systemy zarządzania baz danych trzymają
dane bezpośrednio na dyskach – ograniczone są tylko pojemnością użytego dysku.
Pamięć drugorzędowa
Wolniejsza, ale bardziej pojemna. Najczęściej używane są dyski magnetyczne, jednak czasem zdarzają się również optyczne i magneto-optyczne.
Dyski są używane do przechowywania stron pamięci wirtualnej używanych przez aplikacje. Pliki są przenoszone pomiędzy pamięcią a dyskiem w blokach, pod kontrolą o.s. lub db s.
Czytanie pliku odbywa się za pośrednictwem buforowanie, czyli czytania pliku fragmentami, blokami. Bufor znajduje się w pamięci głównej. Po skonsumowaniu zawartości przez aplikację, do bufora kopiowany jest kolejny fragment pliku. DBMS zarządza blokami dysku samodzielnie, rzadziej za pomocą o.s.
Pamięć trzeciorzędowa
W przypadku dużych baz danych, przechowujących terabajty danych zwykła pamięć drugorzędowa nie wystarcza.
Z pomocą przychodzi pamięć trzeciorzędowa. Charakteryzuje się nieco wyższym czasem dostępu
(czytania / pisania) niż pamięć 2-go rzędowa. Ale o ile przy pamięciach 2-go rzędowych czas dostępu jest
mniej więcej taki sam dla wszystkich danych, czas dostępu pamięci 3-cio rzędowych rożni się, w zależności
jak blisko jest do punktu czytania / pisania. Wyróżniamy:
- Tape Storage – najstarszy i najprostszy rodzaj. Dane nagrywane są na taśmach przechowywanych na
półkach. Operatorem jest człowiek, który musi odnaleźć i zamontować właściwą taśmę. Szukanie konkretnych
danych dalej przebiega w następujący sposób: taśma jest przewijana do danej lokalizacji, znalezione dane
zostają skopiowane na nośnik pamięci drugorzędowej lub do pamięci głównej. Zapisywanie – w analogiczny
sposób.
- Optical Juke Boxes – “szafa grająca” złożona z półek, na których znajdują się dyski optyczne – CDROM.
Mechaniczne ramię odnajduje właściwą płytę z danymi i dostarcza głowicy czytającej, po czym dane są
kopiowane na dysk twardy.
- Tape Silos – ulepszona wersja Tape Storage – jednostka wielości pokoju, zbudowana z półek wypełnionych
taśmami. Posiada mechaniczne ramię, wyszukujące odpowiednią taśmę. O wiele szybsza niż Tape Storage.
Czas dostępu w pamięciach 3-cio rzędowych różni się od kilku sekund do kilku minut. Są one ok. 1000 razy pojemniejsze, co wpływa na zwiększenie czasu dostępu, również około 1000 razy.
Trwałe i ulotne przechowywanie (pamięć)
Definicje powyższych pojęć są intuicyjne: pamięci ulotne nie pamiętają co przechowują po wyłączeniu
zasilania, w przeciwieństwie do pamięci trwałych. Rozważania na ten temat są bardzo ważne, ponieważ jedną
z podstawowych własności DBMS jest możliwość zachowywania danych nawet w przypadku braków zasilania.
Urządzenia magnetyczne, taśmy są trwałe, podobnie jak optyczne (CDROM) z tą różnicą, że CDROMy nie można
powtórnie zapisywać. Generalnie pamięci 2-go i 3-cio rzędowe są trwałe. Pamięć główna jest przykładem
pamięci ulotnych. DBMS, który działa na maszynie z ulotną pamięcią musi robić kopię zapasową na dysku,
zanim zmiana dotrze do bazy – dlatego wszelkie zapytania, modyfikacje wymagają dużej liczby operacji I/O.
Alternatywą są pamięci FLASH.
Pamięć drugorzędowa
Dyski
Używanie pamięci drugorzędowej jest jedną z podstawowych charakterystyk DBMS. A pamięci drugorzędowe
prawie w całości bazują na dyskach magnetycznych.
Mechanizmy dysków
Jak jest zbudowany dysk? Składa się z talerzy złożonych z dwóch powierzchni, na których zapisane są bity.
Bity organizuje się w ścieżki w postaci koncentrycznych okręgów. Następnie ścieżki organizowane są w
sektory (kawałki okręgów oddzielone od siebie lukami [gaps]). Sektory są atomowymi jednostkami, jeśli
chodzi o czytanie i pisanie.
Kontroler dysku
Dyski sterowane są za pośrednictwem kontrolera. Jego zadaniem jest:
- kontrola mechanizmu przesuwającego głowice do właściwych pozycji. Przy ustalonej pozycji jedna ścieżka
z każdej powierzchni jest pod głowicą i może być odczytana lub nadpisana. Ścieżki, które są pod głowicami
w tym samym czasie tworzą cylinder.
- wybór powierzchni do czytania lub zapisu, wybór ścieżki i sektora
- odpowiedzialny za wiedzę, kiedy w czasie obracania talerzy, żądany sektor zacznie się pojawiać pod
głowicą,
- transfer czytanych/zapisywanych bitów z/do pamięci głównej.
Charakterystyka przechowywania dyskowego
- prędkość obrotowa,
- liczba talerzy,
- liczba ścieżek na powierzchnię,
- liczba bajtów na ścieżkę,
Charakterystyka dostępu do dysku
Analizując DBMS musimy dowiedzieć się nie tylko jak dane są przechowywane, ale również w jaki sposób
przebiega ich manipulacja. Ponieważ wszystkie wyliczenia mają miejsce w pamięci głównej, jedynym zadaniem
dysku jest dostarczenie danych do pamięci. Dane są czytane blokami gdy:
- głowica przesunie się nad cylinder zawierający ścieżkę, na której znajduje się blok,
- sektory zawierające blok przesuną się pod głowicą w czasie obracania talerzy.
Czas pomiędzy złożeniem zamówienia na dane, a momentem, gdy żądany blok znajdzie się w pamięci nazywa się
opóźnieniem, złożonym z:
- czasu potrzebnego procesorowi i kontrolerowi aby przetworzyć zamówienie,
- czasu przeszukiwania (umieszczenie głowicy nad cylindrem),
- opóźnienia obrotowego (obrót, aby 1-szy sektor znalazł się pod głowicą) – średnio, pół obrotu,
- czasu transmisji (obrót wszystkich żądanych sektorów i luk pomiędzy nimi wokół głowicy).
Zapisywanie bloków przebiega analogicznie do odczytu: głowice ustawiają się nad właściwym cylindrem,
czekamy aż odpowiedni sektor znajdzie się pod głowicą w czasie obrotu. Problem pojawia się, gdy chcemy
zweryfikować zapisane dane. Musimy wtedy czekać ma dodatkowy obrót i czytać każdy zapisany sektor.
Prostszym sposobem jest mechanizm sum kontrolnych, który omówię później.
Modyfikowanie bloków przebiega następująco:
- wczytać blok do pamięci głównej,
- przeprowadzić wszystkie pożądane zmiany,
- zapisać nową zawartość bloku na dysk,
- zweryfikować dane, w razie potrzeby.
Czas potrzebny do modyfikacji bloku jest sumą czasów: czytania, aktualizacji (który jest stosunkowo mały w porównaniu z czasem czytania/zapisu i można go pominąć), zapisywania i jednego obrotu dysku (jeśli weryfikujemy dane).
Efektywne wykorzystywanie pamięci 2-rzędowej.
Kiedy implementujemy jakieś algorytmy prawie zawsze zakładamy, że dane będą mieścić się w pamięci głównej.
Nie wolno popełnić tego błędu przy programowaniu DBMS. Musimy zakładać, że pamięć nie jest w stanie
pomieścić ogromu informacji, jakie będą przechowywane w bazie. Należy brać pod uwagę rozwiązania stosujące
pamięci 2-go, a czasami nawet 3-cio rzędowe. Efektywność ma silny związek ze współdziałaniem pamięci
głównej i 2-go rzędowej. W szczególności, lepszym algorytmem jest ten, który wymaga mniejszej ilości
dostępu do dysku, nawet jeśli jako algorytm nie jest za bardzo efektywny – gdyby działał w pamięci głównej.
Przy bazach danych mających wielu użytkowników i dużym ruchu na kontrolerze, operacje I/O zaczynają
dominować – koszt czytania / pisania przerasta koszt, związany z manipulowaniem danymi. Dlatego też
należy w używanych algorytmach minimalizować liczbę operacji I/O.
Two Phase, Multiway Merge Sort (Sortowanie zewnętrzne)
Jednym z algorytmów, które musiały się zmienić pod wpływem takiego modelu (I/O) jest klasyczny Merge Sort,
którego pochodna jest bardzo popularnym algorytmem sortującym w aplikacjach bazodanowych. Składa się z
dwóch faz:
- faza 1: posortuj kawałki danych (rozmiar = dostępna wielkość pamięci operacyjnej) – czyli każdy
rekord jest częścią posortowanej listy. Może wystąpić wiele posortowanych list.
- faza 2: połącz wszystkie posortowane listy w jedną.
Mechanizm postępowania: część rekordów, znajdującą się w pamięci sortujemy szybszym algorytmem, np.
QuickSort.Faza 1 wygląda tak:
- wypełniamy dostępną pamięć blokami z sortowanej relacji,
- sortujemy i zapisujemy w nowym miejscu na dysku.
Na koniec fazy 1 wszystkie rekordy relacji zostaną wczytane do pamięci i staną się elementami
posortowanych podlist, zapisanych na dysku.
Faza 2 polega na łączeniu wygenerowanych list. Można łączyć je parami, tak jak w klasycznym Merge Sort.
Wymaga to czytania wszystkich danych 2logn razy, przy n listach. Lepszym rozwiązaniem jest wczytanie po
jednym bloku, z każdej listy do bufora, znajdującego się w pamięci głównej. Dla małych relacji (liczba
podlist nie jest astronomiczna) jest to b.dobry sposób. Listę wynikową zapisujemy w buforze wyjściowym
według zasady:
- znajdź najmniejszy element z 1 –szych elementów,
- dołącz znaleziony na pierwsze wolne miejsce w buforze wynikowym,
- jeśli bufor się przepełnia – zapisz zawartość na dysku i ponownie zainicjuj bufor w pamięci,
- jeśli blok, z którego pochodził dołączony element wyczerpał się, wczytaj kolejny blok z tej samej
podlisty – jeśli nie ma już nowych bloków zostaw bufor pusty i nie rozważaj dalej tej listy.
Spostrzeżenie
W fazie 2 bloki są czytane w nieprzewidywalnej kolejności, nie wiemy kiedy lista się wyczerpie. Każdy
blok zawierający rekord jest czytany dokładnie raz. Dlatego liczba ‘czytań’ wynosi n – podobnie jak w
fazie 1.
Przyśpieszanie dostępu do pamięci 2-go rzędowej
We wcześniejszych rozważaniach nie uwzględnialiśmy położenia danych na dysku. W systemach, które wykonują
wiele małych zapytań fakt położenia nie ma dużego wpływu. Jednak jeśli wszystko co robi system, to
sortowanie ogromnych relacji, możemy zaoszczędzić nieco czasu. Aby przyśpieszyć wykonywanie zapytań i
zwiększyć przepustowość (przetwarzanie kilku zapytań w tym samym czasie) można użyć jednej z poniższych
strategii:
- układanie bloków, które są udostępniane razem na tym samym cylindrze, (zyskujemy czas szukania i ewentualne opóźnienie obrotowe)
- dzielenie danych na kilka mniejszych dysków,(w ten sposób mamy więcej głowic czytających i
zapisujących – zwiększamy w ten sposób liczbę czytanych/zapisywanych bloków w jednostce czasu)
- dublowanie dysku – umieszczanie na dysku kilku kopii jego zawartości,
- użycie algorytmu planowania, aby wybrać najwłaściwszą kolejność obsługi żądań,
- podwójne buforowanie – wcześniejsze czytanie bloków do pamięci i późniejsze ich wykorzystywanie.
Organizowanie danych w cylindrach
Ponieważ czas przeszukiwania to średnio połowa czasu dostępu, jest kilka aplikacji, w których całkiem
dobrym rozwiązaniem jest umieszczenie danych, do których odwołujemy się razem na jednym cylindrze. Jeśli
dane są większe, można użyć kilku sąsiednich cylindrów. Jeśli chcemy przeczytać wszystkie bloki kolejno
w ramach jednej ścieżki lub cylindra, możemy pominąć wszystko za wyjątkiem pierwszego czasu szukania
(przesunięcia nad cylinder) i pierwszego opóźnienia obrotowego (przesunięcie na początek pierwszego bloku). Zawartość cylindra możemy przeczytać w ramach czasu przeszukiwania. Takie zorganizowanie danych znacznie przyśpiesza faza 1 TPMMS, ale nie pomaga w fazie 2. Dlaczego? Ponieważ nie umiemy przewidzieć w jakiej kolejności dane będą czytane. Wiemy tylko tyle, że wybieramy najmniejszy element z pośród wszystkich pierwszych elementów list. Dlatego wynikowe bloki będą zapisywane po jednym na raz, czyli dokładnie tak samo jak wcześniej.
- Za: dobre dla aplikacji, w których dostęp może być przewidziany z wyprzedzeniem, tylko jeden proces
używa dysku,
- Przeciw: nie użyteczne, gdy nie wiadomo jak będą dostarczane dane.
Użycie wielu dysków
Często możemy zwiększyć prędkość naszego systemu zastępując jeden dysk wieloma. Co za tym idzie, mamy
więcej niezależnych głowic. I tak w TPMMS: załóżmy, że mamy 4 dyski. Wtedy w fazie 1 pamięć ładujemy z
każdego z nich, po 1. Jeśli tylko system jest w stanie obsłużyć czytane dane, możemy czytać dane w fazie
1 cztery razy szybciej. Podobnie jest z zapisywaniem – każdą posortowaną listę możemy dystrybuować na 4
dyskach, zapisując ją na kolejnych cylindrach. Skupmy się na fazie 2. Wciąż musimy czytać bloki z początków podlist w losowy, zależny od danych sposób. Jeśli wybieranie najmniejszego elementu wymaga wczytania do pamięci pierwszych bloków każdej listy – nie możemy użyć 4 dysków. Za każdym razem gdy wyczerpie się blok, musimy poczekać, aż nowy blok zostanie wczytany na miejsce starego. Dlatego używany jest tylko jeden dysk na raz. Ale jeśli będziemy uważniej pisać kod, możemy sprawić, żeby poszukiwanie elementu najmniejszego wznowiło się jak tylko przybędzie to pamięci pierwszy element nowego bloku. Wtedy kilka list będzie mogło mieć swoje bloki załadowane do pamięci w tym samym czasie. Tak długo jak są one na osobnych dyskach, możemy czytać na raz kilka bloków. Zyskamy w ten sposób 4-krotne zwiększenie prędkości czytania w fazie 2. Pisanie w fazie 2 jest łatwiejsze do przyśpieszenia. Możemy użyć 4 buforów wyjściowych i zapisywać każdy kolejno. Gdy bufor jest pełny, jest zapisywany na kolejny dysk, na kolejne cylindry. W ten sposób możemy zapisywać dane z jednego bufora, gdy pozostałe 3 są ładowane. Jeśli
jednak okazało się, że kolejne 2 bloki znajdują się na tym samym dysku, to pierwszy musi czekać ma
drugiego, wszystkie operacje w pamięci są wstrzymywane do czasu, dopóki początek drugiego bloku znajdzie
się w pamięci. Oczywiście nie możemy zapisywać wynikowej listy szybciej niż czytać danych z podlist. Jak
zobaczyliśmy nie jest możliwym równoczesne korzystanie z 4 dysków, dlatego faza 2 uzyskuje 2-3 krotne
przyśpieszenie.
- Za: zwiększa możliwości obsługi żądań pisania/czytania,
- Problem: żądania czytania lub pisania dla jednego dysku nie mogą być realizowane w jednym czasie.
- Przeciw: koszt kilku dysków.
Dublowanie (Mirroring)
Są sytuacje, w których dobrze jest mieć dwa lub więcej dysków zawierających te same dane. Dyski takie
nazywa się mirror’ami. Jeśli jeden z dysków ulegnie uszkodzeniu, pozostanie zawsze jego kopia na
innym. Dzięki mirroring’owi można też przyśpieszać dostęp do danych. Np. w fazie 2 TPMMS mogliśmy ładować
maksymalnie do 4 bloków z różnych list, których poprzednie bloki zostały wykorzystane. Jednak nie mogliśmy
wskazać, które 4 listy dostaną nowe bloki. W pesymistycznym przypadku mogło być tak, że pierwsze 2 listy
były na jednym dysku etc. Jeśli możemy kupić i użyć 4 kopii dużego dysku, mamy gwarancję, że zawsze
będziemy czytać 4 bloki za jednym razem. Jeśli mamy n kopii dysku, możemy czytać n bloków równolegle.
Używanie mirror’ow nie przyśpiesza ani nie spowalnia pisania. Po prostu, zapisujemy na wszystkich dyskach
posiadających kopię zapisywanego bloku. Zapisywanie przebiega równolegle, czyli czas = zapis na 1 dysk.
- Za: zwiększa możliwości obsługi żądań pisania/czytania, nie ma problemu obsługi żądań z tego samego dysku, działa nawet gdy jeden z dysków padnie – obraz jest na innym.
- Przeciw: płacimy za 2, 3 dyski a mamy pojemność jednego.
Planowanie algorytmem windowym (elevator alg.)
Inny sposób przyśpieszania polega na podpowiadaniu kontrolerowi kolejności obsługi żądań. Algorytm windowy
polega na specyficznym poruszaniu się głowicy. Jeździ one z dołu na górę i z powrotem. Znajduje żądanie,
obsługuje je i jeśli czekają jakieś inne żądania dalej od bieżącego – jedzie tam. W przeciwnym przypadku
zawraca. Lepszy niż naiwny FCFS. Skraca czas dostępu do bloku.
- Za: skraca średni czas pisania/czytania bloku, gdy dostęp do bloków jest losowy,
- Problem: jest efektywny, gdy jest dużo czekających zamówień, zatem średnie opóźnienie dla
zgłaszającego procesu jest duże.
Prefetching (podwójne buforowanie)
Ostatnim użytecznym sposobem przyśpieszania jest podwójne buforowanie. Czasem możemy przewidzieć kolejność,
w jakiej dane będą żądane. Dlatego ładujemy je do pamięci zanim będą potrzebne. Znowu skupmy się na fazie
2 TPMMS. Łączymy kilka posortowanych list, ładując po jednym bloku z każdej do pamięci. Jeśli po
wczytaniu elementów mamy miejsce w pamięci (liczba list nie jest wielka), moglibyśmy dołączyć do każdej
listy po 2 bufory-bloki. Jeśli jeden się wyczerpie, przełączamy się na kolejny. Jednak nadal musimy
czytać kolejno wszystkie elementy posortowanych list. Możemy ulepszy technikę i połączyć podwójne
buforowanie z „cylindrową strategią” jeśli będziemy:
- zapisywać posortowane podlisty na całych, sąsiednich cylindrach, gdzie kolejnymi blokami ścieżek są
kolejne bloki listy,
- czytać całe ścieżki lub cylindry za każdym razem, gdy chcemy kolejne rekordy z danej listy.
Wróćmy do fazy 2 TPMMS. To co robimy, to dla każdej listy tworzymy 2 bufory wielkości cylindra. Jeden
bufor wypełniamy, gdy drugi jest używany. Używając takich buforów ograniczamy się jedynie do szukania raz
na cylinder.
- Za: przyśpiesza dostęp, gdy potrzebne bloki są znane, ale czas żądań zależy od danych, jak w fazie 2
TPMMS.
- Przeciw: Wymaga dodatkowej pamięci, nie użyteczne, gdy dostęp jest losowy.
Disk Failures
Sytuacje, w których dyski mogą zawieść:
- najbardziej popularną formą błędu jest nieregularny błąd (intermittent failure) – próba
czytania lub pisanie sektora nie powiodła się, ale po kilku kolejnych próbach osiągamy sukces.
- Bardziej poważną formą błędu jest media decay –jeden lub więcej bitów ulega trwałemu
zniszczeniu, staje się niemożliwym do zapisania / odczytania bez względu na liczbę podjętych prób.
- Kolejna forma to błąd zapisu – nie możemy ani zapisać wartości, ani odczytać poprzednio zapisanej w
tym sektorze. Prawdopodobną przyczyną tego była awaria zasilania podczas zapisywania.
- Najbardziej poważnym błędem jest disk crash – cały dysk staje się niedostępny, nagle i trwale.
Intermittent failures
Zwykle sektory na dyskach mają pewne zbędne bity – sumy kontrolne. Dzięki nim wiemy, czy to co czytaliśmy
z sektora jest poprawne czy nie. Podobnie jest z pisaniem. Zwykłe funkcja czytająca dane zwraca parę
(w,s), gdzie w – dane, s – status. Przy błędach tej klasy, możemy trzymać status „bad”
kilka razy, ale w końcu dostaniemy „good”. Jednak czasami możemy zostać oszukani. Prawdopodobieństwo jest
jednak niewielkie. Przy zapisywaniu możemy próbować czytać sektor po zapisie i weryfikować, czy proces
przebiegł pomyślnie.
Sumy kontrolne
Każdy sektor ma kilka dodatkowych bitów, zwanych sumami kontrolnymi – zbiór bitów zależnych od
danych przechowywanych w tym sektorze. Jeśli podczas czytania odkrywamy, że suma kontrolna się nie zgadza,
zgłaszamy status „bad”. W p.p. zgłaszamy „good”. Mogą oczywiście wystąpić przekłamania,
ale używając dostatecznie długiej sumy (liczba bitów) redukujemy ten problem.
Prosta forma sum kontrolnych bazuje na parzystości. Jeśli jest nieparzysta liczba 1-nek, mówimy że bit
parzystości to 1, 0 w p.p. Gdy zapisujemy sektor można liczyć bit parzystości i dołączać na koniec
sekwencji bitów. Zatem, każdy sektor będzie miał bit parzystości 0.
Stable Storage
Sumy kontrolne pomagają nam wykryć błąd – ale nie umożliwiają korekty. Dlatego jest stabilne przechowywanie,
które polega na parowaniu sektorów. Każda para reprezentuje zawartość jednego sektora X. Para składa się
z lewej i prawej kopii: XL i XR. Zakładamy, że kopie są uzupełnione o bity parzystości,
uniemożliwiające przekłamania. Zakładamy, że jeśli funkcja czytająca zwraca (w,good) dla XL
i XR to w jest prawdziwą zawartością sektora.
Pisanie odbywa się w następujący sposób:
- zapisz wartość X w XL. Sprawdź status i jeśli jest „bad” powtórz zapisywanie.
Jeśli po kilku próbach ciągle mamy status „bad” stwierdzamy błąd nośnika (media failure) w
sektorze. Co zrobić? Podmienić uszkodzony sektor zapasowym.
- powtórz to samo dla XR.
Stabilne czytanie przebiega w następujący sposób:
- aby dostać wartość X, odczytaj XL. Jeśli zwrócony zostanie status „bad”, powtórz
operację kilka razy. Jeśli dostaniemy status „good” to ok.
- jeśli nie można czytać XL, powtórz dla XR.
Error Handling Capabilities of Stable Storage
- Media failures – jeśli po zapisaniu X w XL i XR jeden z nich staje się trwałe
niedostępny, możemy dostać wartość X od drugiego. Jeśli XR padł, a XL nie, to czytamy
XL, a na XR nie zwracamy uwagi. Jeśli tylko XL padło, to czytając dostaniemy status
„bad”. Idziemy do drugiego kroku, czyli czytamy z XR. Jeśli oba zawiodły – mamy problem.
- Write failure – przypuśćmy, że w czasie zapisywanie X wyłączyli zasilanie. Stracimy X w
pamięci oraz kopia X na dysku zostanie prawdopodobnie przekłamana (np. pół sektora nowe, pół stare).
Po włączeniu zasilania i sprawdzeniu XL i XR jesteśmy w stanie uzyskać starą lub nową
wartość X. Mamy 2 przypadki:
- błąd wystąpił w czasie zapisywania XL. Powinniśmy odkryć, że status XL jest „bad”.
Nie zajmowaliśmy się XR, więc jest tam poprawna stara wartość X. Kopiujemy do XL z
XR.
- bąd wystąpił po zapisie XL. Czytamy wartość XL i aktualizujemy XR.
Recovery from Disk Crashes
Trochę o najgroźniejszych wypadkach, czyli awariach głowic, kiedy to dane są trwale zniszczone. Jeśli dane
nie mają kopii zapasowej na innym nośniku nic nie da się zrobić. Jednak jest kilka schematów,
pokazujących jak bronić się przed tego typu sytuacjami. Mowa tu o RAID, Redundant Array of Independent
Disks. Każdy schemat składa się z dysków przechowujących dane (data disks) oraz przechowujących
informacje całkowicie zależne od zawartości tych pierwszych. Drugie nazywane są dyskami nadmiarowymi
(redundant disks). W przypadku awarii, bez względu na rodzaj dysku, dane mogą być w pełni odzyskane.
RAID level 1
Najprostszym sposobem jest stworzenie obrazu każdego dysku. Problem nastąpi tylko wtedy, gdy w czasie
awarii 1-go dysku, nastąpi awaria 2-go.
Bloki parzystości (Parity blocks) RAID level 4
Przy mirroringu musimy mieć tyle samo nadmiarowych dysków, co z danymi. RAID l4 używa tylko jednego
nadmiarowego, bez względu na liczbę dysków z danymi. Zakładamy, że dyski są identyczne i numerujemy bloki
od 1 do n. Nadmiarowy dysk w i-tym bloku ma sumy kontrolne dla i-tych bloków wszystkich dysków. Podobnie,
j-ty bit i-tego sektora.... Wykorzystuje się dodawanie modulo 2.
Przykład
disk1 11110000
disk2 10101010
disk3 00111000
disk4 01100010 - redundant disk
Czytanie może odbywać się zwyczajnie. Załóżmy, że mamy dyski 1,2,3 i 4 – nadmiarowy. Czytamy z 1 blok.
W tym samym czasie możemy przeczytać dane z tego samego dysku wykorzystując pozostałe, czyli 2 + 3 + 4
modulo 2 po każdym bicie.
Zapisywanie – na dysk z danymi + modyfikacja odpowiedniego bloku na dysku nadmiarowym. Można to zrobić za
pomocą 4 operacji I/O:
- czytamy starą wartość zmienianego bloku,
- czytamy odpowiadający blok z dysku nadmiarowego,
- zapisujemy nowy blok na dysku,
- obliczamy i zapisujemy blok na dysku nadmiarowym.
Przykład
Zmieniamy zawartość disk2 z 10101010 na 11001100. Bierzemy sumę modulo 2 starej i nowej wartości i
dostajemy: 01100110. Czyli na dysku nadmiarowym mamy zmienić w odpowiednim bloku 2,3,6,7 bit.
Zobaczmy co stanie się, gdy jeden z dysków ulegnie awarii. Jeśli będzie to dysk nadmiarowy, to zamieniamy
na nowy dysk i obliczamy sumy. Jeśli padnie dysk z danymi, zmieniamy na nowy i obliczamy jego dane na
podstawie pozostałych. Jak? Przyświeca zasada: Każdy bit jest sumą modulo 2 odpowiednich bitów ze
wszystkich dysków.
RAID level 5
RAID 4 działa dobrze, dopóki nie wystąpią dwie, jednoczesne awarie dysków. Ponieważ proces odzyskiwanie
danych, z dysków nadmiarowych i z danymi, jest identyczny czemu nie traktować wszystkich dysków na równi.
Każdy dysk może być nadmiarowe ze względu na pewne bloki. Jeśli mamy n+1 dysków, ponumerowanych od 0
do n, to możemy traktować i-ty cylinder dysku j-tego jako nadmiarowy, jeśli j
jest resztą z dzielenia i mod (n+1).
Przykład
Mamy 4 dyski, ponumerowane od 0 do 3,
- dla dysku 0 nadmiarowe cylindry to 4,8,12,...
- dla dysku 1: 1,5,9...
- dla dysku 2: 2,6,10...
- dla dysku 3: 3,7,11...
Jak poradzić sobie z wieloma, jednoczesnymi awariami dysków? Odp: RAID level 6
Jeśli będziemy mieli dostatecznie dużo nadmiarowych dysków, możemy poradzić sobie z każdą awarią, nieważne
jakiego rodzaju dysku – tak głosi RAID l6. Strategia to jest oparta na kodzie Humming’a –
najprostszym, korygującym błędu kodzie.
Załóżmy, że mamy system złożony z 7 dysków, ponumerowanych od 1..7, w tym 1..4 – data disks, 5..7 –
redundant disks. Budujemy macierz 3x7, w której:
- występuje każda kombinacja trzy elementowej kolumny z 0 i 1, z wyjątkiem 0 0 0.
- kolumny dysków nadmiarowych mają tylko jedną 1,
- kolumny dysków z danymi mają co najmniej dwie 1.
 
| Data disks
| Redundant
|
Disk nr | 1 | 2 | 3 | 4 | 5 | 6 | 7
|
| 1 | 1 | 1 | 0 | 1 | 0 | 0
|
| 1 | 1 | 0 | 1 | 0 | 1 | 0
|
| 1 | 0 | 1 | 1 | 0 | 0 | 1
|
Co oznaczają te 1 i 0?
Dyski z 1 w rzędzie traktowane są jako zestawy dysków dla RAID l4. Czyli
dysk nr 5 jest nadmiarowy dla dysków 1,2,3 itd. Czytanie przebiega w zwykły sposób. Nadmiarowe dyski są
ignorowane. Zapisywanie polega na policzeniu sumy modulo 2 starej i nowej wartości
zapisywanego bloku. Następnie wynik jest dodawany modulo 2 do tych nadmiarowych dysków, które mają 1 w
wierszu, w którym zapisywanym dysk ma także 1. Np. zapisując dysk 2, używamy dysków 5,6.
Odzyskiwanie
Załóżmy, że mamy awarię dwóch dysków: a i b. Ponieważ wszystkie kolumny macierzy są różne, można znaleźć
taki wiersz r w którym wartości dla a i b są różne. Załóżmy, że a w wierszu
r ma 0, a b 1. Możemy policzyć dane z b dodając do siebie modulo 2 wszystkie inne
dyski, które mają 1 w wierszu r. Teraz odtwarzamy zawartość dysku a. Szukamy wiersza, w
którym w kolumnie a jest 1. Zawartość dysku a to suma modulo 2 wszystkich innych dysków
mających 1 w tym wierszu.