Latches w OpenEdge
Współczesne systemy bazodanowe muszą obsługiwać setki, a nawet tysiące użytkowników wykonujących operacje w tym samym czasie. Wydajność bazy danych przy tak dużym obciążeniu zależy nie tylko od mocy procesora, ilości pamięci RAM czy szybkości dysków, ale przede wszystkim od wydajnego zarządzania współbieżnością (concurrency).
W silniku bazy OpenEdge RDBMS procesy stale dzielą między sobą dostęp do wewnętrznych struktur pamięci współdzielonej (Shared Memory) – takich jak bufory danych, tablice mieszające czy kolejki transakcji. Aby uniknąć uszkodzenia danych i zapewnić spójność pamięci, silnik musi kontrolować, który proces i w którym momencie może zmodyfikować dany zasób.
Do zabezpieczania tych wewnętrznych zasobów silnika OpenEdge RDBMS (będę pisał o licencji Enterprise, która jest przeznaczona do obsługi dużych systemów w porównaniu do wersji Workgroup) wykorzystuje mechanizm niskopoziomowych zapadek (latches). Aby oczekiwanie na zwolnienie zajętej zapadki nie powodowało przestojów, stosuje się mechanizm Spin Locks, który możemy stroić przez parametr startowy -spin.
Pisałem o tym rok temu, ale wciąż spotykam się z pytaniami dotyczącymi ich działania i strojenia.
Jak działają Spin Locks?
Zapadka (latch) to bardzo szybki mechanizm synchronizacji wewnątrz silnika bazy danych. Gdy jeden proces modyfikuje strukturę w pamięci współdzielonej, nakłada na nią zapadkę, aby inne procesy nie naruszyły spójności danych.
Dla zadań o bardzo krótkim czasie trwania stosuje się mechanizm Spin Locks. Zamiast natychmiastowo wstrzymywać proces i usypiać go przy zajętym zasobie, silnik sprawia, że proces “kręci się” w pętli na procesorze (tzw. spinning), wykonując serię szybkich, wielokrotnych prób przejęcia zapadki.
Ponieważ zapadki są zazwyczaj przytrzymywane przez ułamek milisekundy, ponawianie prób bezpośrednio na procesorze pozwala błyskawicznie zdobyć zasób zaraz po jego zwolnieniu.
Konfigurację tego mechanizmu określa parametr startowy -spin (Spin Lock Retries). Parametr ten definiuje, ile maksymalnie razy proces podejmie próbę zdobycia zapadki na zablokowanym zasobie pamięci współdzielonej, zanim przejdzie w stan drzemki.
Cykl obsługi zapadki przebiega następująco:
1. Proces próbuje uzyskać dostęp do zapadki.
2. Jeśli zapadka jest zajęta, proces ponawia próbę w pętli do “N” razy (gdzie N to wartość parametru -spin).
3. Jeśli po wykonaniu ustawionej liczby prób zapadka nadal nie jest wolna, dochodzi do tzw. Latch Timeout (przekroczenia czasu oczekiwania).
4. Proces wstrzymuje działanie (drzemka) na określoną liczbę milisekund.
5. Po wznowieniu pracy proces rozpoczyna cały cykl od nowa i ponawia próby zdobycia zapadki.
Wartość parametru -spin należy dostosować do liczby procesorów (rdzeni) dostępnych w serwerze oraz ich wydajności.
Domyślana wartość jest obliczana jako 6.000 x liczba procesorów zgłoszonych przez system operacyjny. Ponieważ obecna konfiguracja procesorów składa się z rdzeni i działających wątków, wartość ta może być obliczona zbyt wysoko. Jak już pisałem rok temu, w moim laptopie procesor ma 4 rdzenie, 8 wątków a parametr ten ma domyślną wartość 48.000.
Podgląd wartości -spin możemy uzyskać w promonie:
R&D -> 4. Administrative Functions 4. Adjust Latch Options

W tym samym miejscu możemy zmienić tę wartość online (podopcja 1).
Eksperci uważają, że w zdecydowanej większości systemów wartości te powinny się zawierać w przedziale 5.000 do 20.000.
Wskazówki diagnostyczne do strojenia:
– Uważajcie na wolne procesory: ustawienie zbyt wysokiej wartości -spin na powolnym sprzęcie może obniżyć ogólną wydajność bazy, ponieważ procesy będą niepotrzebnie marnować cykle CPU na ciągłe próby zdobycia zapadki.
– Monitorujcie Latch Timeouts (najlepiej w promonie):
Im więcej zdarzeń Latch Timeout (przekroczeń czasu oczekiwania) występuje w bazie, tym więcej jest konfliktów i tym niższa wydajność całej aplikacji.
R&D -> 3. Other Displays -> 1. Performance Indicators -> Latch timeouts

