Batch Process Server FARMER

Jan Posiadała
jan.posiadala@tiger.com.pl

Procesy wsadowe

Pakiet dbms_job pojawił się po raz pierwszy w wersji 7.3.4 bazy Oracle, ok. 1997 roku naszej ery. Czy, w związku z tym, istnieje specjalista Oracle, który nie napisał, nie specyfikował, nie optymalizował lub nie uruchomił procesu wsadowego w bazie Oracle? Zapewne nie. Skoro praca nad procesami wsadowymi jest tak powszechna, spróbujmy krótko uściślić co – na potrzeby dalszych rozważań – będziemy rozumieli pod pojęciem procesu wsadowego w bazie Oracle, czy też szerzej, w środowisku bazodanowym.

Z biznesowego punktu widzenia proces wsadowy to zrobienie czegoś z elementami jakiegoś dobrze (trwale) zdefiniowanego zbioru obiektów reprezentowanych w bazie danych. W systemie bankowym może być to na przykład naliczenie odsetek od kapitału na rachunkach lokat bankowych. Realizacja programistyczna tak zdefiniowanych funkcjonalności jest oczywista: aplikujemy procedurę do kolejnych elementów zbioru reprezentowanych przez pewną wartość np. typu wierszowego. Zatem realizacja w środowisku bazodanowym Oracle powinna być równie oczywista: w pętli wywołujemy procedurę składowaną PL/SQL z parametrem typu wierszowego zwracanym przez kursor otwarty na zapytaniu SQL.

Dlaczego jednak nie jest? Lata doświadczeń w tworzeniu oprogramowania opartego na bazach danych pokazują, że na realizację tak pojmowanego procesu jest nałożone szereg zapotrzebowań i ograniczeń wynikających ze specyfiki środowiska bazodanowego jako środowiska wykonawczego, ale też wymagań użytkowników i administratorów.

Zatem realizacja procesu wsadowego w środowisku bazodanowym musi uwzględniać następujące wspólne dla rodziny procesów wsadowych aspekty:

  • politykę zarządzania transakcjami bazy danych,
  • mechanizm kontroli postępu procesu,
  • mechanizm zatrzymywania i wznawiania wykonywanego procesu,
  • logowanie komunikatów diagnostycznych,
  • optymalizację wydajności,
  • współbieżność wykonania procesu,
  • diagnostykę i naprawę błędów,
  • skalowalność i bezwzględną wydajność przetwarzania,
  • odporność na błędy i obsługę wyjątków.

Ponadto niektóre z wymienionych wcześniej wymagań spowodowały wypracowanie szeroko stosowanego praktycznego schematu implementacji procesów wsadowych polegającego na podziale przetwarzanego zbioru na podzbiory (najlepiej zbliżonego rozmiaru). Naturalnym sposobem implementacji takiego wzorca projektowego jest użycie dwóch zagnieżdżonych pętli:

  • zewnętrznej – iterującej podział na podzbiory,
  • wewnętrznej – iterującej elementy pojedynczego podzbioru.

Zatem do funkcjonalności wspólnych dla rodziny procesów wsadowych należy dołączyć jeszcze podział i iterację przetwarzanego zbioru.


Rys. 1. Aspekty funkcjonalne w BPS Farmer

Wszystkie procesy są prawie takie same

BPS Farmer jest wzorcowym rozwiązaniem serwerowym realizującym procesy wsadowe w środowisku Oracle. Farmer jest oparty na następującym – nieco przewrotnie sformułowanym – paradygmacie: wszystkie procesy wsadowe są „prawie” takie same. Wobec wcześniejszych rozważań łatwo już odpowiedzieć na pytanie, co oznacza kwantyfikator „prawie”: procesy wsadowe różnią się „tylko” funkcjonalnością biznesową, to znaczy definicją przetwarzanego zbioru oraz definicją przetwarzania wykonywanego na pojedynczym elemencie zbioru. Co z funkcjonalnością wspólną dla rodziny procesów wsadowych? Ta funkcjonalność jest realizowana przez BPS Farmer – serwer procesów wsadowych.

Wzorzec projektowy BPS Farmer

Implementacja biznesowa

Jak możemy się domyślać, do realizacji procesu wsadowego w Farmerze na bazie Oracle potrzeba – i zazwyczaj wystarcza – zdefiniowanie dwóch obiektów.

