Performans: N+1 ve DataLoader
Hızınızı korumak için alan çözümlemelerini toplu olarak işleyin ve önbelleğe alın.
Performans: N+1 ve DataLoader, CoddyKit'te ücretsiz bir PHP Academy dersidir. Bu, 4 dersinin 4. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, PHP Academy öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. PHP Academy kursu toplamda 4 dersten oluşur.
Sessiz Katil: N+1
GraphQL'ın en büyük performans tuzağı N+1 sorgu sorunudur. Her bir çözümleyici tek başına masum göründüğü için sessizdir — ancak bir listeyi öğe başına çalışan bir alanla iç içe kullandığınızda, liste için bir sorgu ve her öğe için bir sorgu çalıştırırsınız. 100 öğede bu, 101 gidiş-dönüş demektir. Bu derste bunları DataLoader ile birkaç toplu sorguya nasıl indirgeyeceğiniz gösterilmektedir.
N+1'i Somut Olarak Görmek
Şunu düşünün: { posts { author { name } } }. posts çözümleyicisi bir sorgu çalıştırır. Ardından her gönderi için author çözümleyicisi kendi sorgusunu çalıştırır. Aşağıdaki saf kod, maliyeti açıkça ortaya koyar.
<?php
// 1 query for posts...
$posts = [['id'=>1,'author_id'=>7],['id'=>2,'author_id'=>7],['id'=>3,'author_id'=>9]];
$queries = 1;
foreach ($posts as $post) {
// ...then 1 query PER post to fetch its author
$queries++;
// SELECT * FROM users WHERE id = $post['author_id']
}
echo "Total DB queries: {$queries}\n"; // Total DB queries: 4
İçgörü: Düzeye Göre Toplu İşlem
GraphQL, düzey düzey çözümleme yapar. Gönderi listesindeki tüm author çözümleyicileri aynı yürütme turunda çalışır. Her yazar aramasını erteleyebilir, istenen kimlikleri toplayabilir ve ardından tek bir WHERE id IN (...) sorgusu çalıştırabilirsek N sorguyu bire indirgeriz. Bu erteleme işlevini tam olarak GraphQL\Deferred sağlar.
<?php
$ids = [7, 7, 9];
$unique = array_values(array_unique($ids));
// One query instead of three:
echo 'SELECT * FROM users WHERE id IN (' . implode(',', $unique) . ")\n";
// SELECT * FROM users WHERE id IN (7,9)
En Basit Tampon/Yükleyici
DataLoader'ın özü şöyledir: kimlikleri biriktiren, bunları bir toplu işlem işlevi aracılığıyla tek seferde yükleyen ve sonuçları bir önbellekten sunan bir tampon. Aynı anahtar iki kez istenirse bir kez yüklenir — otomatik tekilleştirme.
<?php
class UserLoader {
private array $queue = [];
private array $cache = [];
public function __construct(private \Closure $batchFn) {}
public function add(int $id): void { $this->queue[$id] = true; }
public function loadOnce(): void {
$missing = array_diff(array_keys($this->queue), array_keys($this->cache));
if ($missing) {
foreach (($this->batchFn)(array_values($missing)) as $id => $row) {
$this->cache[$id] = $row;
}
}
$this->queue = [];
}
public function get(int $id): mixed { return $this->cache[$id] ?? null; }
}
Bunu Bir Çözümleyiciye Bağlamak
author çözümleyicisi kimliği kuyruğa alır ve bir Deferred döndürür. graphql-php tüm ertelenmiş işlemleri geçerli düzeyden sonra çalıştırır; bu nedenle kapatma işlevi yürütüldüğünde listenin tamamındaki her yazar kimliği kuyruğa alınmış olur. Tek bir loadOnce() çağrısı, tek bir toplu sorguyu tetikler.
<?php
use GraphQL\Deferred;
$authorField = [
'type' => $userType,
'resolve' => function ($post, $args, $context) {
/** @var UserLoader $loader */
$loader = $context['userLoader'];
$loader->add($post['author_id']);
return new Deferred(function () use ($loader, $post) {
$loader->loadOnce(); // batches across all posts
return $loader->get($post['author_id']);
});
},
];
overblog/dataloader Kitaplığını Kullanmak
Bunu nadiren kendiniz yazmanız gerekir. overblog/dataloader-php, Facebook'un DataLoader için geliştirdiği yerleşik uyarlamadır. Ona, bir anahtar dizisi alan ve aynı sıradaki değerlerden oluşan bir vaat döndürmesi gereken bir toplu işlem işlevi verirsiniz. Önbelleğe alma, tekilleştirme ve vaatlerin çözümlenmesi işlemlerini kendisi yönetir.
composer require overblog/dataloader-phpDataLoader Oluşturmak
Toplu işlem işlevinin sözleşmesi katıdır: [k1, k2, k3] verildiğinde, konumsal olarak [v1, v2, v3] biçiminde çözümlenmelidir. Veritabanı satırlarınızı anahtara göre indeksleyin ve eksik anahtarların null olması için girdi sırasını yeniden eşleyin.
<?php
use Overblog\DataLoader\DataLoader;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use Overblog\PromiseAdapter\Adapter\WebonyxGraphQLSyncPromiseAdapter;
$adapter = new WebonyxGraphQLSyncPromiseAdapter(new SyncPromiseAdapter());
$userLoader = new DataLoader(function (array $ids) use ($adapter, $db) {
$rows = $db->usersByIds($ids); // SELECT ... WHERE id IN (...)
$byId = [];
foreach ($rows as $r) { $byId[$r['id']] = $r; }
// MUST return values in the SAME ORDER as $ids
$ordered = array_map(fn($id) => $byId[$id] ?? null, $ids);
return $adapter->createFulfilled($ordered);
}, $adapter);
Yükleyici Üzerinden Çözümleme
Çözümleyicide yalnızca bir vaat döndüren $loader->load($id) çağrısını yaparsınız. graphql-php, uyarlayıcı aracılığıyla bunları toplar ve turun sonunda toplu işlemi otomatik olarak tetikler. Manuel tamponlama gerekmez.
<?php
$authorField = [
'type' => $userType,
'resolve' => fn($post, $args, $context) =>
$context['userLoader']->load($post['author_id']),
];
İstek Başına Ömür Kritik Öneme Sahiptir
DataLoaders anahtara göre önbelleğe aldığı için her istek için yeniden oluşturulmalıdır. İstekler arasında paylaşılan bir yükleyici güncelliğini yitirmiş veriler sunar ve belleğin sızmasına neden olur. Yükleyicileri istek $context'ini oluştururken kurun ve istek sona erdiğinde elden çıkarın.
<?php
// Per request: brand new loaders, attached to context
function buildContext($db, $currentUser): array {
return [
'db' => $db,
'user' => $currentUser,
'userLoader' => makeUserLoader($db), // fresh, not a singleton
'postLoader' => makePostLoader($db),
];
}
Diğer Performans Önlemleri
DataLoader, N+1 okumalarını giderir; ancak kötü niyetli veya dikkatsiz bir sorgu yine de size zarar verebilir. Şu katmanları ekleyin:
- Sorgu derinliği sınırlaması —
QueryDepthkuralı, patolojik biçimde iç içe sorguları reddeder. - Sorgu karmaşıklığı —
QueryComplexity, her alan için bir maliyet bütçesi belirler. - Kalıcı sorgular — yalnızca bilinen bir izin listesindeki işlemlere izin verin.
- Sayfalama — sınırı olmayan listeleri hiçbir zaman çözümlemeyin; imleç bağlantılarını kullanın.
<?php
use GraphQL\Validator\Rules\QueryDepth;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\DocumentValidator;
DocumentValidator::addRule(new QueryDepth(10));
DocumentValidator::addRule(new QueryComplexity(200));
Kazanımı Ölçmek
İyileşmeyi her zaman nicel olarak değerlendirin. Bir sınamada sorguları sayması için veritabanı katmanınızı sarın, ardından toplu işlem kullanan sürümün liste boyutundan bağımsız olarak sınırlı sayıda sorgu çalıştırdığını doğrulayın. Bu, birinin saf iç içe çözümleyici ekleyerek N+1 sorununu sessizce yeniden ortaya çıkarması gibi gerilemelere karşı koruma sağlar.
<?php
class CountingDb {
public int $queries = 0;
public function usersByIds(array $ids): array {
$this->queries++; // one batched call
return array_map(fn($id) => ['id' => $id], $ids);
}
}
$db = new CountingDb();
$db->usersByIds([7, 9, 11, 13]); // 4 authors
echo "Queries for 4 authors: {$db->queries}\n"; // Queries for 4 authors: 1
Hızlı Kontrol
DataLoaders neden istek başına oluşturulmalıdır?
Özet
GraphQL'ın en kötü performans tuzağını ortadan kaldırdınız:
- İç içe liste alanları N+1 sorununa yol açar: her öğe için bir sorgu.
- graphql-php çözümlemeyi düzey düzey yapar; bu nedenle erteleme, tüm anahtarları tek bir
IN (...)sorgusunda toplamanızı sağlar. GraphQL\Deferredtemel yapı taşıdır;overblog/dataloader-phptoplu işlemeyi, anahtar başına önbelleğe almayı ve tekilleştirmeyi bir araya getirir.- Toplu işlem işlevi, değerleri girdi anahtarlarıyla aynı sırada döndürmelidir.
- Yükleyiciler istek başınadır; ek önlemler olarak derinlik ve karmaşıklık sınırları ile sayfalama ekleyin.
Sıkça Sorulan Sorular
“Performans: N+1 ve DataLoader” dersi ücretsiz mi?
Evet — “Performans: N+1 ve DataLoader” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve PHP Academy kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. PHP Academy kursu toplamda 4 dersten oluşur.
“Performans: N+1 ve DataLoader” dersinde ne öğreneceğim?
Hızınızı korumak için alan çözümlemelerini toplu olarak işleyin ve önbelleğe alın. PHP Academy ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.
PHP Academy öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te PHP Academy, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 4. dersidir.
“Performans: N+1 ve DataLoader” dersi ne kadar sürer?
Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.
Bu PHP Academy dersinde kod yazıp çalıştırabilir miyim?
Evet. Her PHP Academy dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.
Bu kursun tüm dersleri
- GraphQL ve REST Karşılaştırması
- graphql-php ile Şema Oluşturma
- Çözücüler, Mutasyonlar ve Abonelikler
- Performans: N+1 ve DataLoader