Znudzone procesory albo krótka rozprawa o blokadach, kolejkach i oczekiwaniu. Część 1.

Bohdan Szymczak

Baza danych Oracle została zaprojektowana specjalnie do zastosowań w aplikacjach wymagających masowego dostępu i dużego wolumenu przetwarzanych danych. Te wymagania są spełnione po posadowieniu bazy na dużych wieloprocesorowych serwerach, lub co ostatnio staje się coraz częstsze, na instalacjach klastrowych. Dla aplikacji zorientowanych na transakcyjne przetwarzanie on-line, powszechnie stosowaną miarą wydajności jest liczba elementarnych transakcji biznesowych wykonywanych przez bazę w ciągu jednej minuty. Zgodnie z definicją używaną przez Transaction Processing Performance Council, jednostka ta jest wyrażana jako tpmC. Temat ten był szerzej omówiony w numerze 33 i 34 PLOUG’tek z roku 2005.

Na liście najszybszych maszyn sporządzonej przez wzmiankowaną powyżej organizację, a pracujących w oparciu o bazę Oracle znajduje się już całkiem pokaźna liczba tych, które bez użycia klastra są w stanie przetworzyć ponad 1 mln transakcji biznesowych na minutę. Zainteresowanych odsyłam do strony www.tpc.org, tu zacytuję tylko pozycje znajdujące się na samej górze w maju 2007 roku. Jak widać, w ciągu dwóch lat maksymalna zdolność przetwarzania wzrosła cztery razy. Jako dygresję można podać, że miejsce pierwsze w kategorii baz danych należy do bazy Oracle 10gR2, co wskazuje, że orientacja na wydajność ciągle stanowi cel jej projektantów.Baza danych Oracle została zaprojektowana specjalnie do zastosowań w aplikacjach wymagających masowego dostępu i dużego wolumenu przetwarzanych danych. Te wymagania są spełnione po posadowieniu bazy na dużych wieloprocesorowych serwerach, lub co ostatnio staje się coraz częstsze, na instalacjach klastrowych. Dla aplikacji zorientowanych na transakcyjne przetwarzanie on-line, powszechnie stosowaną miarą wydajności jest liczba elementarnych transakcji biznesowych wykonywanych przez bazę w ciągu jednej minuty. Zgodnie z definicją używaną przez Transaction Processing Performance Council, jednostka ta jest wyrażana jako tpmC. Temat ten był szerzej omówiony w numerze 33 i 34 PLOUG’tek z roku 2005.

Na liście najszybszych maszyn sporządzonej przez wzmiankowaną powyżej organizację, a pracujących w oparciu o bazę Oracle znajduje się już całkiem pokaźna liczba tych, które bez użycia klastra są w stanie przetworzyć ponad 1 mln transakcji biznesowych na minutę. Zainteresowanych odsyłam do strony www.tpc.org, tu zacytuję tylko pozycje znajdujące się na samej górze w maju 2007 roku. Jak widać, w ciągu dwóch lat maksymalna zdolność przetwarzania wzrosła cztery razy. Jako dygresję można podać, że miejsce pierwsze w kategorii baz danych należy do bazy Oracle 10gR2, co wskazuje, że orientacja na wydajność ciągle stanowi cel jej projektantów.

Parametr Wartość
Serwer HP Integrity Superdome
Typ procesora Itanium2 1.6GHz/24MB iL3
Liczba procesorów/
rdzeni/wątków
64/128/256
Pamięć RAM 2 TB
System operacyjny HP-UX 11i v3
Baza danych Oracle Database 10gR2 EE
Liczba klientów 95
Monitor transakcyjny BEA Tuxedo 8.0
Uzyskana wydajność 4 092 799 tpmC

Parametr

Wartość
Serwer IBM eServer p5 595
Typ procesora IBM POWER5 – 1.9 GHz
Liczba procesorów/
rdzeni/wątków
16/32/64
Pamięć RAM 1 TB
System operacyjny IBM AIX 5L V5.3
Baza danych Oracle Database 10g EE
Liczba klientów 56
Monitor transakcyjny Microsoft COM+
Uzyskana wydajność 1 601 785 tpmC