Po pierwsze, potrzebujemy widoku reprezentującego przetwarzany zbiór. Widok musi spełniać następujące warunki:

  • w widoku istnieje wyróżniona kolumna ID$ typu NUMBER lub INTEGER identyfikująca wiersze w zbiorze danych. Wartości w tej kolumnie muszą być unikalne oraz poindeksowane na potrzeby wyszukiwania zakresowego,
  • w widoku istnieje wyróżniona kolumna OBJECT_ID$ typu NUMBER lub INTEGER, identyfikująca logiczne obiekty w zbiorze danych. Wartości w tej kolumnie są poindeksowane na potrzeby wyszukiwania zakresowego,
  • wiersze w widoku są posortowane zgodnie z porządkiem: OBJECT_ID$ ASC, ID$ ASC.

Kolumny OBJECT_ID$, ID$ są nazywane kolumnami sterującymi.

Po drugie, potrzebujemy jednoargumentowej procedury PL/SQL reprezentującej przetwarzanie wykonywane na elemencie zbioru. Procedura jest umieszczana w pakiecie procesowym dedykowanym do implementacji procesu.


Rys.2. Schemat wzorca projektowego BPS Farmer

Przykładowa implementacja

Widok implementujący przetwarzany zbiór zawierający dane o pracownikach.

create or replace view v_proces_pracownicy

(id$, object_id$, imie, nazwisko)

as select id id$,

    id object_id$,

    imie imie,

    nazwisko nazwisko

from pracownicy

order by object_id$ asc, id$ asc

Pakiet implementujący proces zawierający procedurę przetwarzającą jeden element zbioru – wiersz z widoku.

create or replace package proces_pracownicy

as

    c_app_name varchar2 (30) := 'FRM';

    subtype t_rowtype is v_proces_pracownicy%rowtype;

    procedure one (r in t_rowtype);

end;

/

create or replace package body proces_pracownicy

as

    c_process_name constant varchar2(40) := 'PRACOWNICY';

    g_env logger.tt_env;

    procedure one (r in t_rowtype)

    is

    begin

        logger.info(g_env,'Pracownik'||r.nazwisko||'.');

        /* przetwarzanie pojedynczego wiersza */

    end;

begin

    g_env.module := c_app_name||'$'||c_process_name;

end;

Obiekty opcjonalne w implementacji procesu

Przedstawiona powyżej obowiązkowa specyfikacja obiektów dotyczy definicji zbioru oraz definicji przetwarzania wykonywanego na pojedynczym elemencie zbioru. Poza tą specyfikacją proces implementujący może udostępnić serwerowi Farmer sześć – w tym dwie na potrzeby współbieżności – innych procedur do wywołania w trakcie wykonywania procesu. Semantyka tych procedur jest ściśle powiązana z rdzeniem wzorca projektowego Farmera tj. podziałem przetwarzanego zbioru na podzbiory oraz użycia dwóch zagnieżdżonych pętli.

  • init_thread – wywołanie na początku wykonania procesu przed rozpoczęciem iteracji podziału na podzbiory przetwarzanego zbioru.
  • init_batch – wywołanie przed iteracją elementów każdego podzbioru.
  • finalize_batch – wywołanie po iteracji elementów każdego podzbioru.
  • finalize_thread – wywołanie na końcu wykonania procesu po zakończeniu iteracji podziału na podzbiory przetwarzanego zbioru.

Dwie pozostałe procedury zostaną opisane przy omówieniu współbieżności.

Aspekty funkcjonalne BPS Farmer

BPS zapewnia realizację znacznej części funkcjonalności procesów wsadowych.

1. Podział zbioru

Podział przetwarzanego zbioru na podzbiory jest faktycznym fundamentem realizacji przez Farmera kilku ważnych funkcjonalności wspólnych dla klasy procesów wsadowych. Podział zbioru może się odbywać w dwóch trybach:

  • dynamicznie podczas wykonania procesu,
  • statycznie przed wykonaniem procesu.

Statyczny podział zbioru przed wykonaniem procesu jest sugerowany przy przetwarzaniu jednowątkowym i bezwzględnie zalecany do wprowadzenia współbieżności. Przetwarzany zbiór jest dzielony względem wartości znajdujących się w kolumnie OBJECT_ID$. Wyliczony podział ma postać zbioru par wartości liczbowych, innymi słowy rozłącznych przedziałów o rosnących wartościach brzegowych. Typ tych wartości musi być zgodny z typem wartości w kolumnie OBJECT_ID$ widoku procesowego, co nie oznacza, że musi być wyliczony na podstawie tych wartości. Musi jedynie dążyć do równomiernego rozłożenia ilości elementów przetwarzanego zbioru w poszczególnych przedziałach.

