Author Archive

Doctrine cache

Author: Paweł Sołtysek (pawel.soltysek) | styczeń 17th, 2011
avatar

Użycie mechanizmu keszującego (ang. cache) jest powszechnie uznawane za jedną z najefektywniejszych metod optymalizacji. Niestety mimo licznych zastosowań często pomija się wykorzystanie tego mechanizmu w celu przechowywania wyników zapytań do baz danych. Tymczasem dzięki wbudowanemu w Doctrine wsparciu dla keszowania możemy łatwo przyspieszyć obsługę zapytań SQL w naszych aplikacjach.

Konfiguracja

Podstawowa konfiguracja polega na ustawieniu odpowiedniej klasy sterownika oraz strategii keszowania. Doctrine dostarcza klasy sterowników dla większości powszechnie używanych mechanizmów keszujących (APC, XCache, memcache) oraz dwie strategie keszowania (keszowanie wyników procesu parsowania zapytań DQL oraz keszowanie wyników zapytań SQL). W zależności od potrzeb konfigurację możemy przeprowadzić globalnie (do wyboru mamy konfigurację managera lub połączeń) lub lokalnie z poziomu konkretnego zapytania.

Pracując z symfony ustawienia globalne najlepiej zdefiniować poprzez dodanie metody configureDoctrine() klasy ProjectConfiguration. W zależności od wybranej strategii rejestrujemy sterownik poprzez ustawienie odpowiedniego atrybutu (Doctrine_Core::ATTR_QUERY_CACHE lub Doctrine_Core::ATTR_RESULT_CACHE). Dodatkowo poprzez ustawienie atrybutu Doctrine_Core::ATTR_*_CACHE_LIFESPAN możemy ustawić czas ważności rekordów.

Powyższa konfiguracja obejmuje wszystkie połączenia nawiązywane przez Doctrine. Czasem może zaistnieć potrzeba ograniczenia konfiguracji do konkretnego połączenia (na przykład jedno z dwóch połączeń jest nawiązywane do bazy danych modyfikowanej przez inną aplikacje). W takiej sytuacji możemy użyć metody configureDoctrineConnectionName() zastępując człon Name nazwą naszego połączenia.

W razie potrzeby konfigurację możemy przeprowadzić z poziomu konkretnego zapytania. Służą do tego metody useQueryCache() oraz useResultCache() (w dalszej części artykułu poznamy szersze zastosowanie tych metod). Konfiguracja lokalna nadpisuje konfigurację globalną.

Keszowanie w praktyce

Mając poprawnie skonfigurowany sterownik jesteśmy gotowi, aby zastosować mechanizm keszujący do naszych zapytań. Kolejne kroki zależą od poziomu konfiguracji oraz wybranej strategii keszownia. Dla przykładu, jeśli wybraliśmy konfigurację globalną oraz keszowanie wyników zapytań, należy powiadomić Doctrine, które zapytania mają być keszowane (keszowanie wyników procesu parsowania DQL jest automatycznie zastosowane do wszystkich zapytań). Służy do tego wczesniej poznana metoda useResultCache(). Metoda ta jako pierwszy parametr przyjmuje instancję sterownika lub wartość logiczną, który informuje czy dane zapytanie ma być objęte mechanizmem keszowania. Drugi parametr określa czas ważności rekordu dla naszego zapytania, natomiast trzeci jest identyfikatorem tego rekordu.

W ten sposób wynik zapytania po pierwszym wykonaniu zostanie umieszczony w mechanizmie keszującym pod nazwą sample_query na okres jednej godziny. W ciągu tej godziny każde kolejne wykonanie zapytania będzie oddelegowane do mechanizmu keszującego.

Przekazując do zapytania dodatkowe parametry, musimy zadbać, aby każdemu ich zestawowi odpowiadał inny ciąg identyfikujacy wynik zapytania. Doctrine automatycznie wygeneruje odpowiedni identyfikator jeśli nie podamy go jawnie. Takie rozwiązanie jest niewygodne jeśli mamy w perspektywie zarządzanie zawartością mechanizmu keszującego (musimy znać idnentyfikator). Więcej o zarządzaniu wynikami operacji keszowania można przeczytać w poniższym paragrafie.

Metoda useQueryCache() poza tym, że nie przyjmuje trzeciego parametru (identyfikatora) działa analogicznie do useResultCache(). Szczegółowe informacje można znaleźć w dokumentacji API.

Co dalej?

Keszowanie wyników zapytań niesie ze sobą pewne niebezpieczeństwo – dane mogą szybko stać się nieaktualne. Wraz ze sterownikami otrzymujemy zestaw metod umożliwiających zarządzanie zawartością mechanizmu keszującego. W połączeniu z mechanizmem zdarzeń możemy automatycznie usuwać skeszowane wyniki, zapewniając sobie w ten sposób aktualność danych.

