Přejít k navigační liště

Zdroják » PHP » Doctrine 2 – Optimalizace výkonu

Doctrine 2 – Optimalizace výkonu

Články Databáze, PHP

V tomto článku si představíme způsob, kterým můžeme zvýšit výkon a snížit nároky webové aplikace využívající Doctrine 2 s minimálním zásahem do aplikace.

Nálepky:

Second Level Cache

Výkon aplikace je ovlivněný celou řadou různých faktorů (samotná implementace, počet a druh provedených dotazů do databáze, následná práce s daty apod.). Doctrine 2, která se stará o mapování dat z relačních databází do objektů v aplikaci (princip ORM), má na starost hned několik věcí, které jsou zodpovědné za celkový výkon aplikace. První z nich je porozumění tomu, která data po ní aplikace chce (např. data o produktu s ID=10). Doctrine musí znát způsob, kterým požadovaná data získá z databáze a následně musí vytvořit objekty, do kterých vloží získaná data.

Celý proces od získání dat po vytvoření a naplnění objektu je velmi náročný. Doctrine nejprve musí podle mapovacích metadat (PHP anotace, XML, YAML) porozumět databázovému schématu. Díky tomu dokáže vygenerovat validní a optimalizovaný SQL dotaz do relační databáze. Následně ze získaných dat dokáže vytvořit objekty a naplnit je daty (vyhydratovat).

Co je Second Level Cache?

Second level cache spatřila světlo světa ve verzi Doctrine 2.5.0, která byla vydána v dubnu 2015. Jejím cílem bylo výrazně zvýšit výkon celé Doctrine ORM. Jak toho chtěla docílit? Postavila se mezi aplikaci a databázi a snížila počet prováděných dotazů na minimum.

Před položením SQL dotazu se Doctrine nejprve podívá do Second level cache, zda nemá potřebná data uložena zde. Pokud v cache nejsou, tak položí SQL dotaz do databáze a získaná data si uloží do cache (pro další hledání stejných dat). Pokud požadovaná data jsou nalezena v cache, tak jsou z ní načtena a dotaz do databáze se vůbec neprovede.

Nejsou data jako data

Data, která ukládáme do second level cache, dělíme do třech základních skupin.

1. Data entity

Entita reprezentuje nějaký objekt reálného světa. Například země má mj. svůj identifikátor, název, kód. Podle identifikátoru jsme schopni bezpečně určit, o kterou zemi se jedná.

Doctrine využije tohoto identifikátoru a uloží pod ním všechna ostatní data o entitě. Pokud poté chceme načíst zemi s ID=3, tak jsou všechna data načtena přímo ze Second level cache. Uložený identifikátor v Second level cache slouží také k aktualizaci a mazaní dat. Většina implementací cache by v případě aktualizace dat cache invalidovala. Second level cache zjistí, co se v entitě změnilo, a provede odpovídající aktualizace přímo v cache (stejně jako je provedla databáze). Proto může být následný požadavek pro načtení aktualizované entity opět plně obsloužen z cache.

Doctrine potřebuje znát entity, které má do Second level cache odkládat. Tuto informaci získá z anotace @ORM\Cache, kterou se označí cachovatelné entity.

<?php declare(strict_types=1);

namespace App\Model\Entity;

use Doctrine\ORM\Mapping as ORM;


/**
 * @ORM\Entity
 * @ORM\Cache
 */
class Country
{
	/**
	 * @ORM\Id
	 * @ORM\GeneratedValue
	 * @ORM\Column(type="integer")
	 * @var int
	 */
	private $id;

	/**
	 * @ORM\Column(type="string")
	 * @var string
	 */
	private $name;

	/**
	 * @ORM\Column(type="string", unique=TRUE)
	 * @var string
	 */
	private $code;

	// ...
}

2. Data kolekce

Kolekce je množina souvisejících entit (např. entita Country definuje množinu měst, která jsou v dané zemi). V řeči relačních databází by se jednalo o sloupec country_id obsahující cizí klíč z tabulky city do tabulky country. Přes tyto vazby jsme schopni bezpečně určit, o kterou zemi a město se jedná.

Second level cache dokáže tyto vazby ukládat také (podobně jako jsou uloženy v databázi). Do cache se uloží primární klíč navázané entity a její data jsou následně uložena do vlastního regionu. Pokud kolekci měníme (přidáváme nebo odebíráme položky), tak jsou tyto změny prováděny i na úrovní cache (po změně v databázi).

