Ostatnio w pracy podczas testów jednego z systemów spotkaliśmy się z nastepującą charakterystyką obciążenia bazy:
Top 5 events
latch: cache buffers lru chain 14,664 2,489 170 47.9
enq: TX – row lock contention 5,582 1,203 216 23.1
log file sync 60,707 807 13 15.5
enq: US – contention 204 164 806 3.2
latch: cache buffers chains 1,367 153 112 2.9
Jak widać zdarzeniem które miało największy wpływ na obciążenie bazy było:
latch: cache buffers lru chain 48% czasu pracy bazy.
Najpierw trochę teorii. Co to są wogóle latche ?
Jest to mechanizm niskopoziomowy używany do chronienia obszarów współdzielonych przez więcej niz jeden proces, który wykonuje ten sam kod w tym samym czasie. Gdy na obszar nałożony jest latch , wtedy ten sam proces (jego druga instancja) nie może założyć latcha o tym samym lub niższym poziomie na ten sam obszar. Jedynym wyjątkiem jest nałożenie latcha w trybie nowait.
Jak jest zatem różnica pomiędzy latchami a Enqueue ? Enqueue jest o wiele bardziej złożnym mechanizmem , który pozwala kilku równoległym procesom używanie tych samych zasobów na różnym poziomie współdzielenia . Przykładem może tutaj służyć mechanizm obsługi tabel. Enqueue używa mechanizmów systemu operacyjnego, podczas gdy latche implementują mechanizmy niezależne od platformy. Enqueue używa kolejki FIFO dla procesów które czekają na założenie blokady w niekompatybilnym trybie. Latche z kolei nie mają zaimplementowanego mechanizmu kolejkowania. W dużym uproszczeniu proces gdy nie może założyć latcha, próbuje klikukrotnie po odczekaniu zadanego czasu (spin) . Nie ma gwarancji który z procesów założy latch.
Latche używane są do zarządzaniem przestrzenią SGA. Latch jest kasowany gdy proces kończy pracę na strukturze w pamięci. Jeśli taki proces zostanie zabity, zadaniem PMON jest “sprzątanie” takich osieroconych latchy.
Przejdźmy zatem do BUFFER CACHE LATCHES. Rozróżniamy dwa rodzaje latchy.
Cache buffer chains latch – zakładany , gdy blok w buffer cache jest “odwiedzany” przez proces (pinned). Wysoki poziom tego typu latchy może oznaczać istnienie gorących bloków HOT BLOCK.
Ogólnym sposobem rozwiązywania problemów z takimi latchami jest optymalizacja zapytań SQL, tak aby wymagały mniej logicznych I/O, np. używanie indeksów zamiast skanów tabeli. Według wsparcia Oracle, można próbować eliminować tego rodzaju latche poprzez ustawianie parameteru _DB_BLOCK_HASH_BUCKETS, który reprezentuje ilośc “łańcuchów” w buffer cache (chains in the buffer cache – i weź to przeŧłumacz na polski 🙂 )
Łańcuchy reprezentują ciągi bloków w buffer cache, które są skanowane w poszukiwaniu zadanego DBA
Cache buffer LRU chain latch – ten latch jest nabywany przez proces aby wprowadzić nowy blok do buffer cache, oraz przy zapisywaniu bloku na dysk, szczególnie podczas skanowania list LRU (least recently used). Jednym z oficjalnych sposobów zmniejszania konkurencji na tych latchach jest zwiększenie rozmiarów buffer cache, w ten sposób zmniejszając ruch na buffer cache który w przypadku zbyt małej wielkości “wymiata” najdawniej używane bloki aby pomieścić nowo wprowadzane do cache bloki
I tu dochodzimy do konsensusu. Pierwszym moim skojarzeniem był zbyt mały buffer cache skoro mamy taki duży ruch na liście LRU która zarządza używanymi blokami w buffer cache.
Jednak po bliższym przyjrzeniu się użycia buffer cache wyniki były dość zaskakujące. Cache wcale nie był wypełniony ! O co więc chodzi ??
Rozwiązanie jak zwykle tkwiło w szczegółach … ZBYT DUŻY BUFFER CACHE !!! Nie mieścił się w RAM i do jego składowania używany był obszar swap systemu operacyjnego !!!
Skanowanie listy która znajduje się na dysku, czy też wczytywanie do pamięci (nie cache bazy) (paging out i paging in) bloków które następnie mają być zapisane do plików danych, spowalniało cały system oraz powodowało takie oczekiwania !!!