Nette Caching
Önbellek, bir zamanlar hesaplaması pahalı olan veriyi saklayarak uygulamanızı hızlandırır ve gelecekte ona daha hızlı erişilmesini sağlar. Şunları ele alacağız:
- önbelleğin nasıl kullanılacağı
- depo arka ucunun nasıl değiştirileceği
- önbelleğin doğru biçimde nasıl geçersiz kılınacağı
Nette'de önbelleği kullanmak çok basittir, yine de gelişmiş önbellekleme gereksinimlerini karşılar. Başarım ve %100 dayanıklılık için tasarlanmıştır. En yaygın depo arka uçları için adaptörler içerir. Etikete dayalı geçersiz kılmayı, zaman aşımını, cache stampede'e karşı korumayı ve dahasını destekler.
Kurulum
Paketi Composer kullanarak indirin ve kurun:
composer require nette/caching
Temel Kullanım
Önbellekle çalışmanın çekirdek unsuru Nette\Caching\Cache nesnesidir. Onun bir örneğini
oluşturur ve yapıcıya bir depo arka ucu nesnesi aktarırız. Bu depo nesnesi, verinin saklanacağı fiziksel konumu temsil eder
(veritabanı, Memcached, diskteki dosyalar vb.). Depo nesnesini genellikle Nette\Caching\Storage tipini isteyerek bağımlılık enjeksiyonuyla elde edersiniz. Temelleri Depolar bölümünde öğreneceksiniz.
3.0 sürümünde arayüzün hâlâ I öneki vardı, dolayısıyla adı
Nette\Caching\IStorage idi. Ayrıca Cache sınıfının sabitleri büyük harfle yazılıyordu,
örneğin Cache::Expire yerine Cache::EXPIRE.
Aşağıdaki örnekler için Cache adında bir alias'ımız ve $storage değişkeninde bir depo
örneğimiz olduğunu varsayın.
use Nette\Caching\Cache;
$storage = /* ... */; // Nette\Caching\Storage örneği
Önbellek özünde bir anahtar-değer deposudur; yani veriyi, ilişkisel dizilere benzer biçimde anahtarlarla okur ve yazarız. Uygulamalar birden çok bağımsız parçadan oluşur. Tüm parçalar tek bir depoyu kullansaydı (diskte tek bir dizin düşünün), er ya da geç anahtar çakışmaları olurdu. Nette Framework bunu, depo alanını ad alanlarına (kavramsal olarak alt dizinler gibi) bölerek çözer. Uygulamanın her parçası böylece benzersiz bir adla kendi ad alanında çalışır ve hiçbir çakışma olmaz.
Ad alanı adını Cache sınıfı yapıcısının ikinci argümanı olarak belirtin:
$cache = new Cache($storage, 'Full Html Pages');
Gerekirse, var olan bir örnekten derive() metodunu kullanarak bir alt ad alanına kapsamlanmış yeni bir
önbellek türetebilirsiniz:
$subCache = $cache->derive('Images');
Artık $cache nesnesini önbellekten okumak ve ona yazmak için kullanabiliriz. load() metodu her iki
amaca da hizmet eder. İlk argüman anahtar, ikincisi ise anahtar önbellekte bulunamazsa çağrılan bir PHP callback'idir.
Callback değeri üretir, onu döndürür ve load() metodu onu önbelleğe alır:
$value = $cache->load($key, function () use ($key) {
$computedValue = /* ... */; // pahalı hesaplama
return $computedValue;
});
İkinci parametre atlanırsa ($value = $cache->load($key)), öğe önbellekte bulunamadığında
load() metodu null döndürür.
Yalnızca dizelerin değil, serileştirilebilir her yapının önbelleğe alınabilmesi harikadır. Aynısı anahtarlar için de geçerlidir.
Bir öğeyi önbellekten silmek için remove() metodunu kullanın:
$cache->remove($key);
Bir öğeyi önbelleğe $cache->save($key, $data, ?array $dependencies = null) metoduyla da kaydedebilirsiniz.
Ancak yukarıda gösterilen load() yaklaşımı genellikle yeğlenir.
Memoization
Memoization, bir fonksiyon ya da metot çağrısının sonucunu önbelleğe almaktır; böylece bir dahaki sefere aynı argümanlarla çağrıldığında yeniden hesaplanmak yerine önbellekteki sonuç döndürülür.
Metotlar ve fonksiyonlar, call(callable $callback, ...$args) kullanılarak memoize edilmiş biçimde
çağrılabilir:
$result = $cache->call('gethostbyaddr', $ip);
gethostbyaddr() fonksiyonu böylece her benzersiz $ip argümanı için yalnızca bir kez
çağrılır. Aynı $ip ile sonraki çağrılar önbellekteki değeri döndürür.
Bir metodun ya da fonksiyonun etrafında, sonradan çağrılabilecek memoize edilmiş bir sarmalayıcı oluşturmak da mümkündür:
function factorial($num)
{
return /* ... */;
}
$memoizedFactorial = $cache->wrap('factorial');
$result = $memoizedFactorial(5); // ilk seferde hesaplar
$result = $memoizedFactorial(5); // ikinci seferde önbellekten döndürür
Süre Dolması ve Geçersiz Kılma
Önbellekleme kullanırken, daha önce saklanmış verinin ne zaman geçersiz olacağı sorusunu ele almak gerekir. Nette Framework, verinin geçerliliğini sınırlamak ya da onu açıkça silmek için mekanizmalar sunar (framework'ün terminolojisinde buna „geçersiz kılma“ denir).
Verinin geçerliliği kaydetme sırasında, genellikle save() metodunun üçüncü parametresiyle ayarlanır;
örneğin:
$cache->save($key, $value, [
$cache::Expire => '20 minutes',
]);
Alternatif olarak, load() metodundaki callback'e başvuruyla aktarılan $dependencies parametresiyle
ayarlanabilir; örneğin:
$value = $cache->load($key, function (&$dependencies) {
$dependencies[Cache::Expire] = '20 minutes';
return /* ... */;
});
Ya da load() metodunun kendi 3. parametresi kullanılarak; örneğin:
$value = $cache->load($key, function () {
return /* ... */;
}, [Cache::Expire => '20 minutes']);
Aşağıdaki örneklerde, callback içinde $dependencies değişkenini kullanan ikinci çeşidi
varsayacağız.
Süre Dolması
Süre dolmasının en basit biçimi bir zaman sınırıdır. Bu, veriyi 20 dakikalık geçerlilikle önbelleğe alır:
// saniye sayısını ya da bir UNIX zaman damgasını da kabul eder
$dependencies[Cache::Expire] = '20 minutes';
Geçerlilik süresinin her okumada uzamasını istiyorsanız (kayan süre dolması), bunu şöyle sağlayabilirsiniz, ama bunun önbellek ek yükünü artırdığını bilin:
$dependencies[Cache::Sliding] = true;
Yararlı bir seçenek, belirli bir dosya ya da birkaç dosyadan biri değiştirildiğinde verinin süresini doldurmaktır. Bu, örneğin bu dosyaların işlenmesinden türeyen veriyi önbelleğe alırken yararlıdır. Mutlak yollar kullanın.
$dependencies[Cache::Files] = '/path/to/data.yaml';
// ya da
$dependencies[Cache::Files] = ['/path/to/data1.yaml', '/path/to/data2.yaml'];
Bir önbellek öğesinin, başka belirli bir öğenin (ya da birkaçından birinin) süresi dolduğunda süresini
doldurmasını sağlayabiliriz. Bu, örneğin bir HTML sayfasının tamamını ve parçalarını farklı anahtarlarla önbelleğe
alırken yararlıdır. Bir parça değiştiğinde sayfanın tamamı geçersiz kılınmalıdır. Parçalar frag1 ve
frag2 gibi anahtarlarla saklanıyorsa şunu kullanın:
$dependencies[Cache::Items] = ['frag1', 'frag2'];
Süre dolması, özel fonksiyonlar ya da statik metotlar kullanılarak da denetlenebilir. Bunlar, öğenin hâlâ geçerli olup
olmadığını saptamak için her okumada çağrılır. Örneğin, PHP sürümü her değiştiğinde bir öğenin süresini
doldurabiliriz. Geçerli sürümü bir parametreyle karşılaştıran bir fonksiyon oluşturun ve kaydederken bağımlılıklara
[fonksiyon adı, ...argümanlar] biçiminde bir dizi ekleyin:
function checkPhpVersion($ver): bool
{
return $ver === PHP_VERSION_ID;
}
$dependencies[Cache::Callbacks] = [
['checkPhpVersion', PHP_VERSION_ID] // checkPhpVersion(...) === false olduğunda süresi dolar
];
Doğal olarak tüm bu ölçütler birleştirilebilir. Ölçütlerden en az biri artık karşılanmıyorsa önbellek öğesinin süresi dolar.
$dependencies[Cache::Expire] = '20 minutes';
$dependencies[Cache::Files] = '/path/to/data.yaml';
Etiketlerle Geçersiz Kılma
Etiketler çok yararlı bir geçersiz kılma mekanizması sunar. Önbellekte saklanan her öğeye bir etiket listesi (rastgele dizeler) atayabiliriz. Örneğin, önbelleğe almak istediğimiz, bir makaleyi ve yorumlarını gösteren bir HTML sayfamız olduğunu varsayalım. Kaydederken ilgili etiketleri belirtiriz:
$dependencies[Cache::Tags] = ["article/$articleId", "comments/$articleId"];
Şimdi yönetim bölümüne geçelim. Burada makaleleri düzenlemek için bir formumuz var. Makaleyi veritabanına kaydetmenin
yanı sıra, önbellekteki öğeleri etiketlerine göre silmek için clean() metodunu çağırırız:
$cache->clean([
$cache::Tags => ["article/$articleId"],
]);
Benzer biçimde, yeni bir yorum eklerken (ya da bir yorumu düzenlerken) ilgili etiketi geçersiz kılmayı unutmamalıyız:
$cache->clean([
$cache::Tags => ["comments/$articleId"],
]);
Ne başardık? HTML önbelleğimiz artık ilişkili makale ya da yorumları her değiştiğinde geçersiz kılınacak
(silinecek). ID = 10 olan makale düzenlendiğinde article/10 etiketi geçersiz kılınır ve bu etiketi taşıyan
önbellekteki HTML sayfası silinir. Aynısı, ilgili makalenin altına yeni bir yorum eklendiğinde de olur.
Etiketler bir Journal gerektirir.
Önceliğe Göre Geçersiz Kılma
Tek tek önbellek öğelerine öncelik atayabiliriz. Bu, örneğin önbellek belirli bir boyut sınırını aştığında denetimli silme olanağı sağlar:
$dependencies[Cache::Priority] = 50;
Önceliği 100'e eşit ya da ondan küçük olan tüm öğeleri silmek için:
$cache->clean([
$cache::Priority => 100,
]);
Öncelikler Journal denen şeyi gerektirir.
Önbelleği Temizleme
Cache::All parametresi her şeyi temizler:
$cache->clean([
$cache::All => true,
]);
Toplu Okuma
Önbellekten toplu okuma ve ona toplu yazma için bulkLoad() metodunu kullanın. Ona bir anahtar dizisi aktarın;
o da karşılık gelen değerlerden oluşan bir dizi döndürür:
$values = $cache->bulkLoad($keys);
bulkLoad() metodu load() metoduna benzer çalışır ve ikinci bir callback parametresi de kabul
eder. Bu callback, üretilmekte olan öğenin anahtarını alır:
$values = $cache->bulkLoad($keys, function ($key, &$dependencies) {
$computedValue = /* ... */; // pahalı hesaplama
return $computedValue;
});
Tersine, birden çok öğeyi tek seferde yazmak için, key => value çiftlerinden oluşan bir dizi ve isteğe
bağlı bağımlılıklar alan bulkSave() metodunu kullanın:
$cache->bulkSave([
$key1 => $value1,
$key2 => $value2,
], [Cache::Expire => '20 minutes']);
PSR-16 ile Kullanım
Nette Cache'i bir PSR-16 arayüzüyle kullanmak için PsrCacheAdapter sınıfından yararlanabilirsiniz. Bu,
Nette Cache ile PSR-16 uyumlu bir önbellek gerçekleştirimi bekleyen her kod ya da kütüphane arasında kusursuz entegrasyon
sağlar.
$psrCache = new Nette\Bridges\Psr\PsrCacheAdapter($storage);
Artık $psrCache nesnesini standart bir PSR-16 önbelleği olarak kullanabilirsiniz:
$psrCache->set('key', 'value', 3600); // değeri 1 saat saklar
$value = $psrCache->get('key', 'default');
Adaptör, getMultiple(), setMultiple() ve deleteMultiple() dahil PSR-16'da tanımlı
tüm metotları destekler.
Çıktı Önbellekleme
Çıktı çok zarif biçimde yakalanıp önbelleğe alınabilir:
if ($capture = $cache->capture($key)) {
// echo ... bir miktar veri yazdırılıyor
$capture->end(); // çıktıyı önbelleğe kaydet
}
Çıktı önbellekte zaten varsa, capture() metodu onu yazdırır ve null döndürür, dolayısıyla
if koşulunun bloğu atlanır. Aksi hâlde çıktıyı arabelleğe almaya başlar ve bir $capture
nesnesi döndürür; yakalanan veriyi sonunda end() metoduyla önbelleğe kaydetmek için onu kullanırsınız.
3.0 sürümünde bu metodun adı $cache->start() idi.
Latte'de Önbellekleme
Latte şablonlarında önbellekleme çok basittir. Şablonun önbelleğe almak
istediğiniz bölümünü {cache}...{/cache} etiketleriyle sarmanız yeter. Kaynak şablon dosyası (önbelleğe
alınan bloğun içine eklenen şablonlar dahil) her değiştiğinde önbellek otomatik geçersiz kılınır. {cache}
etiketleri iç içe olabilir. İç içe bir blok geçersiz kılındığında (örneğin bir etiket aracılığıyla), onun ana
bloğu da geçersiz kılınır.
Etiketin içinde, önbellek girdisinin bağlanacağı anahtarları (burada $id değişkeni) belirtebilir, bir
süre dolma zamanı ayarlayabilir ve geçersiz kılma etiketleri
tanımlayabilirsiniz.
{cache $id, expire: '20 minutes', tags: [tag1, tag2]}
...
{/cache}
Tüm bu parametreler isteğe bağlıdır, dolayısıyla süre dolmasını, etiketleri, hatta anahtarları belirtmeniz gerekmez.
Önbelleklemenin kullanımı if ile koşullu da kılınabilir; içerik yalnızca koşul karşılanırsa
önbelleğe alınır:
{cache $id, if: !$form->isSubmitted()}
{$form}
{/cache}
Depolar
Depo, verinin saklandığı fiziksel konumu temsil eden bir nesnedir. Bir veritabanı, bir Memcached sunucusu ya da en kolay erişilebilen depoyu, yani diskteki dosyaları kullanabiliriz.
| Depo | Açıklama |
|---|---|
| FileStorage | Varsayılan depo, önbelleği diskteki dosyalara kaydeder. |
| MemcachedStorage | Saklama için bir Memcached sunucusu kullanır. |
| MemoryStorage | Veri bellekte geçici olarak saklanır (istek bitince yiter). |
| SQLiteStorage | Veri bir SQLite veritabanı dosyasında saklanır. |
| DevNullStorage | Veri aslında saklanmaz; sınama için yararlıdır. |
Depo nesnesini, Nette\Caching\Storage tipini isteyerek bağımlılık enjeksiyonuyla elde edersiniz. Nette varsayılan olarak,
veriyi geçici dosyalar dizinindeki
cache alt dizininde saklayan bir FileStorage nesnesi sağlar.
Varsayılan depoyu yapılandırmada değiştirebilirsiniz:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
FileStorage
Önbellek girdilerini diskteki dosyalara yazar. Nette\Caching\Storages\FileStorage deposu başarım açısından
epey iyileştirilmiştir ve en önemlisi, işlemlerin tam atomikliğini güvence altına alır. Bu ne demek? Önbelleği
kullanırken, başka bir iş parçacığının henüz tümüyle yazmadığı bir dosyayı okumanız ya da siz okurken birinin onu
silmesi olanaksızdır. Bu yüzden bu önbellek deposunu kullanmak tümüyle güvenlidir.
Bu depo ayrıca, önbellek temizlendiğinde ya da hâlâ „soğuk“ olduğunda (yani henüz oluşturulmadığında) CPU kullanımında aşırı bir sıçramayı önleyen önemli, yerleşik bir özellik içerir. Buna cache stampede önleme denir. Bu durum, birden çok eşzamanlı istek aynı önbellek öğesini (örneğin pahalı bir SQL sorgusunun sonucunu) aynı anda istediğinde ortaya çıkar. Öğe o an önbellekte değilse, tüm bu süreçler aynı pahalı işlemi (SQL sorgusu gibi) yürütmeye başlayabilir. Bu, sunucu yükünü katlar ve hiçbir iş parçacığının zaman sınırı içinde yanıt verememesi, önbelleğin oluşmaması ve uygulamanın çökmesi bile olabilir. Neyse ki Nette'in önbelleği bunu ele alır: aynı öğe için birden çok eşzamanlı istek yapıldığında yalnızca ilk iş parçacığı onu üretir. Diğer iş parçacıkları bekler ve sonra ilkinin ürettiği sonucu kullanır.
FileStorage oluşturma örneği:
// depo, diskteki '/path/to/temp' dizini olacak
$storage = new Nette\Caching\Storages\FileStorage('/path/to/temp');
MemcachedStorage
Memcached sunucusu, yüksek başarımlı, dağıtık bir bellek nesnesi önbellekleme
sistemidir. Nette'deki adaptörü Nette\Caching\Storages\MemcachedStorage sınıfıdır. Yapılandırmada sunucunun
IP adresini ve standart 11211'den farklıysa portunu belirtin.
memcached PHP uzantısını gerektirir.
services:
cache.storage: Nette\Caching\Storages\MemcachedStorage('10.0.0.5')
MemoryStorage
Nette\Caching\Storages\MemoryStorage, veriyi bir PHP dizisinin içinde tutan bir depodur. Dolayısıyla istek
bittiğinde veri yiter.
SQLiteStorage
SQLite veritabanı, Nette\Caching\Storages\SQLiteStorage adaptörüyle birlikte, veriyi diskteki tek bir dosyada
önbelleğe almanın bir yolunu sunar. Yapılandırma, bu veritabanı dosyasının yolunu belirtir.
pdo ve pdo_sqlite PHP uzantılarını gerektirir.
services:
cache.storage: Nette\Caching\Storages\SQLiteStorage('%tempDir%/cache.db')
DevNullStorage
Özel bir depo gerçekleştirimi, aslında hiçbir veri saklamayan Nette\Caching\Storages\DevNullStorage
sınıfıdır. Bu yüzden önbelleklemenin etkilerini ortadan kaldırmak istediğinizde sınama amaçları için uygundur.
Kodda Önbelleği Kullanma
Kodunuzda önbellekleme kullanırken iki ana yaklaşım vardır. İlki, depo nesnesini bağımlılık enjeksiyonuyla elde etmek ve sonra Cache
nesnesini kendiniz oluşturmaktır:
use Nette;
class ClassOne
{
private Nette\Caching\Cache $cache;
public function __construct(Nette\Caching\Storage $storage)
{
$this->cache = new Nette\Caching\Cache($storage, 'my-namespace');
}
}
İkinci seçenek, doğrudan Cache nesnesini istemektir:
class ClassTwo
{
public function __construct(
private Nette\Caching\Cache $cache,
) {
}
}
Cache nesnesi o zaman yapılandırmada, örneğin şöyle tanımlanmalıdır:
services:
- ClassTwo( Nette\Caching\Cache(namespace: 'my-namespace') )
Journal
Nette, etiket ve öncelik bilgisini journal denen bir yerde saklar. Bunun için varsayılan olarak journal.s3db
dosyası aracılığıyla SQLite kullanılır ve pdo ile pdo_sqlite PHP uzantıları
gereklidir.
Journal gerçekleştirimini yapılandırmada değiştirebilirsiniz:
services:
cache.journal: MyJournal
DI Servisleri
DI container'a şu servisler eklenir:
| Ad | Tip | Açıklama |
|---|---|---|
cache.journal |
Nette\Caching\Storages\Journal | Önbellek journal deposu |
cache.storage |
Nette\Caching\Storage | Birincil önbellek deposu |
Önbelleği Kapatma
Uygulamanızda önbelleklemeyi kapatmanın bir yolu, depo arka ucunu DevNullStorage olarak ayarlamaktır:
services:
cache.storage: Nette\Caching\Storages\DevNullStorage
Bu ayar, Latte şablonlarının ya da DI container'ın önbelleklenmesini etkilemez; çünkü bu kütüphaneler
nette/caching servislerini kullanmaz ve önbelleklerini bağımsız yönetir. Üstelik onların önbelleklerinin
geliştirme kipinde genellikle kapatılması
gerekmez.
Daha yeni bir sürüme yükseltiyorsanız yükseltme sayfasına bakın.