Dziś jest piątek, 9 stycznia 2009 roku (z kalendarza...)

Optymalizacja drzewa zawartości

Icon

25.02.2007, 19:20

PHP

Komentarze (0)

Powrót

Drzewo zawartości to "technologia" do zarządzania strukturą serwisu WWW umożliwiająca pełną kontrolę nad jego budową z poziomu panelu administracyjnego. Wszystkie elementy strony, zarówno tworzące szkielet, jak działy z artykułami/newsami, jak i przechowujące dane, czyli same artykuły i newsy, są zgromadzone w jednym wielkim hierarchicznym drzewie. W ten sposób możliwe jest istnienie jednolitego systemu dodawania i edycji treści, kilka sztuczek związanych z nawigacją i ostatecznym renderingiem dla internauty.

Drzewo takie zaimplementowałem w moim prywatnym systemie C-Z-W, który niejednokrotnie gościł już na łamach Zyxist.com. Do efektywnej implementacji samego mechanizmu drzewek wykorzystałem algorytm przechodzenia zmodyfikowaną metodą preorder opisany w jednym z artykułów na tej stronie. Mimo wielu zalet, system ma jednak nadal kilka niedociągnięć:

  • Problemy z cache'owaniem zawartości - czasem zdarza się, że wyedytuję jakiś obiekt, a na stronie dalej widnieje stara zawartość.
  • W niektórych przypadkach podczas renderowania pobierana jest niepotrzebnie ogromna ilość dodatkowych informacji. Wynika to z własności samego drzewa i języka SQL, który nie potrafi w tym wypadku już na poziomie bazy danych odrzucić wszystkich potomków jakiegoś węzła, na którym powinien się zatrzymać.
  • Aktualnie ograniczone możliwości personalizacji - dozwolone typy zawartości przechowywanej w obiektach są zakodowane na sztywno.