Parametr

Wartość
Serwer Fujitsu PRIMEQUEST 540 16p/32c
Typ procesora Intel Dual-Core Itanium2 1.6 GHz
Liczba procesorów/
rdzeni/wątków
16/32/64
Pamięć RAM 1 TB
System operacyjny Red Hat Enterprise Linux AS 4.0
Baza danych Oracle Database 10g EE
Liczba klientów 51
Monitor transakcyjny BEA Tuxedo 8.1
Uzyskana wydajność 1 238 579 tpmC


Osiągnięcie tak wysokich wskaźników wydajności nie jest wyłącznie zasługą zaawansowanych technologicznie maszyn, ale w ogromnym stopniu jest spowodowane właściwą konstrukcją użytych do testów aplikacji.

W produkcyjnie eksploatowanych aplikacjach takie wskaźniki są raczej niemożliwe do osiągnięcia. Nasuwa się tu naturalne porównanie do samochodów Formuły 1 konstruowanych w celu osiągania maksymalnych prędkości i seryjnych samochodów, powszechnie używanych do celów bardziej utylitarnych. Tak jednak, jak technologia Formuły 1 trafia do masowej produkcji, tak i zasady konstrukcji aplikacji o najwyższej wydajności powinny być stosowane w aplikacjach użytkowych.

Najczęściej stosowanym sposobem wizualizacji obciążenia serwera bazy danych jest wykres użycia procesorów w funkcji czasu. Taka aplikacja diagnostyczna jest obecnie częścią wszystkich systemów operacyjnych, jest również funkcjonalnością wielu narzędzi, nie wyłączając Oracle Enterprise Managera.

 


Rys. 1. Wizualizacja obciążenia procesorów w systemie Windows.



Rys. 2. Wizualizacja obciążenia procesorów w Enterprise
Managerze.

W równie prosty sposób można zobrazować obciążenie procesorów przy pomocy komendy sar w systemach Unix, przy czym dużą porcję danych należy oczywiście skierować do pliku.

 


Rys. 3. Użycie komendy sar do odczytu użycia
procesorów.

 

Z tak pozyskanych danych można łatwo sporządzić wykres w dowolnym arkuszu kalkulacyjnym.

 


Rys. 4. Wykres obciążenia procesorów sporządzony
w OpenOffice

 

Oracle10g udostępnia mechanizm zbierania statystyk systemu operacyjnego bezpośrednio za pomocą perspektywy systemowej V$OSSTAT. Interesujące nas wartości znajdują się w kolumnach IDLE_TIME, USER_TIME, SYS_TIME.

SQL> desc v$osstat
 
Nazwa       Wartość NULL?          Typ
--------------------------------- ---------
STAT_NAME                          VARCHAR2(64)
VALUE                              NUMBER
OSSTAT_ID                          NUMBER
 
SQL> select STAT_NAME,VALUE/100 "time [s]" from v$osstat
    2     where STAT_NAME='USER_TIME'or STAT_NAME='SYS_TIME' or STAT_NAME='IDLE_TIME';
 
STAT_NAME             time [s]
------------------- -----------
IDLE_TIME             5035.64
USER_TIME             185.56
SYS_TIME              1109.23

Bazując na tej perspektywie można łatwo zbudować w bazie danych mechanizm zapamiętujący stany z określonym interwałem, co później da się bez trudu przetworzyć na wykres obciążenia. Mechanizm jest szczególnie przydatny w sytuacjach, gdy bezpośredni dostęp do systemu operacyjnego serwera jest utrudniony lub istnieje potrzeba wbudowania śledzenia obciążenia w aplikację bazodanową. Należy jedynie pamiętać, iż wartości w perspektywie V$OSSTAT są inkrementowane od momentu startu instancji, więc dla wyliczenia obciążenia procesorów należy obliczać różnice w kolejnych przedziałach czasu.