Wolne od takich problemów jest keszowanie wyników parsowania DQL, które zarządzane jest przez Doctrine automatycznie.

Podsumowanie

Pomimo oczywistych zalet, stosowanie mechanizmu keszującego powinno być dobrze przemyślane. O ile keszowanie sparsowanych zapytań DQL jest bezpieczne (jeśli stosujemy prepared statements) i zaleca się jego użycie w większości projektów, to wykorzystanie skeszowanych wyników zapytań powinno dotyczyć tylko wybranych przypadków, w których ma sens i faktycznie przyniesie nam korzyści. Nie należy zapominać tutaj o zasadzie, że nie wszystko co zostało opisane jako dobra metoda na polepszenie wydajności aplikacji, będzie dawało taki rezultat we wszystkich sytuacjach.

Round Robin Database Tool. Wprowadzenie

Author: Paweł Sołtysek (pawel.soltysek) | lipiec 3rd, 2010
avatar

Charakter niektórych danych wymaga, aby były one gromadzone w pewnych ustalonych odstępach czasu. Załóżmy, zupełnie teoretycznie i z pominięciem poprawności realizacji, że bierzemy udział w projekcie mającym na celu monitorowanie temperatury ustalonych punktów na Ziemi. Przyjmijmy, że w owych punktach zainstalowana została aparatura pomiarowa, która co godzinę dokonuje pomiaru, a następnie przesyła zmierzoną wartość do komputera odpowiadającego danemu punktowi. W tym miejscu zaczyna się nasze zadanie — gromadzenie otrzymanych danych. Round Robin Database Tool (RRDtool), to narzędzie które nadaje się do tego wprost idealnie.

RRDtool to oprogramowanie open source służące do przechowywania danych, które pojawiają się stale co pewien okres czasu. Dane gromadzone są w bazach o rozszerzeniu rrd, które wyróżnia wysoka regularność zapisu, cykliczność oraz stały rozmiar. Oprócz funkcjonalności charakterystycznych dla bazy danych, RRDtool oferuje inne mechanizmy. Jako przykład może tutaj posłużyć mechanizm prezentacji graficznej, który pozwala przedstawiać gromadzone dane w postaci konfigurowalnych wykresów. Licznie biblioteki (m. in. dla PHP, Ruby, Perl) umożliwiają używanie RRDtool bezpośrednio z poziomu języka.

Rozpocznijmy od utworzenia nowej bazy, przy czym dla uproszczenia i bez większych strat na ogólności ograniczymy się do jednego punktu pomiarowego. Tworząc bazę (RRDtool udostępnia do tego polecenie create) musimy określić, poprzez jawne podanie wartości lub decydując się na wartość domyślą (dostępną tylko w niektórych przypadkach), zestaw parametrów które ściśle określą jej strukturę i zachowanie. Przykładowo parametr kroku (en. step) oznacza wartość interwału, z jakim będą analizowane dane. Każda baza wymaga zdefiniowana co najmniej jednego źródła danych DS (en. data source), czyli wielkości, której pomiary będziemy chcieli przechowywać. Każde źródło danych posiada nazwę, typ, dopuszczalne wartości ekstremalne oraz wartość pulsu (en. heartbeat), która oznacza maksymalną ilość sekund pomiędzy zapisem dwóch danych. W sytuacji, gdy RRDtool nie otrzyma nowych danych przez okres dłuższy niż wynosi wartość pulsu, automatycznie użyta zostanie wbudowana wartość UNKNOWN (U). RRDtool udostępnia cztery podstawowe typy źródeł danych: GAUGE, COUNTER, DERIVE oraz ABSOLUTE. Źródła typu GAUGE przechowują wartości w postaci, w jakiej zostały one dostarczone do RRDtool, dlatego nadają się do przechowywania np. wartości temperatury. Źródła typów COUNTER, DERIVE oraz ABSOLUTE przechowują wartość przyrostu mierzoną na jednostkę czasu (kroku). Na podstawie wartości kroku i zgodnie ze zdefiniowanymi źródłami danych RRDtool konstruuje układ punktów głównych PDP (en. primary data point) — uporządkowanych par, których pierwszym elementem jest znormalizowany znacznik czasu, a drugim — zestaw wartości wszystkich źródeł danych odpowiadających temu znacznikowi. Dane trafiające do pliku rrd w pierwszej kolejności zapisywane są w układzie punktów głównych, a następnie, po przetworzeniu przez funkcję konsolidującą CF (en. consolidation function), tworzą punkt skonsolidowany CDP (en. consolidated data point). Punkty skonsolidowane przechowywane są w Round Robin Archive (RRA) — specjalnych archiwach, których zdefiniowanie (co najmniej jednego) jest wymagane podczas tworzenia bazy. Na podstawie definicji RRA obliczana jest długość cyklu. Długość cyklu w tym przypadku należy rozumieć jako ilość punktów CDP, które mogą znaleźć się w danym archiwum (kosztem zapisu n+1 punktu CDP, gdzie n oznacza długość cyklu, następuje utrata najstarszego). Dzięki takiemu rozwiązaniu RRDtool posiada jednoznacznie wyznaczone informacje dotyczące ilości punków PDP, które musi przechowywać. Skutkuje to stałym, obliczanym podczas kreacji rozmiarem baz RRD.