Aby uzmysłowić sobie skalę problemu, przedstawię fragment metody selectSite(), która pobiera zawartość drzewka przeznaczoną do renderingu:

 
public function selectSite($hash)
{
global $sql;
// pobierz parametry glownego wezla
$result = array();
			
$sql -> setCache('pagedef_'.$hash, OPD_CACHE_PREPARE);
if(ctype_digit($hash))
{
	$stmt = $sql -> prepare('SELECT
			d.id, d.left, d.right, t.user_selection, d.language_id,
			l.title AS `license_title`, l.address AS `license_address`,
			l.image AS `license_image`, l.important AS `license_important`,
			dpr.title AS `prev_title`, dpr.full_hash AS `prev_full_hash`, dpr.hash AS `prev_hash`,
			dne.title AS `next_title`, dne.full_hash AS `next_full_hash`, dne.hash AS `next_hash`,
			dpa.title AS `parent_title`, dpa.full_hash AS `parent_full_hash`, dpa.hash AS `parent_hash`, dpa.type_id AS `parent_type`
		FROM ((('.DB_PREFIX.'data d, '.DB_PREFIX.'types t, '.DB_PREFIX.'licenses l)
			LEFT JOIN '.DB_PREFIX.'data dpr ON dpr.id = d.prev_id)
			LEFT JOIN '.DB_PREFIX.'data dne ON dne.id = d.next_id)
			LEFT JOIN '.DB_PREFIX.'data dpa ON (dpa.id = d.parent_id AND dpa.parent_id <> 0)
		WHERE l.id=d.license_id AND  t.id=d.type_id AND d.id=:id');
	$stmt -> bindValue(':id', $hash, PDO::PARAM_INT);
}
else
{
	$stmt = $sql -> prepare('SELECT
			d.id, d.left, d.right, t.user_selection, d.language_id,
			l.title AS `license_title`, l.address AS `license_address`,
			l.image AS `license_image`, l.important AS `license_important`,
			dpr.title AS `prev_title`, dpr.full_hash AS `prev_full_hash`, dpr.hash AS `prev_hash`,
			dne.title AS `next_title`, dne.full_hash AS `next_full_hash`, dne.hash AS `next_hash`,
			dpa.title AS `parent_title`, dpa.full_hash AS `parent_full_hash`, dpa.hash AS `parent_hash`, dpa.type_id AS `parent_type`
		FROM ((('.DB_PREFIX.'data d, '.DB_PREFIX.'types t, '.DB_PREFIX.'licenses l)
			LEFT JOIN '.DB_PREFIX.'data dpr ON dpr.id = d.prev_id)
			LEFT JOIN '.DB_PREFIX.'data dne ON dne.id = d.next_id)
			LEFT JOIN '.DB_PREFIX.'data dpa ON (dpa.id = d.parent_id AND dpa.parent_id <> 0)
		WHERE l.id=d.license_id AND t.id=d.type_id AND d.hash=:hash');
	$stmt -> bindValue(':hash', $hash, PDO::PARAM_STR);
}
// itd.

Podane tutaj zapytania pobierają podstawowe informacje o węzłach drzewa. Dalszy algorytm usuwa niewidoczne węzły, a rezultat przekazuje do drugiego zapytania, które pobiera już szczegółowe informacje, jak treść, obrazki, pliki itd. jedynie dla widocznych obiektów. W celu przyspieszenia wyniki są cache'owane.

Jak widać, taki kod jest na dłuższą metę nieco toporny i już od dawna snułem plany na jego przebudowę. Dzisiaj znalazłem interesujące oraz eleganckie rozwiązanie. Polega ono na przypisaniu każdego obiektu do dwóch drzew naraz: globalnego, czyli tego, które jest obecnie, oraz jednego z wielu drzew lokalnych, które funkcjonują niezależnie od siebie. Dla każdego drzewa lokalnego można utworzyć w drugiej tabeli własny rekord z dodatkowymi informacjami... ba, same drzewa można też poukładać w drzewo, choć nie wiem, czy to się już w tym momencie za skomplikowane robić nie będzie. Zalety, jakie daje takie podejście:

  • Możemy utworzyć drzewo "szkielet" zawierający wyłącznie strukturę strony. Jeśli istnieje obiekt np. "dział z artykułami", to jest on w nim zawsze liściem.
  • Dla artykułów zgromadzonych w pojedynczym dziale tworzone jest nowe drzewo lokalne, działające niezależnie od drzewa szkieletowego.
  • Jeśli wyświetlamy fragment szkieletu strony, możemy bez obawy zrezygnować z topornego kodu zamieszczonego powyżej. Wystarczy puścić zapytanie po drzewie szkieletowym i mamy pewność, że pobieranie węzłów zatrzyma się na poziomie działu z artykułami i niżej nie zejdzie.
  • A dział z artykułami, po staremu, będzie mógł wykorzystać własny kod pobierania danych z drzewa.
  • Jednocześnie żadne z drzew nie będzie wchodzić sobie w paradę, ponieważ nie są ze sobą połączone. Jeśli dodajemy artykuł, wystarczy zaktualizować tylko pola left i right lokalnego drzewa działu artykułów, a reszta pozostaje bez zmian.
  • Także system cache jest odciążony. Jeśli dodajemy pojedynczy artykuł, do modyfikacji idzie tylko cache związany z działem artykułów.

Kolejna rzecz, jaką mogłyby rozwiązać takie lokalne drzewka, dotyczy warstwy użytkowej. Każdy obiekt w C-Z-W musi mieć nadany unikalny tzw. "hash", czyli tytuł do wyświetlania w ładnym adresie URL, np. w /o_firmie/historia/czesc2 poszczególne fragmenty odseparowane slashem to hashe poszczególnych obiektów w drzewie zawartości. W drzewie globalnym unikalność musi zachodzić dla absolutnie wszystkich obiektów, czyli nie ma mowy o utworzeniu dwóch subdomen z zawartością i wstawieniu do każdego z nich obiektu home reprezentującego stronę główną. Na razie radziłem sobie w ten sposób, że home działał jak alias do strony głównej ustawionej w danych subdomeny. Nowy mechanizm zezwoli na tworzenie niezależnych drzew szkieletowych dla każdej subdomeny, dając możliwość zapewnienia unikalności na poziomie lokalnym. Nie wspomnę już o takiej rzeczy, jak duplikowanie szkieletu w innej subdomenie. Tworzymy polską wersję językową, wciskamy "Kopiuj", wszystko nam się dokładnie odtwarza w innym miejscu i my już musimy jedynie przetłumaczyć tekst na np. angielski.

Powrót

Skomentuj

NickInformacja
E-mailTylko do użytku wewnętrznego.
WWWNie zapomnij o http://
LayoutNapisz tu, czy widzisz dzienny czy nocny layout.
WpisFormatowanie wiki
Internauto, pamiętaj! Wolność to nie samowola - dbaj o kulturę wypowiedzi oraz dyskusji w sieci.

Na Zyxist.com panuje swoboda wyrażania opinii oraz krytyki pod dowolnym adresem. Jedyny warunek: musi być ona kulturalna i rzeczowa. Na chamstwo, prostactwo lub jawne obrażanie kogokolwiek nie ma tu miejsca i takie komentarze są bardzo szybko usuwane. Jeśli zamierzasz polemizować z treścią wpisu, wpierw uważnie ją przeczytaj.

© Tomasz "Zyx" Jędrzejewski 2005 - 2009 | Wykonanych zapytań: 1 | Serwer wirtualny zapewnia