<?php declare(strict_types=1);

namespace App\Model\Entity;

use Doctrine\ORM\Mapping as ORM;


/**
 * @ORM\Entity
 * @ORM\Cache
 */
class Country
{
	// ...

	/**
	 * @ORM\OneToMany(targetEntity="City", mappedBy="country")
	 * @ORM\Cache
	 * @var City[]
	 */
	private $cities;

	// ...
}

Pro správnou funkčnost musí být entita City také označena anotací @ORM\Cache.

3. Data z dotazů

Do Second level cache nemusíme ukládat pouze entity a vazby mezi nimi, ale máme možnost do ní odkládat i výsledky libovolných DQL dotazů.

Pokud bychom např. chtěli získat města začínající na písmeno „A“ v dané zemi, tak nám bude stačit vytvořit odpovídající DQL dotaz a Second level cache si poté výsledky z databáze uloží. S dalším požadavkem tak dojde k načtení identifikátorů měst z cache (nikoli z databáze) a následná hydratace entit (vytvoření objektů a jejich naplnění daty) je také plně obsloužena ze Second level cache (jsou v ní podle identifikátoru dohledána všechna potřebná data o městech).

<?php declare(strict_types=1);

namespace App\Model\Repository;

use App\Model\Entity\City;
use Doctrine\ORM\EntityManager;


class CountryRepository
{
	/**
	 * @var EntityManager
	 */
	private $entityManager;

	public function __construct(EntityManager $entityManager)
	{
		$this->entityManager = $entityManager;
	}

	/**
	 * @return City[]
	 */
	public function getCitiesStartingWith(string $startWith, int $countryId): array
	{
		$query = $this->entityManager->createQuery('
			SELECT c 
			FROM App\Model\Entity\Country c 
			...
		');

		return $query->setCacheable(TRUE) // use Second Level Cache for query
			->getResult();
	}
}

Regiony

Doctrine do Second level cache neukládá instance entit, ale identifikátor a jednotlivé hodnoty, které dostane z databáze. Aby se z cache nestalo jedno obrovské úložiště, které bude problematické invalidovat, tak vznikly oddělené regiony. Region si lze jednoduše představit jako složku, do které dáváme související data. Pro různá data máme různé složky (regiony) např. produktový region (obsahující produkty, ceny a statistiky prodeje), zákaznický region (obsahující zákazníky, adresy) apod.

/**
 * @ORM\Entity
 * @ORM\Cache(region="my_product_region")
 */
class Product
{
	// ...
}
/**
 * @ORM\Entity
 * @ORM\Cache(region="my_category_region")
 */
class Category
{
	// ...
}

Jednotlivé regiony mají v cache různé jmenné oddíly (namespace) a mohou mít různou expiraci (dobu platnosti záznamů = lifetime). Doctrine umožňuje ruční invalidaci určitého regionu, kdy dojde k zneplatnění všech dat, která jsou v daném regionu uložena, ale všechny ostatní regiony v cache zůstanou naplněné a platné.

V případě ukládání kolekcí jsou data entit ukládána do svých regionů (podle definice v entitě).

Tři způsoby využití cache

Second level cache definuje tři různé způsoby, kterými jsou data do cache ukládána.

1. READ_ONLY

Jedná se o výchozí způsob cachování, který je nejrychlejší a nejjednodušší ze všech tří způsobů. Tento způsob se perfektně hodí pro velmi často získávaná data, která se nikdy nemění. Jak napovídá název, tak v tomto způsobu (módu) není možné provádět jakoukoli editaci a mazání dat. Při pokusu o úpravu nebo smazání dat z entity, která je cachována v módu READ_ONLY, dojde k vyhození výjimky.

/**
 * @ORM\Entity
 * @ORM\Cache(usage="READ_ONLY", region="country_region")
 */
class Country
{
	// ...
}

2. NONSTRICT_READ_WRITE

Tento způsob umožňuje čtení, vkládání, editaci i mazání dat. Kvůli tomu, že Second level cache musí hlídat změny v datech, tak v tomto módu dochází k určitému zpomalení. Cachování přes způsob NONSTRICT_READ_WRITE se hodí pro data, která jsou čtena často, ale přitom může občas dojít k jejich změnám. Přístup k datům není omezen žádným zámkem.

/**
 * @ORM\Entity
 * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="comment_region")
 */
class Comment
{
	// ...
}

3. READ_WRITE

Poslední způsob práce s cache umožňuje stejné možnosti jako předchozí způsob (NONSTRICT_READ_WRITE), ale vytváří zámek před editací a smazáním dat. Tato strategie je nejpomalejší ze všech tří způsobů a hodí se pro data, která musí být aktualizována.

/**
 * @ORM\Entity
 * @ORM\Cache(usage="READ_WRITE", region="customer_region")
 */
class Customer
{
	// ...
}

Závěr

Díky Second level cache může naše aplikace drasticky snížit počet komunikací s databází. Nastavení a použití je velmi snadné a rychlé. Data jsou do cache ukládaná v logických skupinách (regionech), které můžeme libovolně invalidovat. Second level cache neukládá instance entit, ale pouze identifikátor a odpovídající data. Díky tomu dokáže i cache aktualizovat změněné hodnoty a nemusí “hloupě” invalidovat všechna uložená data a načítat si je znovu z databáze. Navíc si sami můžeme rozhodnout, jakým způsobem bude Second level cache zacházet s ukládanými daty, a tím ovlivnit výkon cache.

Chceš se dozvědět více?

Aktuální dokumentace Second level cache vysvětluje problematiku ještě více do hloubky. Na školení “Doctrine 2 – Pokročilé použití” probírám Second level cache spolu s dalšími způsoby optimalizace a celou řadou dalších věcí na reálných příkladech, tak přijď! Pokud chceš Doctrine používat každý den v práci, tak přijď do našeho skvělého IT týmu a posouvej dál první internetovou lékárnu Lekarna.cz spolu s námi.

Komentáře

Odebírat
Upozornit na
guest
3 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
Zobrazit všechny komentáře
Tomáš Votruba

Ahoj Tome, díky za přehledný článek. Ten graf se mi moc líbí! :)