Pełna definicja przykładowej bazy RRD odpowiadającej naszym wymaganiom mogłaby wyglądać następująco:

rrdtool create somewhereOnEarth.rrd --start 1277675999 --step 3600 /
DS:temperature:GAUGE:3600:U:U /
RRA:MIN:0.5:24:365 /
RRA:MAX:0.5:24:365 /
RRA:AVERAGE:0.5:24:365 /
RRA:MIN:0.5:720:12 /
RRA:MAX:0.5:720:12 /
RRA:AVERAGE:0.5:720:12 /
RRA:MIN:0.5:8760:1 /
RRA:MAX:0.5:8760:1 /
RRA:AVERAGE:0.5:8760:1

Powyższe polecenie utworzy nową bazę danych o nazwie somewhereOnEarth, z jednym źródłem danych typu GAUGE oraz dziewięcioma archiwami RRA przechowującymi odpowiednio minimalne, maksymalne i średnie temperatury dzienne, minimalne, maksymalne i średnie temperatury miesięczne oraz minimalną, maksymalną i średnią temperaturę roczną. Wartości powinny napływać co godzinę, nie wcześniej niż 28.06.2010, 0:00:00 CET (1277676000 to pierwszy znormalizowany znacznik czasu po 1277675999).

Na tym etapie jesteśmy gotowi na gromadzenie danych. Za uaktualnianie bazy o nowe wartości odpowiedzialna jest funkcja update. Niestety nie dysponujemy odpowiednimi możliwościami, aby dodawać nową wartość temperatury co godzinę. Spróbujemy zasymulować część pomiarów przy użyciu poniższego generatora temperatur:

#!/bin/bash
base=( 15.6 17.4 19.3 23.9 26.1 28.5 30.1 31.4 31.1 29.4 26.9 24.4 22.5 21.3
       20.5 19.7 19.1 18.4 17.5 16.9 16.7 17 17.3 18.9 14.4 15 16.2 17.1 18.8
       18.9 19.1 19.4 19.3 19.1 18.6 18.4 17.4 16.4 16.1 15.4 14.3 14.1 13.3
       11.9 11.3 10.3 10.1 9.9 20.1 22.3 23.3 24.2 27 28.8 30.6 31.1 31.9 31.4
       31.1 30.2 28.9 24.3 20.2 19.5 17.3 15.1 12.9 12.1 13.2 13.6 14.2 14.9 )

fluctuations=( 3.4 5.5 1.3 -3.6 4.2 0.3 2.5 -2.2 -4.1 2.1 )

rrd='somewhereOneEarth.rrd'
timestamp=1277676000

for((i=0;i<=90;i++))
do

 let fluctuationIndex=$RANDOM%10
 let baseIndex=$RANDOM%3

 for((j=0;j<=23;j++))
  do

   let k=$j+$(($baseIndex*24))
   rrdtool update $rrd $timestamp:`echo ${base[$k]}+${fluctuations[$fluctuationIndex]} |bc`
   let timestamp=$timestamp+3600

  done
done

Skrypt powinien uzupełnić naszą bazę o dane z okresu dziewięćdziesięciu dni. Do odpytywania bazy służy funkcja fetch. Funkcja fetch używana jest przez mechanizm prezentacji graficznej wywoływany poleceniem graph. Szeroki zbiór parametrów konfiguracyjnych w większości przypadków umożliwia dostosowywanie wykresów do własnych potrzeb. Przykaładowe polecenie

rrdtool graph sampleGraph.png -s 1277676000 -e 1293832800 /
-w 500 -h 200 -G mono -E -v "Temperature [Celsius]" /
DEF:maxtemp=somwhereOnEarth.rrd:temperature:MAX /
DEF:mintemp=somwhereOnEarth.rrd:temperature:MIN /
AREA:maxtemp#bbff00:"Maximum temperature\n" /
AREA:mintemp#0066ee:"Minimum temperature"

powinno wygenerować wykres zbliżony do następującego (z każdym uruchomieniem skrypt najprawdopodobniej wygeneruje inny zestaw danych, stąd prawdopodobne są różnice w wartościach na wykresie):

RRDtool to z pewnością ciekawe narzędzie, które może okazać się bardzo pomocne w wielu sytuacjach. Należy jednak pamiętać, że powstało, aby radzić sobie z pewną klasą problemów i zastosowanie w niektórych przypadkach będzie niemożliwe lub bardzo trudne. Jednocześnie zachęcam do własnych eksperymentów oraz zapoznania się z dokumentacją oraz tutorialami na stronie projektu.