Typowy wykres syntetyczny obejmuje okres 24 godzin pracy serwera, przy czym pomierzone wartości uśrednia się zwykle w interwałach nie większych niż 5 minut. Taki wykres daje możliwość oceny pracy maszyny w okresach dobowych, w których zwykle zamyka się cały typowy proces przetwarzania. Porównanie wykresów z kolejnych dni umożliwia z kolei ocenę powtarzalności charakterystyki obciążeniowej, na przykład w celu wczesnego wykrycia zapotrzebowania na większą maszynę na skutek istotnego wzrostu biznesu organizacji eksploatującej określoną aplikację bazodanową.

Ponieważ taki wykres jest prosty i bardzo czytelny, jest często używany jako element raportu działu IT przedstawiany zarządowi firmy. O ile jednak prezes firmy jest na ogół specjalistą w zakresie prowadzonego przez firmę biznesu, to trudno od niego oczekiwać znajomości zagadnień dotyczących systemów operacyjnych i baz danych. Typowym zjawiskiem jest rozumienie wykresu obciążenia maszyny w sposób intuicyjny, co często prowadzi do dużych nieporozumień.

Wyobraźmy sobie, że na wykresie przedstawiającym okres czasu istotny dla przetwarzania biznesowego, poziom obciążenia nie przekracza 50%. Spotyka się w tym przypadku dwie typowe reakcje kierownictwa. Pierwsza, którą można określić mianem „uspokojenia wewnętrznego”, sprowadza się do stwierdzenia – „istnieje duża rezerwa mocy maszyny i możliwa jest w związku tym bezpieczna eksploatacja systemu nawet przy dwukrotnym wzroście przetwarzania biznesowego”. Reakcja druga, którą można określić mianem „podejrzenia o nieuczciwość dostawcy”, sprowadza się do irytacji, iż zakupiono maszynę przewymiarowaną, która jest w połowie niewykorzystana, czyli w konsekwencji wydano w sposób nieuzasadniony pieniądze. Jakkolwiek obie interpretacje mogą być w określonych warunkach prawdziwe, to – jak pokazane zostanie w dalszej części artykułu, takie dane o obciążeniu maszyny są niewystarczające do jednoznacznego wnioskowania.

Do prześledzenia problemu posłużmy się prostą aplikacją symulującą przetwarzanie bazodanowe. Test jest uruchamiany na serwerze wyposażonym w dwa procesory, bazą danych jest Oracle10gR2. W trakcie testu zbierane będą statystyki sumarycznego obciążenia procesorów z interwałem 5 sekund.

Załóżmy, że naszym modelem jest aplikacja obsługująca system zamówień, w którym istotnym elementem przetwarzania jest modyfikacja stanów magazynowych, po realizacji zamówienia na określony towar. Algorytm przetwarzania jest następujący:

Stany magazynowe są przechowywane w tabeli STANY_MAG, moduły przetwarzające są symulowane przez procedurę workload wykonującą przez zadany w sekundach okres czasu operacje bazodanowe, nie będące w interakcji z innymi operacjami; czyli de facto procedura służy utylizacji mocy procesora.

SQL> desc STANY_MAG
Nazwa         Wartość NULL?        Typ
------------------------------- -------------------
INDEKS        NOT NULL             NUMBER
NAZWA                              VARCHAR2(25)
STAN                               NUMBER
 
SQL> desc workload
PROCEDURE workload
Nazwa Argumentu         Typ                 We-Wy Domyślne?
--------------------------------------------------------------------
TSEC                    NUMBER              IN

Przy takich założeniach program symulujący przetwarzanie ma postać:

declare
    StanAkt number;
begin
    workload(30); -- symulacja wyznaczanie indeksu magazynowego towaru przez 30 sek
    select STAN into StanAkt from STANY_MAG -- pobranie stanu magazynowego towaru Ind
    where Indeks=Ind
    for update;
    workload(30); -- symulacja przetwarzania zamówienia przez 30 sek
    update STANY_MAG set STAN=StanAkt-Ilosc -- aktualizacja stanu
    where Indeks=Ind;
    commit;
    workload(20); -- symulacja zakończenia realizacji zamówienia przez 20 sek