2. Zarządzanie transakcjami i wyjątkami

Procesy w Farmerze mogą być wykonywane w 2 trybach.

Tryb pojedynczy oznacza, że każdy element ze zbioru danych jest przetwarzany w osobnej transakcji. Tryb pojedynczy jest zalecany w przypadku, gdy zbyt duże transakcje są niepożądane np. przeplot operacji transakcyjnych i nietransakcyjnych.

Tryb masowy oznacza, że transakcja bazodanowa będzie obejmowała operacje wykonane dla całego podzbioru przetwarzanych danych. W przypadku zgłoszenia wyjątku operacje wykonane dla całego podzbioru są wycofywane, a przetwarzanie jest wznawiane w trybie pojedynczym. Kolejne podzbiory są oczywiście przetwarzane w trybie masowym.

3. Odporność na błędy

Taki model zarządzania wyjątkami w prosty sposób zapewnia dużą odporność mechanizmu na błędy zgłoszone podczas przetwarzania pojedynczego elementu zbioru.

W praktyce mogą jednak wystąpić błędy, które powinny skutkować zatrzymaniem całego przetwarzania: np. brak miejsca na dysku lub nieprzebudowany indeks. Obsługę takiej sytuacji BPS Farmer wspiera na 2 sposoby. Po pierwsze pozwala zdefiniować listę błędów Oracle, których przechwycenie przy przetwarzaniu pojedynczego elementu zatrzymuje wykonanie procesu. Po drugie, Farmer udostępnia predefiniowany wyjątek fatal_one_exception, którego podniesienie w procedurze przetwarzającej element zbioru także powoduje zatrzymanie wykonania procesu.

Współbieżność

Realizacja współbieżności w serwerze FARMER jest przezroczysta, oparta jedynie na następującym naturalnym założeniu dotyczącym reprezentacji przetwarzanego zbioru: przetwarzanie elementów zbioru o różnych wartościach w polu OBJECT_ID$ może się odbywać współbieżnie.

Przy współbieżnym (wielowątkowym) trybie wykonania procesu, zestaw procedur opcjonalnych jest uzupełniony o dwie pozycje:

  • init_process – wywołanie na początku wykonania procesu (przed init_thread) w trybie wzajemnego wykluczania przez pierwszy wątek osiągający ten punkt przetwarzania,
  • finalize_process – wywołania na końcu wykonania procesu (po finalize_thread) w trybie wzajemnego wykluczania przez ostatni wątek osiągający ten punkt przetwarzania.

Scenariusze zastosowań BPS Farmer

W jakich okolicznościach korzyści z zastosowania serwera procesów wsadowych są największe? Ogólna odpowiedź jest oczywista: wtedy, gdy procesy wsadowe są znaczącą lub krytyczną częścią systemu. Poniżej zaprezentowane są 2 przykładowe scenariusze przypadków, w których użycie Farmera skutkuje znaczącymi korzyściami.

Nowy duży projekt

Opis

Duży operator telekomunikacyjny planuje szybko uruchomić projekt biznesowy wymagający aplikacji bazodanowej ze znaczącym udziałem procesów wsadowych.

Wyzwania

 

  • duża ilość procesów wsadowych,
  • krótki termin realizacji,
  • testy jednostkowe dla nowych funkcjonalności,
  • wiele błędów podczas testów uruchomieniowych,
  • potrzeba stosowania zaślepek w trakcie rozwoju systemu.

Rozwiązanie

Zaczynamy od identyfikacji klas obiektów, które będą przetwarzane przez procesy wsadowe, np.: klienci, umowy, urządzenia. Następnie tworzymy widoki reprezentujące te klasy, będące bazą dla widoków procesowych implementujących zbiory obiektów przetwarzanych przez poszczególne procesy wsadowe.

