Doctrine Query Cache w Symfony

Dzisiaj o odkrywaniu Ameryki w konserwie. Właśnie takie miałem wrażenie, gdy dłubiąc w projekcie trafiłem na rozdział na temat cache w dokumentacji Doctrine. Wcześniej tam nie trafiłem, bo całe keszowanie robiłem w Symfony – wszak do pamięci podręcznej najlepiej wrzucać efekt końcowy. Dodatkowo w dokumentacji Symfony nie rzuciło mi się w oczy nic na temat cacheowania w Doctrine.

Nie zamierzam streszczać dokumentacji Doctrine. W kontekście tej notki ważne jest, że Doctrine może zapamiętywać w cache dwa rodzaje danych:

  • skompilowane zapytania
  • wyniki zapytań

Dzięki zapamiętywaniu zapytań czasochłonny proces parsowania zapytania i budowania docelowej SQL-ki jest wykonywany tylko raz, kolejne wywołania będą korzystać z danych zapamiętanych w pamięci podręcznej. Problem aktualności danych w cache nie istnieje ponieważ zmiana zapytania spowoduje automatyczne wygenerowanie nowych danych. Korzyści są niebagatelne, użycie proste jak konstrukcja cepa, a problemów nie ma. Dlatego zgodnie z dokumentacją i zdrowym rozsądkiem, pamięć podręczna zapytań powinna być zawsze włączona, nawet w środowisku testowym.

Włączenie cache dla Doctrine w projekcie Symfony jest banalne i sprowadza się do dodania króciutkiej metody w klasie config/ProjectConfiguration.class.php.

public function configureDoctrine(Doctrine_Manager $manager)
{
    $manager->setAttribute(Doctrine_Core::ATTR_QUERY_CACHE, new Doctrine_Cache_Apc());
}

Powyższy kod spowoduje, że skompilowane zapytania będą zapamiętywane w pamięci APC. Oprócz APC Doctrine może współpracować z serwerem memcached lub inną bazą danych (np. SQLite).

Cache można włączyć nie tylko na poziomie managera, równie dobrze można zrobić to w samym zapytaniu:

$q = Doctrine_Query::create()
    ->useQueryCache(new Doctrine_Cache_Apc());

Jak wspomniałem wcześniej, pamięć podręczna zapytań powinna być włączona zawsze. Na poziomie konkretnego zapytania więcej sensu ma włączanie zapamiętywania wyników:

$q = Doctrine_Query::create()
    ->useResultCache(new Doctrine_Cache_Apc());

Dane binarne w PHP

Przy pisaniu poprzedniej notki o base64_encode() przypomniało mi się inne zastosowanie base64, a mianowicie osadzanie danych binarnych w kodzie PHP. Poniższy kawałek kodu wypluwa przezroczysty obrazek w formacie GIF o rozmiarach 1 x 1 px. Jest to fragment skryptu, który zlicza otwarcia mailingu.

header('Content-Type: image/gif'); 
header('Expires: Wed, 11 Nov 1998 12:00:00 GMT'); 
header('Cache-Control: no-cache'); 
header('Cache-Control: must-revalidate'); 
die(base64_decode('R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOwA='));

Oczywiście równie dobrze plik można zapisać na dysku i wypluwać go przez file_get_contents() lub przekierować do niego przez wysłanie nagłówka Location, ale powyższe rozwiązanie ma tę zaletę, że plik jest osadzony w skrypcie. To może być istotne jeśli sprzedajemy skrypt zakodowany np. ionCubem i nie chcemy, żeby klient miał możliwość podmiany obrazka. Dodatkowo base64_decode() ma szanse być szybsze niż otwarcie i wczytanie zewnętrznego pliku, a od przekierowania przez nagłówek Location jest szybsze na bank.

Z drugiej strony osadzanie plików w skrypcie ma sens tylko dla niedużych plików, tak π razy oko do 2-3 KiB. Przy większych plikach wybrałbym file_get_contents(), ze względu na mniejszy rozmiar skryptu PHP i prawdopodobnie większą szybkość działania.

Z trzeciej strony, jeśli podstawowym celem jest szybkość działania, można pokusić się o jeszcze inne rozwiązanie.

define(EMPTY_GIF_IMAGE, "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\xff\xff\xff\x21\xf9\x04\x01\x0a\x00\x01\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x4c\x01\x00\x3b\x00");
header('Content-Type: image/gif'); 
header('Expires: Wed, 11 Nov 1998 12:00:00 GMT'); 
header('Cache-Control: no-cache'); 
header('Cache-Control: must-revalidate'); 
die(EMPTY_GIF_IMAGE);

Jeśli korzystamy z APC lub podobnego rozwiązania to po kompilacji skryptu stała EMPTY_GIF_IMAGE będzie zawierała nasz pliczek GIF w postaci gotowej do użycia, odpada konieczność konwersji z base64. Zastrzegam, że nie sprawdzałem tego w praktyce, ale zdziwiłbym się, gdyby nie było to najszybsze z opisywanych rozwiązań.

Krótsze skróty dzięki base64

Skróty wiadomości (ang. message digest, hash) to chleb powszedni w programowaniu. Przez funkcje md5(), sha1() itp. generowane są identyfikatory sesji, ciasteczka identyfikujące użytkownika, nazwy plików i tuzin innych rzeczy.

Skrót, jak sama nazwa wskazuje, powinien być krótki. Tymczasem w większości wypadków, z którymi się zetknąłem, skróty używane są w zapisie szesnastkowym, co oznacza, że 128 bitowy skrót MD5 jest zapisywany w 32 bajtach (czyli 256 bitach), a 160 bitowy SHA1 potrzebuje 40 znaków (256 bitów). To, delikatnie mówiąc, trąci rozrzutnością. Oszczędzić bajty można wykorzystując zapis base64, co pokazuje poniższy przykład:

var_dump(sha1('Ala ma kota')); 
var_dump(strtr(rtrim(base64_encode(sha1('Ala ma kota', true)), '='), '+/', '-_'));

Wynik:

string(40) "43fd70009a97a7d311c5644047ccc700f8d08a9d" 
string(27) "Q_1wAJqXp9MRxWRAR8zHAPjQip0"

Czytaj dalej „Krótsze skróty dzięki base64”