Mám 2 otázky do praxe:

1) Jak moc dlouho ti trvalo to všechno nasát a zjistit, abys to mohl použít v praxi? Zajímá mě hodinová zátěž úkolu „přidat Doctrine 2nd level cache“ pro někoho, kdo zná Doctrine, ale čte o 2nd level cache poprvé.

2) Jak velké je zrychlení např. na přehledu produktů, relativně i absolutně a při jakém počtu produktů (a další elementů, které tam jsou a mají roli a teď mě nenapadli).

Propaguji 2nd level cache ve firmách, kde Doctrine používají, ale chybí mi reálná čísla k přesvědčení :)

Martin Hassman

O tom jaké jsou možnosti snížení počtu dotazů do DB bych mohl
připravit navazující článek.

Za redakci dodávám, že navazující článek rozhodně vítáme a oceníme. 👍

Přístupnost není jen o splnění norem: nový pohled na inkluzivní design

Přístupnost a inkluze možná nepatří mezi nejžhavější témata digitálního světa – dokud o nich nezačne mluvit Vitaly Friedman. Na WebExpo 2024 předvedl, že inkluzivní design není jen o splněných checkboxech, ale hlavně o lidech. S energií sobě vlastní obrátil zažité přístupy naruby a ukázal, že skutečně přístupný web je nejen možný, ale i nezbytný.

Efektivnější vývoj UI nebo API: Co si odnést z WebExpo 2025?

Různé
Komentáře: 0
Jak snadno implementovat moderní uživatelské rozhraní? Které funkce brzdí rychlost vašeho webu? A kdy raději sami přibrzdit, abychom využitím AI nepřekročili etické principy? Debatu aktuálních dev témat rozdmýchá sedmnáctý ročník technologické konference WebExpo, která proběhne v Praze od 28. do 30. května. Který talk či workshop si rozhodně nenechat ujít? Toto je náš redakční výběr z vývojářských hroznů.

Zapřáhněte AI jako nikdy předtím. Květnová konference WebExpo přivítá hvězdy technologického světa

Od 28. do 30. května 2025 promění pražský Palác Lucerna na tři dny technologická konference WebExpo. Na programu je více než 80 přednášek a workshopů od expertů z celého světa. WebExpo tradičně propojuje vývojáře, designéry, marketéry i byznysové lídry a nabízí praktické dovednosti, strategické myšlení a přináší nejnovější trendy nejen v oblasti AI.