end;

Test pierwszy polega na kolejnym uruchomieniu w dwóch osobnych sesjach Oracle programu symulacyjnego, przy czym modyfikacji stanów magazynowych podlegają towary o różnych indeksach. Test drugi jest analogiczny do pierwszego; różnica polega na tym, iż modyfikacji podlega ten sam towar, o identycznym indeksie magazynowym. Dla lepszego zobrazowania przebiegu przetwarzania, obydwa procesy są uruchamiane z piętnastosekundowym przesunięciem w czasie.

 


Rys. 5. Wykres obciążenia procesorów w teście 1.

 

Rysunek nr 5 przedstawia wykres obciążenia procesorów w teście pierwszym. Można na nim wyodrębnić trzy fazy. Faza „A”, w czasie której pracował proces pierwszej sesji; faza „B”, w czasie której pracowały obydwie sesje oraz faza „C”, podczas której pierwsza sesja już zakończyła pracę, zaś sesja druga jeszcze wykonywała przetwarzanie. Zgodnie z przewidywaniem, sumaryczne obciążenie procesorów w fazie „A” i „C” wynosiło około 50%, co jest udziałem w pełni obciążonego jednego procesora przez jeden proces na maszynie dwuprocesorowej. W fazie „B” obydwa procesory były w pełni obciążone przez obydwie sesje Oracle, co zużyło pełną moc maszyny.

 


Rys. 6. Wykres obciążenia procesorów w teście 2.

 

Rysunek nr 6 przedstawia wykres obciążenia procesorów w teście drugim. Jak widać występuje na nim pięć faz. W fazie „A” i fazie „E” przetwarzanie wykonywała tylko jedna sesja Oracle, w fazach „B”, „C”, „D” pracowały obydwie. Charakterystyczną cechą, różniącą obydwa testy jest wystąpienie fazy „C”, w której obciążenie procesorów wynosi około 50%, co sugeruje, że przetwarzanie wykonywane jest tylko przez jedną sesję Oracle.

Odpowiedzialność za to zjawisko ponosi oczekiwanie na zwolnienie blokady na wierszu tabeli STANY_MAG.

W programie testowym występuje zapytanie:

select STAN into StanAkt from STANY_MAG
            where Indeks=Ind
            for update;

użyte do pobrania stanu magazynowego towaru. Ponieważ dalsza część przetwarzania wymaga, aby stan towaru nie został zmieniony przez równoległy proces przetwarzający inne zamówienie, konieczne jest założenie na wierszu blokady (fraza for update). Wykonane w tym czasie poniższe zapytanie pokazuje, że na tabeli zostaje założona blokada.

SQL> select l.OBJECT_ID, o.OBJECT_NAME, LOCKED_MODE,
    2 decode( LOCKED_MODE,
    3 0,'None', 1,'Null', 2,'SS Row-S', 3,'SX Row-X', 4,'S Share', 5,'SSX S/Row-X', 6,'X Exlusive' ) "LM",
    4 SESSION_ID,
    5 ORACLE_USERNAME
    6 from V$LOCKED_OBJECT l, DBA_OBJECTS o
    7 where l.OBJECT_ID=o.OBJECT_ID;
 
OBJECT_ID     OBJECT_NAME     LOCKED_MODE LM     SESSION_ID ORACLE_USERNAME
------------------------------------------------------------------------------
53346         STANY_MAG         3 SX Row-X       135         USER1

To samo zapytanie zostaje uruchomione z drugiej sesji zanim kończy się przetwarzanie sesji pierwszej w wyniku czego na tej samej tabeli zostaje podjęta próba założenia kolejnej blokady, co widać w perspektywie V$LOCKED_OBJECT.

SQL> select l.OBJECT_ID, o.OBJECT_NAME, LOCKED_MODE,
    2 decode( LOCKED_MODE,
    3 0,'None', 1,'Null', 2,'SS Row-S', 3,'SX Row-X', 4,'S Share', 5,'SSX S/Row-X', 6,'X Exlusive' ) "LM",
    4 SESSION_ID,
    5 ORACLE_USERNAME
    6 from V$LOCKED_OBJECT l, DBA_OBJECTS o
    7 where l.OBJECT_ID=o.OBJECT_ID;
 
