sobota, 24 stycznia 2009

Scaling Hibernate: tips, recipes and new perspectives oraz Tune this!

Po godzinnej przerwie rozpoczęły się sesję BOF. Jak się później okazało wszystkie sesje BOF prowadzone były w 2 wąskich, dusznych salkach ze słabym sprzętem audio-video.



Scaling Hibernate: tips, recipes and new perspectives - zgodnie z zapowiedziami Emmanuel Bernard miał przedstawić sposoby optymalizacji aplikacji opartych o Hibernate. Po minutowym wstępie, w którym omówił "klasyczne" metody optymalizacji: batch size czy fetch size ciut więcej można się było dowiedzieć o 2nd level cache:

  • nie ma sensu używać 2nd level cache dla bardzo dużych woluminów danych: jak mamy milion userów w systemie to nie ma sensu cachowac takiej ilości danych: skończy się to Out of memory exception lub sytuacją w której tylko część danych tak naprawdę będzie w cache i to nie koniecznie te, które byśmy chcieli
  • ma być już możliwe używanie JBoss Cache jako transakcyjnego 2nd level cache - miało zostać to ostatnio naprawione

Pojawiła się wzmianka o użyciu Hibernate w systemach masowego przetwarzania/wsadowych i korzystaniu z takich dobrodziejstw jak: batch insert, batch update, ScrollableResults, StatelessSession (pobierane encje są automatycznie odłączane i mogą zostac usunięte przez GC). Ja osobiście dodałbym do tego bezpośrednią możliwość wykonania operacji update/delete z poziomu Hibernate za pomocą session.createQuery("...").executeUpdate();
Następnie przedstawiono różne możliwości radzenia sobie w bardziej specyficznych przypadkach, np. bardzo duża ilość danych, bardzo duże obciązenie baz danych, dane z różnych (politycznych) względów muszą być rozdzielone.
Jeśli chodzi o seperacji danych to wyróżniamy następujące opcje:

  • najprostszą możliwością (choć nie do końca można to nazwać separacją) jest separacja logiczna: wszystkie dane są przechowywanych w tych samych tabelach a separacja odbywa się na poziomie aplikacyjnym(logicznym) za pomocą Hibernate Filter lub jakiś innych tricków. Od razu przypomniał mi się mechanizm przedstawiony przez Alefa Arendsena podczas Spring ONE. Chodziło o to, aby za pomocą Spring AOP wrzucić do sztucznej tabeli identyfikator aktywnej transakcji oraz identyfikator klienta. Encje nie zostały "zbindowane" do tabel a do widoków, których zadaniem jest filtrowanie danych zawartych w tabeli po identyfikatorze klienta, którego aktualna wartość (wartość do "zmatchowania") znajduje się w sztucznej tabeli
  • można rozproszyć dane na wiele schematów i są tutaj 2 podejścia: 1 SessionFactory na 1 schemat (problem skalowalności) lub 1 SessionFactory dla wszystkich schematów (super login, wymaga przepisania zapytań - uwzględnienie nazwy schematów lub implementacji metody onPrepareStatement interfejsu org.hibernate.Interceptor - doklejenie do zapytania nazwy schematu). Ma tu się pojawiać dodatkowo problem z 2nd level cache, ale nie jestem pewien czy dobrze rozumiem dlaczego
  • wykorzystanie mechanizmów bazy danych, szczególnie kuszącym rozwiązaniem jest Oracle VPD (filtr na poziomie bazy danych)

Jeśli dodatkowo danych jest tak dużo i zdecydujemy się je rozpraszać pomiędzy wiele baz danych to Emmanuel Bernard zaprezentował 2 najpowszechniejsze rozwiązania tego problem, oba dotyczące warstwy apikacyjnej:

  • Homogenous nodes - każda instancja serwera aplikacyjnego ma pulę połączeń do każdej z bazy danych. Podejście takie ma mie wiele problemów natury wydajnościowej: duża ilość połączeń do bazy danych, zużycie pamięci, wolny start aplikacji (na każdej instancji startuje tyle SessionFactory ile mamy baz danych)
  • Specialized nodes - poszczególne instancje serwera aplikacyjnego mają dostęp jedynie do określonych baz danych. Ważny jest tutaj odpowiedni mechanizm load-balancing, który musi rozrzucać żądania użytkowników na podstawie żądanej funkcjonalności. Zaletą takiego podejścia jest skalowalność oraz wykorzystanie zasobów, ale mogą pojawić się problemy gdy nie istnieje żadna instancja serwera posiadająca dostęp do wszystkich danych, których będzie wymagać żądanie użytkownika