Teraz możemy przejść do analizy i projektowania procesów masowych. W przeważającym stopniu będzie to polegało na rozważeniu poszczególnych aspektów projektowanych procesów w ramach wzorca projektowego wspieranego przez BPS Farmer. Dysponując wynikami powyższej analizy możemy poprzez formatkę Farmer BPS stworzyć i wstępnie skonfigurować realizowane procesy. W tym momencie możemy już zdefiniować przetwarzane zbiory nadpisując automatycznie wygenerowane zaślepki widoków procesowych.

Przeprowadzamy testy funkcjonalne na ograniczonym, a testy wydajnościowe na rozszerzonym zestawie danych.

Etap Korzyści
Projektowanie
  • klarowne i ogólne wzorce projektowe
  • łatwe prototypowanie
  • predefiniowany zbiór aspektów procesów wsadowych
Kodowanie
  • znaczna redukcja ilości kodu źródłowego
  • izolowana implementacja logiki biznesowej
  • automatyczna generacja wydmuszek obiektów procesowych
Testy
  • wydzielenie funkcjonalności biznesowej na potrzeby testów jednostkowych
  • przezroczystość transakcyjna testów jednostkowych
  • łatwa rekonfiguracja zestawu danych testowych
Działani
  • zwiększona wydajność i skalowalność dzięki wprowadzeniu współbieżności
  • duża odporność na błędy
  • łatwa kontrola postępu, zatrzymywanie i wznawianie procesu
Utrzymanie
  • kolekcjonowanie predefiniowanych zapisów diagnostycznych ze strony serwera i implementacji biznesowej w jednym miejscu
  • kolekcje statystyk dotyczących wydajności procesu pochodzących z mechanizmów diagnostycznych: wait event, SQL trace, PL/SQL profiler, utylizacja zasobów sprzętowych
  • łatwe usuwanie błędów na podstawie zapisów stosu błędów

Krytyczny problem wydajnościowy

Opis

Bank detaliczny ma problem z wydajnością przetwarzania dnia końca okresu rozliczeniowego na kartach kredytowych w głównym systemie bankowym – wykonanie zestawu procesów stanowiących zamknięcie okresu rozliczeniowego trwa nieakceptowalnie długo.

Wyzwania

 

  • diagnostyka wydajności,
  • identyfikacja „wąskich gardeł”,
  • diagnoza wykorzystania zasobów sprzętowych,
  • rekonfiguracja użycia zasobów sprzętowych,
  • strojenie bazy danych,
  • rekonfiguracja fizycznego modelu danych,
  • płytka refaktoryzacja, wprowadzenie wzorców projektowych, wprowadzenie współbieżności,
  • głęboka refaktoryzacja, wprowadzenie trybu masowego dla instrukcji DML, krytyczne testy regresji.

Rozwiązanie

Z jakich cech BPS Farmer możemy skorzystać, aby rozwiązać ten problem? Po pierwsze możemy przenieść krytyczne procesy do środowiska wykonawczego Farmera, aby dokładnie przyjrzeć się wydajności procesów. Na tym etapie Farmer zbierze dla nas statystyki z mechanizmów: wait event, SQL Trace, PL/SQL profiler. Ta wiedza może umożliwić nam podstawową diagnozę wydajności np. usunięcie oczywistych wąskich gardeł czy też strojenie na poziomie zasobów systemu operacyjnego – pamięci operacyjnej, procesora, pamięci masowej. Ponadto na podstawie tych danych możemy dokonać podstawowej rekonfiguracji.

Płytka refaktoryzacja

Jeżeli te znane zabiegi administracyjne nie przyniosą oczekiwanych efektów, możemy przystąpić do zastosowania wzorca projektowego Farmera, a w konsekwencji wprowadzenia współbieżności.

Spróbujmy zweryfikować, które procesy będziemy w stanie zrealizować w ramach paradygmatu Farmera. W tym celu zidentyfikujmy klasy obiektów przetwarzanych w ramach zamknięcia okresu rozliczeniowego, np.:

  • rachunki kredytowe przypisane do aktywnych kart kredytowych,
  • aktywne karty kredytowe,
  • klienci posiadający kartę kredytową.

Stwórzmy odpowiednie widoki dla każdej z tych klas: V_KK_KARTY, V_KK_RACHUNKI, V_KK_KLIENCI. Następnie spróbujmy dla każdego procesu składającego się na zamknięcie okresu rozliczeniowego stworzyć widok procesowy, np. dla procesu naliczającego opłaty roczne za użytkowanie kart kredytowych.

create or replace view v_proces_kk_oplata_roczna