OBJECT_ID     OBJECT_NAME     LOCKED_MODE LM     SESSION_ID ORACLE_USERNAME
------------------------------------------------------------------------------
53346         STANY_MAG         3 SX Row-X         137         USER2
53346         STANY_MAG         3 SX Row-X         135         USER1

Ponieważ w teście drugim obydwa procesy miały zmodyfikować stan tego samego towaru, czyli ten sam wiersz tabeli, zatem blokada została założona na tym samym wierszu, co spowodowało, że proces drugi wszedł w stan oczekiwania na zwolnienie blokady.

SQL> select username,lockwait, w.state from v$session s, v$session_wait w
    2 where lockwait is not null and
    3 w.sid = s.sid;
 
USERNAME     LOCKWAIT     STATE
-------------------------------------
USER2        338344C0     WAITING

Sprawdzając szczegółowo blokady zapytaniem:

SQL> select l.SID, s.USERNAME, l.TYPE, l.ID1, l.ID2, l.LMODE, l.REQUEST, l.BLOCK
    2 from V$LOCK l, V$SESSION s
    3 where l.sid=s.sid
    4 and l.type='TX';
 
SID USERNAME TYPE    ID1    ID2 LMODE REQUEST BLOCK
--------------------------------------------------------
137 USER2     TX     458777 791  0     6       0
135 USER1     TX     458777 791  6     0       1

otrzymujemy potwierdzenie, że user1 trzyma blokadę wyłączną (exclusive) LMODE=6 i BLOCK=1, zaś user2 oczekuje na pozyskanie blokady wyłącznej (exclusive) REQUEST=6 na tym samym wierszu. To samo zapytanie w formie podającej od razu zbiór sesji wzajemnie blokujących i oczekujących na ten sam wiersz może mieć postać:

