- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
7.4 KiB
7.4 KiB
Hydrator Eager Loading System
Das Eager Loading System löst das N+1 Query Problem durch intelligente Batch-Queries und bietet massive Performance-Verbesserungen für Relations-intensive Anwendungen.
Problem: N+1 Query Problem
Vorher (Lazy Loading)
// 1 Query für Users
$users = $repository->findAll(); // SELECT * FROM users
// N Queries für Relations (1 pro User)
foreach($users as $user) {
$user->posts; // SELECT * FROM posts WHERE user_id = ?
$user->profile; // SELECT * FROM profiles WHERE user_id = ?
}
// Total: 1 + (N × 2) = 201 Queries für 100 Users!
Nachher (Eager Loading)
// 3 Queries total durch Batch-Loading
$users = $hydrator->hydrateManyWithRelations($metadata, $userData, ['posts', 'profile']);
// 1. SELECT * FROM users
// 2. SELECT * FROM posts WHERE user_id IN (1,2,3,...,100)
// 3. SELECT * FROM profiles WHERE user_id IN (1,2,3,...,100)
// Total: 3 Queries für 100 Users = 98.5% Reduktion!
Features
✅ Selective Eager Loading
// Lade nur spezifizierte Relations
$user = $hydrator->hydrateWithRelations($metadata, $data, ['posts', 'comments']);
// Ohne Relations = normale Hydration
$user = $hydrator->hydrateWithRelations($metadata, $data, []);
✅ Batch Processing für Collections
// N+1 Problem gelöst für Collections
$users = $hydrator->hydrateManyWithRelations($metadata, $dataRows, ['posts', 'profile']);
// Intelligente Gruppierung nach Relation-Types
// - belongsTo: IN-Queries für Foreign Keys
// - hasMany: Gruppierung nach Local Keys
// - one-to-one: Eindeutige Zuordnung
✅ Relation-Type Support
- belongsTo: Foreign Key Lookups mit IN-Queries
- hasMany: Reverse Foreign Key Lookups mit Gruppierung
- one-to-one: Eindeutige Relations-Zuordnung
API Usage
Single Entity mit Relations
$user = $hydrator->hydrateWithRelations(
$userMetadata,
$userData,
['posts', 'profile', 'roles']
);
// Relations sind sofort verfügbar, keine zusätzlichen Queries
echo count($user->posts); // Kein Query
echo $user->profile->bio; // Kein Query
Multiple Entities (Batch Loading)
$users = $hydrator->hydrateManyWithRelations(
$userMetadata,
$userDataRows,
['posts', 'comments', 'profile']
);
// Alle Relations wurden mit nur 4 Queries geladen:
// 1x Users, 1x Posts, 1x Comments, 1x Profiles
Performance-kritische Scenarios
// Blog-System: Posts mit Comments, Tags, Categories
$posts = $hydrator->hydrateManyWithRelations(
$postMetadata,
$postDataRows,
['comments', 'tags', 'category', 'author']
);
// Ohne Eager Loading: 1 + (50 × 4) = 201 Queries
// Mit Eager Loading: 5 Queries = 97.5% Reduktion
Performance Benchmarks
Real-World Scenarios
| Scenario | Entities | Relations | Lazy Queries | Eager Queries | Reduction |
|---|---|---|---|---|---|
| User Dashboard | 50 users | posts, profile | 101 | 3 | 97.0% |
| Blog Listing | 20 posts | comments, tags, author | 81 | 4 | 95.1% |
| E-Commerce | 100 products | category, reviews, images | 301 | 4 | 98.7% |
| Social Feed | 30 posts | author, comments, likes | 91 | 4 | 95.6% |
Database Load Reduction
// Blog System Beispiel (50 Posts)
$lazyQueries = 1 + (50 × 3); // 151 Queries
$eagerQueries = 4; // 4 Queries
$loadReduction = 151 / 4; // 37.75x weniger DB-Load!
Implementation Details
Batch Loading Algorithm
- Entity Creation: Erstelle alle Entities ohne Relations
- Key Collection: Sammle alle IDs/Foreign Keys für Batch-Queries
- Batch Queries: Ein Query pro Relation-Type mit IN-Clauses
- Grouping: Gruppiere Related Entities nach Keys
- Assignment: Weise Relations den entsprechenden Entities zu
BelongsTo Relations (Foreign Key Lookup)
// Sammle alle Foreign Keys
$foreignKeys = array_unique(array_column($dataRows, 'category_id'));
// Ein Batch-Query für alle Categories
$categories = $entityLoader->findBy(Category::class, ['id' => $foreignKeys]);
// Gruppiere nach ID für schnelle Zuordnung
$categoriesById = array_column($categories, null, 'id');
HasMany Relations (Reverse Lookup)
// Sammle alle Entity IDs
$userIds = array_column($userDataRows, 'id');
// Ein Batch-Query für alle Posts
$posts = $entityLoader->findBy(Post::class, ['user_id' => $userIds]);
// Gruppiere Posts nach user_id
$postsByUserId = [];
foreach($posts as $post) {
$postsByUserId[$post->user_id][] = $post;
}
One-to-One Relations
// Ähnlich wie hasMany, aber nur eine Relation pro Entity
$profiles = $entityLoader->findBy(Profile::class, ['user_id' => $userIds]);
$profileByUserId = array_column($profiles, null, 'user_id');
Error Handling & Edge Cases
Missing Relations
// Entities ohne Relations erhalten Default-Werte
$userPosts = $postsByUserId[$userId] ?? []; // Empty array für hasMany
$userProfile = $profilesByUserId[$userId] ?? null; // null für belongsTo/one-to-one
Empty Data Sets
$result = $hydrator->hydrateManyWithRelations($metadata, [], ['posts']);
// Returns: [] (empty array, keine Fehler)
Invalid Relations
// Nicht-existierende oder Nicht-Relations werden übersprungen
$user = $hydrator->hydrateWithRelations($metadata, $data, ['invalid_relation']);
// Kein Fehler, Relation wird ignoriert
Best Practices
1. Selective Loading
// ✅ Gut: Nur benötigte Relations laden
$posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments']);
// ❌ Schlecht: Alle Relations laden
$posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments', 'tags', 'category', 'ratings']);
2. Batch Processing priorisieren
// ✅ Gut: Batch Loading für Collections
$users = $hydrator->hydrateManyWithRelations($metadata, $dataRows, ['posts']);
// ❌ Schlecht: Einzelne Hydration in Loop
foreach($dataRows as $data) {
$users[] = $hydrator->hydrateWithRelations($metadata, $data, ['posts']);
}
3. Relation Depth begrenzen
// ✅ Gut: Direkte Relations
$posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments']);
// ⚠️ Consideration: Nested Relations
// (Aktuell nicht unterstützt, würde zusätzliche Implementierung benötigen)
$posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author.profile', 'comments.author']);
Benefits Summary
- Performance: 95-98% Reduktion der Database-Queries
- Scalability: Lineare statt exponentieller Query-Wachstum
- Resource Efficiency: Drastisch reduzierte DB-Connection Usage
- User Experience: Faster page loads and API responses
- Server Stability: Reduzierter Memory- und CPU-Verbrauch
- Database Health: Weniger Lock-Contention und Connection-Pool-Pressure
Migration Path
Bestehende Code-Base
// Vorher: Lazy Loading mit N+1 Problem
$users = $repository->findAll();
foreach($users as $user) {
// Implicit queries triggered
$user->posts;
$user->profile;
}
// Nachher: Explicit Eager Loading
$userData = $repository->findAllAsData(); // Raw data query
$users = $hydrator->hydrateManyWithRelations($metadata, $userData, ['posts', 'profile']);
Performance Monitoring
- Monitor Query Count vor/nach Migration
- Database Profiling für Query-Performance
- Application Response Time Tracking
- Memory Usage Monitoring bei Large Collections