Rozwiązaniem ma być Hibernate Shards, który jest mechanizmem partycjonowanie poziomego (horizonal partitioning), czyli każda z baz danych posiada ten sam model, a jedynie różne dane. Dla porównania, partycjonowanie pionowe (vertical partitioning) polega na podziale danych ze względu na funkcjonalność i każda baza danych ma inny model : na 1 trzymamy dane użytkowników, na 2 tabele produktów itd. Na poziomie architektury Hibernate Shards rozszerza bibliotekę Hibernate i stanowi warstwę, która enkapsuluje logikę wynikającą z rozproszenia danych pomiędzy wiele baz danych, a z technicznego punktu widzenia opakowuje wiele instancji SessionFactory (podłączonych fizycznie do różnych baz o tym samym modelu) dostarczając użytkownik ujednolicony sposób dostępu do danych. Twórcy biblioteki dokonują wielkich starań i zabiegów, aby do jej obsługi móc korzystać ze znanego i sprawdzonego Hibernate API, istnieją odpowiednie implementacje takich interfejsów jak: SessionFactory, Session, Query, Criteria. Dodatkowo dochodzą do tego bardziej złożone sprawy wynikające ze specyfiki rozproszenia danych, które to zostały wyrażone za pomocą interfejsów:

  • ShardSelectionStrategy - decyduje o tym, na której partycji utworzyć obiekt
  • ShardResolutionStrategy - decyduje o tym na jakiej partycji szukać obiektu o zadanym kluczu głównym
  • ShardAccessStrategy - w jaki sposób wykonywać zapytania, które dotyczą więcej niz 1 partycji

Aktualnie Hibernate Shards udostępnia implementacje (lub kilka implementacji) każdego z tych interfejsów, ale oczywiście bardzo łatwo stworzyć własną.
Jeśli chodzi o zapytania to jak na razie Hibernate w ograniczonym zakresie wspiera Criteria Query (np. avg po poszczególnych partycjach) oraz nie wspiera HQL (problem z parserem). Dodatkowo dany graf obiektów nie może obejmować więcej niż pojedynczej partycji, co osobiście uważam za wielkie ograniczenie. Wyobraźmy sobie przypadek gdy tworzymy rich data model (nasze encje posiadają powiązania do innych encji a nie jedynie ich identyfikatory) i mamy powiązania do generycznych danych, np. kraje (których nie ma sensu rozpraszać). W takim przypadku jak na razie polecane jest mapowanie "nieobiektowe" takiej relacji, tabele krajów przechowywać na jednej z partycji a do szybszego dostępu do niej trzymać ją w cache w pamięci. W przyszłości planuje się zrobić pewnego rodzaju mechanizm replikacji, który to umożliwiałby spójne przechowywanie takiego typu danych w każdej z partycji a dzięki temu możliwe będzie skorzystanie z dobrodziejstw baz danych (ograniczenie foreign key) oraz Hibernate (rich data model).
Przewiduję się dodatkowo wiele problemów z organizacją/reorganizacja danych, aby przygotować je na rozproszenie. Dotyczy to nie tylko inicjalnego rozproszenia, ale co gorsza podziału istniejących juz partycji. Do tego celu stworzony został mechanizm virtual shards, który jest warstwą pośrednią pomiędzy Hibernate Shards a instancjami baz danych czyli partycjami fizycznymi. Podział na partycje wirtualne powinien być dokonany ze względu na wymagania biznesowe i brać pod uwagę "przyszłe potrzeby" systemu. Wiele partycji wirtualnych może początkowo zostać zbindowanych do pojedynczej partycji fizycznej. W razie potrzeby, w dalszej fazie istnienia systemu partycja fizyczna może zostać "przepięta" do dedykowanej dla siebie partycji fizycznej.
Wniosek z tej części prezentacji jest taki, aby próbować wszystkiego, by uniknąć rozpraszania swoich danych. Jednak gdy osiągniemy punkt, w którym nic więcej nie da się zrobić i pozostaje nam jedynie fizyczne rozproszenie danych, a dodatkowo używamy Hibernate to projekt Hibernate Shards na pewno jest w stanie nam pomóc. Następnie Emmanuel Bernard przeszedł do "swojego" tematu, czyli Hibernate Search i właśnie wydawanej ksiązki "Hibernate Search in Action", której jest współautorem.

Tune This! - to było już 2 spotkanie tego dnia z Kirkiem Pepperdine. Po minimalnym wstępie omawiającym miejsca, które mogą mieć negatywny wpływ na wydajność: aplikacja, JVM/OS, hardware oraz paru ogólnikowych wskazówkach związanych z wydajnością, przeszliśmy do konkretnych przykładów. Kirkiem Pepperdine przygotował aplikację, a widownia na podstawie różnych miar zużycia zasobów i wyników z profilera miała ocenić co jest przyczyną słabej wydajności aplikacji. W pierwszym przypadku mieliśmy do czynienia z bardzo dużym obciążeniem procesora. Proces javy nie był głównym konsumentem CPU, a najbardziej obciążał sam system operacyjny. Profiler wskazywał, ze większość wątków było w stanie "wait". Wszystko to oznaczało, że nasza aplikacja ma problemy z lockami. Dopiero po wysunięciu takiej propozycji Kirkiem Pepperdine pokazał nam kod źródłowy aplikacji i faktycznie, ze względu na niewłaściwe użycie synchronized aplikacja działała tak wolno. Po usunięciu problemu, czas działania aplikacji był juz dużo lepszy, jednak tym razem zużycie CPU przez proces java było bardzo wysokie. Powodem tego może być aplikacja sama w sobie lub też działanie GC. Z powodu braku czasu Kirk Pepperdine nie zaprezentował hpjmeter, które to uważa za wyśmienite narzędzie do analizy działania GC, a jedynie pokazał nam w jaki sposób za pomocą HPROF znaleźć, w którym miejscu kodu, aplikacja tworzy na stercie obiekty zajmujące największą ilość pamięci. Jego przykład był dość banalny i błąd polegał na bezsensownym tworzeniu obiektów typu String.

Brak komentarzy:

Prześlij komentarz