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
This commit is contained in:
243
backups/docs-backup-20250731125004/database/eager-loading.md
Normal file
243
backups/docs-backup-20250731125004/database/eager-loading.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user