SQL> select decode(request,0,'Holder: ","Waiter: ") || l.sid sesja, s.username, l.id1, l.id2, l.lmode, l.request, l.type
    2 from v$lock l, v$session s
    3 where (l.id1, l.id2, l.type) in ( select id1, id2, type from v$lock where request>0 ) and
    4 l.sid=s.sid
    5 order by l.id1, l.request;
 
SESJA           USERNAME  ID1        ID2   LMODE REQUEST TYPE
----------------------------------------------------------------
Holder: 135     USER1     458777     791     6     0     TX
Waiter: 137     USER2     458777     791     0     6     TX

Jak widać, sesja oczekująca nie powoduje widocznego obciążenia procesora, co jest uwidocznione na wykresie w postaci spadku ogólnej utylizacji CPU.

 


Rys. 7. Wykres obciążenia procesorów – zestawienie testu 1 i 2

 

Nakładając obydwa wykresy na siebie, co zostało przedstawione na Rysunku nr 7 można zauważyć, że test drugi zakończył się później od testu pierwszego. Ponieważ obie sesje wykonywały dokładnie ten sam program, czas przetwarzania po założeniu blokady na wiersz wynosił 30 s, zaś przesunięcie czasu uruchomienia przetwarzania wynosiło 15 s, zatem czas oczekiwania drugiej sesji wynosił 15 s i o tyle wzrósł ogólny czas przetwarzania.

Wartość tę można zweryfikować odczytując odpowiednie statystyki sesji z perspektywy V$SESSION_EVENT. Ponieważ perspektywa przechowuje dane przyrostowo, należy odczytać jej wartość przed i po wykonaniu testu:

------------------ przed testem ------------------
 
SQL> select s.username, s.sid, w.event, w.time_waited/100 "oczekiwanie [s]"
    2 from v$session_event w, v$session s
    3 where w.sid = s.sid
    4 and w.event like "%enq%"
    5 and username like "USER%";
 
USERNAME     SID EVENT                         oczekiwanie [s]
---------------------------------------------------------------
USER1        135 enq: TX - row lock contention    119
USER2        137 enq: TX - row lock contention    41169
 
------------------ po teście ------------------
 
SQL> select s.username, s.sid, w.event, w.time_waited/100 "oczekiwanie [s]"
    2 from v$session_event w, v$session s
    3 where w.sid = s.sid
    4 and w.event like "%enq%"
    5 and username like "USER%";
 
USERNAME     SID EVENT                         oczekiwanie [s]
---------------------------------------------------------------
USER1        135 enq: TX - row lock contention     119
USER2        137 enq: TX - row lock contention     41184

Jak widać wartość czasu oczekiwania na blokadę typu TX dla sesji użytkownika user2 wzrosła o wartość 41184-41169=15 s, czyli dokładnie o tyle ile wyliczono powyżej.

Przekładając to na rzeczywisty system informatyczny, można by spodziewać się, iż tak skonstruowana aplikacja, charakteryzująca się licznymi sporami o blokady na wierszach, dałaby obraz nieobciążonych procesorów oraz długie czasy przetwarzania. Byłoby to szczególnie widoczne w przypadku podłączenia do bazy wielu sesji Oracle, które w tym samym czasie próbowałyby modyfikować ten sam wiersz tabeli.

Wracając do tematu monitorowania obciążenia serwera bazodanowego poprzez wykres obciążenia CPU, pierwszym sygnałem niepokojącym zarząd byłyby sygnały od użytkowników, skarżących się na wolną pracę aplikacji. Kontrast z niską utylizacją procesorów byłby najlepszym dowodem na niemiarodajność przyjęcia tylko tego jednego parametru do monitorowania pracy systemu.

Jest oczywiste, że projektując aplikację pod kątem wydajności, tak jak jest to robione na potrzeby testów wyznaczania tpmC, dąży się do maksymalizacji obciążenia procesorów i minimalizacji oczekiwań na zwolnienie blokad transakcyjnych. W aplikacjach rzeczywistych nie da się uniknąć blokowania wierszy, ale należy się kierować zasadą, że blokada wiersza powinna być ustawiana na czas możliwie najkrótszy, a najlepiej należy dążyć do takiej konstrukcji, w której prawdopodobieństwo jednoczesnego dostępu do wiersza jest jak najmniejsze. Powyższy przykład pokazał, że pomimo nieprzetworzonego zadania biznesowego, procesory w serwerze były nieobciążone; można by rzec, że zamiast „ciężko pracować” wykazywały stan „znudzenia”. Ta skłonność do bezczynności procesorów będzie widoczna również w innych przypadkach, zaś działania wymuszające maksymalne obciążenie procesorów, okazują się jednym z ważniejszych zadań administratora bazy oraz twórców aplikacji i jednym z głównych celów strojenia systemu.

Podsumowanie

  • Wykres obciążenia procesorów jest przydatnym sposobem monitoringu pracy systemu, ale jego interpretacja wymaga rozważenia również innych wskaźników.
  • Niska wartość obciążenia procesorów, szczególnie w przypadku, gdy w tym czasie aplikacja wykonuje intensywne przetwarzanie uruchamiane w wielu sesjach Oracle, może wskazywać na występowanie w bazie blokad i oczekiwań jednych sesji na zasoby zajęte przez inne.
  • Analizę potencjalnych powodów braku obciążenia procesorów warto rozpocząć od sprawdzenia w perspektywach systemowych informacji o blokadach i oczekiwaniach.

Bibliografia:

  • www.tpc.org
  • TPC BENCHMARKTM C, Standard Specification, Revision 5.3
  • Bohdan Szymczak – Wydajność systemów bazodanowych Oracle. PLOUG’tki nr 33 i 34
  • Oracle® Database Performance Tuning Guide 10g Release 1 (10.1). Part No. B10752-01
  • Oracle® Database Reference10g Release 1 (10.1). Part No. B10755-01
  • Oracle® Database Concepts 10g Release 1 (10.1). Part No. B10743-01

TPC BenchmarkTM, TPC-C, tpmC są zastrzeżonymi znakami handlowymi Transaction Processing Performance Council.