EpicWEB.pl

webdesign, programowanie, phat lewt!

Ostatni projekt

ddrpl.com

Ostatnie wiadomości

XCache - wprowadzenie do mechanizmu cache w PHP

Cache (pamięć podręczna) to mechanizm, w którym ostatnio pobierane dane dostępne ze źródła o wysokiej latencji i niższej przepustowości są przechowywane w pamięci o lepszych parametrach.

Wiedząc już czym będziemy się zajmować przejdźmy do możliwości.

XCache pozwala nam na cachowanie 2 typów danych:

  • zmienne oraz obiekty - w każdej chwili możemy zapisać oraz odczytać z cache wartość zmiennej czy też typ złożony (tablica, obiekt)
  • skompilowane pliki PHP - każdy plik PHP przed wykonaniem przechodzi przez fazę kompilacji, w któej zamieniany jest na kod maszynowy - XCache pozwala nam pa pominięcie tego kroku i przechowuje w pamięci już skompilowany kod

Instalacja na platformie Linux

Jako, że mój serwer korzysta z SuSE Linux skupię się na instalacji dla tego właśnie środowiska - instalacja na innych platformach powinna być bardzo podobna.

Proces instalacji zaczynamy od pobrania paczki bądź źródła. Zakładam, drogi czytelniku, że wiesz jak kompiluje się programy więc nie będę opisywał tego procesu ;-)

Kiedy już mamy pliki u siebie odnajdujemy plik xcache.so i kopiujemy go do folderu, zawierającego rozszerzenia dla PHP - u mnie jest to /usr/lib/php5/extensions/

Następnie, dodajemy zawartość pliku xcache.ini do naszego php.ini, można to robić komendą:

cat xcache.ini >> /sciezka/do/php.ini

Co spowoduje dopisanie danych na koniec. W tym momencie możemy już zrestartować nasz serwer i cieszyć się z nowego rozszerzenia. Proponuję jednak doczytać kolejne akapity i skonfigurować XCache wg. naszych potrzeb.

Cachowanie zmiennych

Do pierwszego zastosowania służą nam głównie 2 funkcje:

bool xcache_set(string name, mixed value [, int ttl]) - pozwala na zapisanie zdefiniowanej zmiennej pod nazwą name nadając jej wartość value, dodatkowo, możemy podać 3-ci parametr ttl określający ilość sekund, po których wartość powinna zostać usunięta z cache

mixed xcache_get(string name) - funkcja zwracająca dane uprzednio zapisane w cache

Dodatkowo możemy korzystać jeszcze z funkcji nie wymagających dokładniejszego wyjaśnienia czyli:

bool xcache_isset(string name)
bool xcache_unset(string name)
int xcache_inc(string name [, int value [, int ttl]])
int xcache_dec(string name [, int value [, int ttl]])

Dwie ostatnie funkcje odpowiednio zwiększają oraz zmniejszają wartość zapisanej zmiennej.

Cachowanie plików PHP

Do drugiego zastosowania nie są potrzebne nam praktycznie żadne funkcje. Po zainstalowaniu i skonfigurowaniu (uwaga - domyślnie cachowanie tego typu jest wyłączone!) rozszerzenie samo zadba o wypełnianie oraz usuwanie cache w razie potrzeby. Sposób działania możemy modyfikować poprzez następujące dyrektywy konfiguracyjne:

xcache.size - określa całkowitą ilość pamięci, którą przeznaczamy na cache
xcache.count - ile oddzielnych jednostek / wątków cache utworzyć (autorzy sugerują, by liczba ta była równa ilości procesorów/rdzeni)
xcache.slots - minimalna wielkość slotu przeznaczona na jeden plik - każdy plik może zajmować więcej niż jeden slot, ale żaden nie może zająć mniej
xcache.ttl - ilość sekund, przez ile dany plik może być maksymalnie przechowywany w cache

U mnie wartości te wyglądają następująco:

xcache.size = 64M
xcache.count = 4
xcache.slots = 8K
xcache.ttl = 3600

Średnie wykorzystanie wynosi 60-70%, a ilość zapchań (clogs - są to sytuacje, w których następuje żądanie pliku, który powinien już znaleźć się w cache, ale wciąż trwa jego kompilacja) nie przekracza 10 na jednostkę (czyli około 0.1%)

Administracja - XCache Admin

W folderze z XCache znajdziemy też katalog admin zawierający prosty panel administracyjny pozwalający na przeglądanie statystyk, nazw przetrzymywanych plików oraz zmiennych (w wypadku tych ostatnich - także na ich modyfikację!) Zanim skorzystamy z panelu administracyjnego, musimy zmienić jeszcze kilka rzeczy w php.ini, a mianowicie:

[xcache.admin]
xcache.admin.enable_auth = On
xcache.admin.user = “login_administratora”
xcache.admin.pass = “tu_wstaw_wynik_md5(twoje_haslo)”

Po takim zabiegu musimy jeszcze tylko zresetować serwer.

