Praktyczne wykorzystanie Memcached + PHP

memcached, mała darmowa aplikacja, która odpowiednio wykorzystana pozwoli nam bardzo szybko i skutecznie podnieść wydajność aplikacji lub serwisów WWW. Słowo memcache na łamach serwisu pojawiało się już kilka razy (opis, charakterystyka memcache, zastosowanie memcache jako storage + nginx) i na pewno jeszcze się pojawi wielokrotnie, ale tym razem wystąpi w roli głównej ;-) pokażę jak można praktycznie wykorzystać memcache w celu zwiększenia wydajności aplikacji PHP poprzez odciążenie np. bazy danych. Za przykład posłuży nam prosty skrypt, ale najpierw instalacja...

Do prawidłowego działania potrzebujemy daemona memcached oraz modułu PHP php5-memcache.

Instalacja memcached (Ubuntu)

root@katha:/home/jamzed# apt-get install memcached php5-memcache

memcached do prawidłowego działania wymaga libevent (biblioteki służącej do obsługi zdarzeń, w skrócie, obsługa kolejki połączeń). Warto pamiętać o tym, żeby po instalacji php5-memcache wykonać restart Apache.

Jeśli apt-get install powiódł się, powinniśmy mieć działający serwer memcached:

root@katha:/home/jamzed# /etc/init.d/memcached start
Starting memcached: memcached disabled in /etc/default/memcached.

Komunikat ten oznacza, że do wystartowania memcached, musimy dokonać małej modyfikacji w pliku /etc/default/memcached:

ENABLE_MEMCACHED=no
zmieniamy na
ENABLE_MEMCACHED=yes

startujemy jeszcze raz:

root@katha:/home/jamzed# /etc/init.d/memcached start
Starting memcached: memcached.

Plik konfiguracyjny memcached znajduje się w lokalizacji /etc/memcached.conf i zawiera takie opcje jak:

  • -d - tryb pracy jako daemon
  • logfile - plik z logami naszego daemona
  • -v - verbose mode (dostępne: -v oraz -vv)
  • -m 64 - ilość pamięci jaką przeznaczamy memcache'owi (tyle MB danych pomieści)
  • -p 11211 - port na którym będzie słuchał
  • -u nobody - uprawnienia użytkownika z jakimi się uruchomi
  • -l 127.0.0.1 - adres IP na którym będzie słuchał
  • -c 1024 - limit połączeń

Mając działający daemon, możemy spróbować napisać prosty kod w PHP do sprawdzenia działania naszego serwera:

Co robi powyższy kod:

  • tworzy nowy obiekt memcached typu Memcache,
  • wykonuje połączenie do serwera memcache,
  • sprawdza czy może pobrać klucz "key",
  • jeśli wartość została pobrana, jest wyświetlana i skrypt końcy działanie,
  • jeśli nie ma wpisanej wartości, jest dodawana na 10 sekund, następnie pobierana oraz wyświetlana,

Wyjaśnienie argumentów $memcache->set ('nazwa_klucza', 'wartość_która_będzie_pod_kluczem', kompresja, czas_życia_obiektu);

Dlaczego tak? Ponieważ przekładając to na implementację gdzie będziemy mieli bazę danych, najpierw powinniśmy sprawdzić czy mamy w memcache'u wynik kwerendy, w przypadku kiedy będzie to pobierzemy go z memcached i zostawimy bazę w spokoju, natomiast jeśli nie będzie to pobierzemy go z bazy danych i wstawimy do memcache'a na przyszłość.

Kod pobierający 10000 razy dane z bazy może wyglądać następująco:

mamy tutaj:

  • połączenie do MySQL'a,
  • wybranie bazy danych,
  • pętlę wykonującą się 10,000 razy,
  • losowanie $rand_id z przediału 1,100,
  • pobranie z bazy danych dla ID = $rand_id,
  • wyświetlenie informacji na ekran,
  • zamknięcie połączenia z bazą danych

wszystko jest ok, zapytanie jest bardzo proste i prawdopodobnie baza i tak wrzuci to wszystko do pamięci, więc po zastosowaniu memcached w tym konkretnym przypadku raczej nie skróci się nam czas wykonywania skryptu, ale chodzi mi o przedstawienie samego sposobu, dlatego nie zwracajmy na to uwagi ;-) przepiszmy powyższy kod tak by przed pobraniem informacji, sprawdził czy jest ona w memcached i dopiero w przypadku kiedy jej nie ma, pobierał ją z bazy.

Skrypcik trochę nam się powiększył, ale nic skomplikowanego nie doszło ;-) pojawiła się funkcja memQuery(), która pobiera w argumencie zapytanie SQL, wylicza z niego sumę kontrolną md5, która tak naprawdę będzie naszym kluczem dla wpisu w memcached, pod którym zostanie umieszczona właściwa wartość (pobrana z bazy danych), jeśli taki klucz będzie w memcached to funkcja zwróci wartość, jeśli nie, to odpyta bazę danych, umieści wynik w memcached na 10 sekund i dopiero zwróci wartość. Taka implementacje daje nam możliwość szybkiego umieszczania w memcached wyników zapytań SQL, to jest tylko PoC, nie obsługiwane są żadne wyjątki ani skomplikowane struktury danych, podpowiem tylko, że jeśli wartości nie będą pojedyncze tak jak w tym przypadku, a np. zapytanie będzie takie SELECT a,b,c,d FROM d, to warto skusić się o serializację danych przed umieszczeniem ich w memcached ;-)

Kiedy używać memcached? Zawsze! ;-) o ile oczywiście jest to możliwe, kiedy np. pobierasz dane z bazy, który przez pewien okres czasu są stałe. Natomiast w przypadku danych które szybko się zmieniają, wrzucanie tego dodatkowo do warstwy cache'ującej nie ma sensu, bo dochodzi Ci dodatkowe połączenie TCP i obsługa, takie zapytania najczęściej charakteryzują się tym, że zawierają jakiś przedział czasowy (WHERE data_dodania < aktulny_czas)...

Warto też wspomnieć, że samo tworzenie zapytań SQL jest bardzo istotną kwestią przy wydajności, często już od samej kolejności użycia klauzuli WHERE zależy szybkość wykonania zapytania, a o kwerendach zawierających timestamp nawet nie będę wspominał. ;-) tak więc nie traktujcie memcached jako lekarstwa na wszystkie problemy, zawsze w pierwszej kolejności optymalizujcie zapytania SQL, optymalizujcie i jeszcze raz optymalizujcie ;-)