is

select karta_id id$,

rachunek_id object_id$,

karta_id karta_id,

rachunek_id rachunek_id

from v_kk_karty

where data_oplaty_rocznej = trunc(sysdate);

Należy upewnić się czy wszystkie warunki dla widoku procesowego – w tym warunek na potrzeby przetwarzania współbieżnego – są spełnione. W analizowanym przypadku logicznym wydaje się, że możemy księgować opłatę roczną równocześnie na dwóch różnych rachunkach, natomiast dla kart podpiętych pod ten sam rachunek chcemy księgować sekwencyjnie. Załóżmy, że to oraz pozostałe założenia są spełnione.

W tym momencie możemy zidentyfikować i zaimplementować drugi obiekt implementujący proces wsadowy – procedurę naliczającą opłatę dla jednej karty kredytowej. Mogłaby ona wyglądać w sposób następujący:

procedure one (r in t_rowtype)

is

v_oplata number(14,2);

begin

    logger.info(g_env,'Odsetki dla
karty'||r.karta_id);

    wylicz_opłate(r.karta_id,v_oplata);

    zaksieguj_oplate(r.rachunek_id,v_oplata);

end;

Doskonale, jeżeli udało się wyeliminować zarządzanie transakcjami z implementacji naliczania odsetek, czyli z procedury one. W przeciwnym przypadku procedura one musi być wykonywana w transakcji autonomicznej.

Następnie wykonujemy podobne czynności dla pozostałych procesów: np. dla klasy rachunków będzie to zamknięcie okresu rozliczeniowego, a dla klasy klientów wysłanie maila z powiadomieniem o zamknięciu okresu rozliczeniowego dla kart kredytowych.

Na etapie jednostkowych testów zrefaktoryzowanych procesów możemy ograniczyć się do wykonania bloku o treści podobnej do poniższego:

begin

    for r in (select *

        from v_proces_kk_oplata_roczna

        where karta_id in (1))

    loop

        proces_kk_oplata_roczna.one(r);

    end loop;

end;

Po testach jednostkowych i funkcjonalnych możemy przystąpić do testów wydajnościowych. W naszym przypadku głównym ich celem będzie zbadanie bezwzględnej wydajności procesów, czyli średniej ilości rekordów przetwarzanych w czasie jednej sekundy. Następnie możemy wprowadzić współbieżność. Z doświadczeń wynika, że optymalnym współczynnikiem współbieżności – czyli liczbą wątków – jest najczęściej podwojona ilość logicznych CPU. Jeżeli wzrost wydajności nie jest zadowalający, należy sprawdzić czy zastosowanie współbieżności nie wprowadziło zbyt dużej ilości wait event’ów z klasy Concurrency. W takiej sytuacji należy przeanalizować i usunąć ich przyczyny. Jeżeli osiągnięta wydajność jest dla nas satysfakcjonująca, przeprowadzamy zaawansowane testy regresji.

Głęboka refaktoryzacja

Jeżeli bezwzględna wydajność aplikacji osiągnięta w wyniku płytkiej refaktoryzacji jest nadal niewystarczająca, pozostaje nam głęboka refaktoryzacja systemu. Na tym etapie „wąskimi gardłami” w sensie wydajności najprawdopodobniej okażą się wielokrotnie wykonywane instrukcje SQL lub PL/SQL, a także instrukcje SQL odpytujące o te same dane. Na podstawie danych diagnostycznych zebranych przez Farmera poprawiamy wydajność kodu SQL i PL/SQL – np. poprzez wprowadzenie masowych (bulk) operacji SQL i DML. Wprowadzenie masowych operacji SQL i DML jest wspierane przez udostępnienie Farmerowi procedur z ich opcjonalnego zestawu. Operacje masowe umieszczamy w zaimplementowanej w pakiecie procesowym procedurze INIT_BATCH (operacje SQL) lub procedurze FINALIZE_BATCH (operacje DML).

Podsumowanie

Korzyści z użycia serwera BPS Farmer manifestują się redukcją kosztów na poszczególnych etapach realizacji funkcjonalności procesów wsadowych w aplikacjach opartych na bazie Oracle. Na wykresie widzimy oszacowanie zredukowanego kosztu realizacji procesu wsadowego w rozbiciu względem stopnia skomplikowania procesu.

Więcej informacji na stronie http://farmerbps.tiger.com.pl