Kilka słów na zakończenie

Pliki cachowane są do momentu osiągnięcia lokalnego bądź też globalnego ttl lub do chwili, gdy nastąpi jakakolwiek zmiana w ich źródle.

Należy zwrócić uwagę, że wszelkie zmienne cachowane są globalnie. Co to znaczy? Każda zcachowana zmienna dostępna jest dla dowolnego wątku PHP - oznacza to, że jeżeli użytkownik A zapisze jakąś zmienną do cache, użytkownik B może ją bez problemu odczytać (dotyczy to tylko zmiennych! pliki nie są cachowane w ten sposób) używając xcache_get! Dodatkowo, jeżeli dwa różne skrypty (lub nawet dwa takie same skrypty - np. gotowe fora internetowe) używają cache będą one dzielić wszystkie dane! By uniknąć tego typu działania sugeruję użyć prostego wrappera do funkcji, który przez zapisaniem lub odczytaniem zmiennej o danej nazwie odpowiednio ją zmodyfikuje - np.:

<?
function myCache_set($name, $value, $ttl = null) {
$salt = "moj_prywatny_skrypt_";
return xcache_set($salt.$name, $value, $ttl);
}

Funkcja GROUP_CONCAT()

Zapewne wielu z was (nas) miało nie raz styczność z poniższym zagadnieniem:

System aktualności z podziałem na kategorie, z założeniem, że jedna aktualność może należeć do wielu kategorii - np. system blogowy. Zakładając, że wiemy co to normalizacja bazy danych, robimy coś mniej więcej takiego:

  1. tabela `posts` (id, title, text)
  2. tabela `tags` (id, name)
  3. tabela `tags_to_posts` (post_id, tag_id)

Następnie, w celu wybrania postów i kategorii doń należących wywołujemy minimum 2 zaptytania:

Wybieramy wiadomości (zapytanie A):

SELECT * FROM `posts`

I w pętli wybieramy kategorie (zapytanie B):

SELECT t.name FROM `tags_to_posts` LEFT JOIN `tags` ON tags.id = tags_to_posts.tag_id WHERE tags_to_posts.post_id = ID_POSTU

Niby nic złego, i wiele osób zasugeruje takie właśnie rozwiązanie. Istnieje natomiast prostsze rozwiązanie, pozwalające nam wyciągnąć wszystkie interesujące nas informacje za jednym razem - jedyny warunek - MySQL >= 4.1

Powitajmy GROUP_CONCAT()

GROUP_CONCAT(nazwa_pola ORDER BY nazwa_pola SEPARATOR ',')

Przykład zastosowania (zapytanie C):

SELECT p.*, GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ', ' ) AS `kategorie` FROM `posts` AS `p` LEFT JOIN `tags_to_posts` AS `ttp` ON ttp.post_id = p.id LEFT JOIN `tags` AS `t` ON t.id = ttp.tag_id GROUP BY p.id

W wyniku zapytania otrzymamy wszystkie dane z tabeli `posts` ORAZ nową kolumnę o nazwie kategorie zawierającą wszystkie kategorie do których należy dany post oddzielone przecinkiem.

Co prawda zamiast dwóch prostych SELECTów zastosowaliśmy dwa LEFT JOINy - ale jeżeli przyjrzymy się bliżej, okazuje się, że zaoszczędziliśmy N zapytań, gdzie N to ilość pobranych przez zapytanie A aktualności (zapytanie B wykonywane jest w pętli dla każdego rekordu pobranego w zapytaniu A).

Więcej informacji (oraz przykładów zastosowania) znajdziesz w manualu MySQL.

Funkcja FIELD()

Funkcja FIELD() w MySQL zwraca pozycję pierwszego podanego stringu w zbiorze stringów, podanych jako kolejne argumenty funkcji.

Niby nic specjalnego, ale jednak ma ona jedno kluczowe zastosowanie!

Wyobraźmy sobie taką kwerendę:

SELECT * FROM `items` WHERE `id` IN(1, 8, 2, 14, 16, 7)

Najprostsza w świecie kwerenda zwracająca nam przedmioty z jakiegośtam zbioru. W jakiej kolejności będą posortowane wyniki? Najprawdopodobniej wg. PRIMARY KEY. Ale co zrobić, jeżeli chcemy by były w takiej, jaka widnieje w IN() - może są to dane uzyskane z zewnętrznego źródła, posortowane tak, jak oczekuje tego klient?

Tu z pomocą przychodzi nam FIELD() - dzięki tej funkcji wszystko posortujemy tak jak chcemy i to błyskiem. Wystarczy przerobić kwerendę na następującą:

SELECT * FROM `items` WHERE `id` IN(1, 8, 2, 14, 16, 7) ORDER BY FIELD(`id`, 1, 8, 2, 14, 16, 7)

Dzięki temu wyniki będą posortowane dokładnie w takiej kolejności, w jakiej występują w podanym zbiorze!