Files
michaelschiemer/backups/docs-backup-20250731125004/database/eager-loading.md
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

243 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
```php
// 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)
```php
// 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
```php
// 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
```php
// 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
```php
$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)
```php
$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
```php
// 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
```php
// 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
1. **Entity Creation**: Erstelle alle Entities ohne Relations
2. **Key Collection**: Sammle alle IDs/Foreign Keys für Batch-Queries
3. **Batch Queries**: Ein Query pro Relation-Type mit IN-Clauses
4. **Grouping**: Gruppiere Related Entities nach Keys
5. **Assignment**: Weise Relations den entsprechenden Entities zu
### BelongsTo Relations (Foreign Key Lookup)
```php
// 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)
```php
// 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
```php
// Ä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
```php
// 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
```php
$result = $hydrator->hydrateManyWithRelations($metadata, [], ['posts']);
// Returns: [] (empty array, keine Fehler)
```
### Invalid Relations
```php
// Nicht-existierende oder Nicht-Relations werden übersprungen
$user = $hydrator->hydrateWithRelations($metadata, $data, ['invalid_relation']);
// Kein Fehler, Relation wird ignoriert
```
## Best Practices
### 1. Selective Loading
```php
// ✅ 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
```php
// ✅ 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
```php
// ✅ 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
1. **Performance**: 95-98% Reduktion der Database-Queries
2. **Scalability**: Lineare statt exponentieller Query-Wachstum
3. **Resource Efficiency**: Drastisch reduzierte DB-Connection Usage
4. **User Experience**: Faster page loads and API responses
5. **Server Stability**: Reduzierter Memory- und CPU-Verbrauch
6. **Database Health**: Weniger Lock-Contention und Connection-Pool-Pressure
## Migration Path
### Bestehende Code-Base
```php
// 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