[Performance] N+1-Abfragen beim Laden eines Wochenplans #49

Closed
opened 2026-09-09 20:17:01 +00:00 by Aceless · 1 comment
Owner

Problem

Data/MealPlanRepository.LoadEntries laedt fuer jeden Eintrag das komplette Rezept:

foreach (var e in entries)
{
    ...
    if (entry.RecipeId.HasValue)
        entry.Recipe = await _recipeRepository.GetRecipeById(entry.RecipeId.Value);
}

GetRecipeById fuehrt sechs Statements aus (Rezept, Zutaten, Schritte, Tags, SuitableFor, Bilder).

Rechnung

Eintraege pro Woche Statements
7 ~44
21 ~128

Dazu kommt: Der Wochenplan liefert am Ende nur RecipeListItemDto, Zutaten und Zubereitungsschritte werden also geladen und dann verworfen.

Fix

  • Alle Rezept-Ids der Eintraege sammeln und in einem QueryMultiple mit WHERE recipe_id = ANY(@Ids) laden, danach im Speicher zuordnen (so wie es GetRecipesInSpace bereits macht).
  • Eine schlanke Lademethode fuer Listendarstellungen ergaenzen, die Zutaten und Schritte gar nicht erst abfragt.
  • Denselben Ansatz in AddEntry und UpdateEntry verwenden.
## Problem `Data/MealPlanRepository.LoadEntries` laedt fuer jeden Eintrag das komplette Rezept: ```csharp foreach (var e in entries) { ... if (entry.RecipeId.HasValue) entry.Recipe = await _recipeRepository.GetRecipeById(entry.RecipeId.Value); } ``` `GetRecipeById` fuehrt sechs Statements aus (Rezept, Zutaten, Schritte, Tags, SuitableFor, Bilder). ## Rechnung | Eintraege pro Woche | Statements | |---|---| | 7 | ~44 | | 21 | ~128 | Dazu kommt: Der Wochenplan liefert am Ende nur `RecipeListItemDto`, Zutaten und Zubereitungsschritte werden also geladen und dann verworfen. ## Fix - Alle Rezept-Ids der Eintraege sammeln und in **einem** `QueryMultiple` mit `WHERE recipe_id = ANY(@Ids)` laden, danach im Speicher zuordnen (so wie es `GetRecipesInSpace` bereits macht). - Eine schlanke Lademethode fuer Listendarstellungen ergaenzen, die Zutaten und Schritte gar nicht erst abfragt. - Denselben Ansatz in `AddEntry` und `UpdateEntry` verwenden.
Author
Owner

Erledigt.

LoadEntriesAsync sammelt die Rezept-Ids aller Einträge und lädt sie über die neue Methode GetRecipeSummariesAsync mit WHERE id = ANY(@Ids). Zutaten und Zubereitungsschritte werden dabei gar nicht erst geholt, der Wochenplan zeigt ohnehin nur RecipeListItemDto.

Gemessen mit log_statement = all auf einer Testdatenbank, Wochenplan mit sieben Einträgen und sieben verschiedenen Rezepten:

Statements pro Abruf
vorher 45
jetzt 7

Die sieben sind Rollenprüfung, Wochenplan, Einträge und je eine Abfrage für Rezepte, Tags, Mealtypes und Bilder. Die Zahl bleibt konstant, unabhängig von der Anzahl der Einträge. Bei einer vollen Woche mit 21 Einträgen waren es vorher 129.

GetRecipesInSpace nutzt jetzt Joins statt der dreifach wiederholten Unterabfrage.


Branch backend/issue-fixes, Commits 77327fc, 3957404.

Erledigt. `LoadEntriesAsync` sammelt die Rezept-Ids aller Einträge und lädt sie über die neue Methode `GetRecipeSummariesAsync` mit `WHERE id = ANY(@Ids)`. Zutaten und Zubereitungsschritte werden dabei gar nicht erst geholt, der Wochenplan zeigt ohnehin nur `RecipeListItemDto`. **Gemessen** mit `log_statement = all` auf einer Testdatenbank, Wochenplan mit sieben Einträgen und sieben verschiedenen Rezepten: | | Statements pro Abruf | |---|---| | vorher | 45 | | jetzt | 7 | Die sieben sind Rollenprüfung, Wochenplan, Einträge und je eine Abfrage für Rezepte, Tags, Mealtypes und Bilder. Die Zahl bleibt konstant, unabhängig von der Anzahl der Einträge. Bei einer vollen Woche mit 21 Einträgen waren es vorher 129. `GetRecipesInSpace` nutzt jetzt Joins statt der dreifach wiederholten Unterabfrage. --- Branch `backend/issue-fixes`, Commits 77327fc, 3957404.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Aceless/RecipePlanner#49
No description provided.