Drugim mechanizmem związanym z latches jest łańcuch LRU.
OpenEdge przechowuje w pamięci bufory zawierające bloki bazy danych (ich ilość określana przez parametr -B).
Ponieważ liczba dostępnych buforów jest ograniczona, silnik musi podejmować decyzję, które z nich pozostawić w pamięci, a które mogą zostać zastąpione innymi blokami, ewentualnym zapisaniem na dysk.
Do zarządzania wykorzystaniem buforów wykorzystywany jest między innymi mechanizm LRU (Least Recently Used).
W dużym uproszczeniu możemy wyobrazić sobie poniższy łańcuch:
MRU → [BUF] → [BUF] → [BUF] → [BUF] → [BUF] → [BUF] ← LRU
Bufory używane niedawno znajdują się po stronie MRU (Most Recently Used), natomiast bufory, które dawno nie były używane, przesuwają się w kierunku LRU.
Przy dużej liczbie użytkowników wiele procesów może jednocześnie korzystać z buffer pool. Jeżeli jednocześnie konieczne jest modyfikowanie struktur związanych z LRU, procesy mogą zacząć konkurować o LRU latch. W takiej sytuacji problemem może być nie sam dostęp do danych, ale oczekiwanie na możliwość wykonania operacji związanej z zarządzaniem buforami.
OpenEdge udostępnia mechanizmy pozwalające ograniczyć taką konkurencję.
Administrator ma tu do dyspozycji dwa parametry
-lruskips (do głównej puli buforów -B)
-lruskips2 (do wtórnej puli buforów -B2)
Ustawiając -lruskips = 100 to próba uzyskania latcha LRU będzie co 100-tny dostęp do bufora, co poprawia współbieżność i wydajność. Oczywiście każda taka zmiana jest kompromisem. Jeżeli zbyt mocno ograniczymy aktualizowanie informacji LRU, może to wpłynąć na skuteczność mechanizmu zarządzania buforami.
Obecnie wartości domyślne dla parametru -lruskips i -lruskips2 wynoszą 100 i w zdecydowanej liczbie systemów są one optymalne. Parametry powinny być zmieniane dopiero wtedy, gdy statystyki wskazują na rzeczywisty problem z konkurencją (najczęściej po rekomendacji zespołu wsparcia technicznego).
Wartości obu parametrów można zmienić online w tym samym miejscu co -spin (patrz pierwszy obrazek), opcja 8-9.
W ostatniej części artykułu przyjrzymy się jednemu z najważniejszych obszarów, w których zapadki decydują o wydajności bazy danych przy dużej liczbie użytkowników: strukturze Buffer Hash Table (BHT) oraz jej optymalizacji.
Po pierwsze, czym jest BHT i do czego służy?
Silnik bazy danych OpenEdge przechowuje najczęściej używane bloki danych w pamięci RAM, w tzw. puli buforów (Buffer Pool, parametr -B).
Gdy proces użytkownika chce odczytać lub zmodyfikować konkretny blok z bazy, musi najpierw sprawdzić, czy ten blok znajduje się już w pamięci RAM, czy też trzeba go dopiero pobrać z dysku. Szukanie bloku „po kolei” wśród tysięcy buforów w pamięci byłoby niezwykle niewydajne. Aby natychmiast zlokalizować właściwy bufor, silnik używa struktury BHT (Buffer Hash Table) – specjalnej tablicy mieszającej (hashującej). Proces oblicza wartość hash dla numeru bloku i od razu wie, w którym “kubełku” (bucket) tablicy znajduje się poszukiwany bufor.
Ponieważ setki procesów mogą w tym samym ułamku sekundy przeszukiwać i modyfikować tablicę BHT, dostęp do poszczególnych sekcji tej tablicy musi być chroniony przez zapadki – BHT Latches. Gdy baza danych obsługuje dużą liczbę współbieżnych operacji odczytu i zapisu, może pojawić się zjawisko BHT Latch Contention (rywalizacja o zapadki BHT), co prowadzi do konfliktów i spadku wydajności całej aplikacji.

W starszych wersjach systemu OpenEdge liczba zapadek chroniących tablicę BHT była sztywno ograniczona do maksymalnie 1024.
W dobie serwerów wieloprocesorowych i dużych wartości pamięci podręcznej (-B) ten sztywny limit stał się wąskim gardłem. Jedna zapadka musiała chronić zbyt duży fragment tablicy mieszającej, przez co dochodziło do częstych konfliktów. Weżmy np.dość dużą poniższą wartość puli buforów:
-B = 100.000 -> -hash = 25.000 -> ok. 24 elem. BHT/zatrzask
W nowszych wersjach OpenEdge (od 11.7.3+) oraz jako standard w architekturze OpenEdge 12, wprowadzono bardziej elastyczny mechanizm skalowania zapadek BHT za pomocą parametru:
-hashLatchFactor
Parametr ten określa procentowy stosunek liczby zapadek BHT do całkowitej liczby kubełków w tablicy hashującej (-hash). Domyślna wartość wynosi 10%.
Jak to działa w praktyce?
* Parametr -hash definiuje rozmiar tablicy hashującej (liczbe kubełków) – domyślnie jest to 25% wartości -B.
* Parametr -hashLatchFactor definiuje, ile zapadek chroni tę tablicę.
* Zwiększenie wartości -hashLatchFactor powoduje, że pojedyncza zapadka chroni mniejszy fragment struktury BHT.
Dla powyższego przykładu mamy teraz:
-hashLatchFactor domyślnie 10% rozmiaru BHT (-hash)
-B = 100.000 -> -hash = 25.000 -> ok. 10 elem. BHT/zatrzask
Parametr -hashLatchFactor nie może być strojony online. W promonie możemy jedynie podejrzeć jego aktualną wartość.
Na zakończenie warto przypomnieć, że wartości parametrów: -spin, -lruskips, lruskips2, -hashLatchFactor nie mogą być zmieniane na chybił-trafił. Ich strojenie zawsze powinno być poprzedzone analizą statystyk (np. w narzędziu PROMON lub tabelach VST), a często warto zasięgnąć porady w zespole wsparcia technicznego Progressa.
