Files
MABEA/backend/alembic/versions/0028_fk_indizes.py
T
patrickandClaude Sonnet 5 17cac8543f
CI / backend-tests (push) Successful in 2m15s
CI / frontend-build (push) Successful in 27s
perf(db): 80 fehlende Indizes auf Fremdschlüssel-Spalten ergänzt
Postgres legt für FK-Spalten selbst KEINEN Index an (nur für die
referenzierte PK-Seite) - grep über alle Models ergab 0 explizite Indizes
im gesamten Schema trotz 86 Fremdschlüsseln. Ohne Index braucht jede
Lookup-Query von der "viele"-Seite (z.B. "alle Fehlbestände zu diesem
Objekt", "alle Kontrollen zu diesem Benutzer") einen Sequential Scan -
bei aktueller Datenmenge unauffällig, würde mit wachsendem Bestand aber
zum Problem.

5 Spalten ausgelassen, weil bereits über eine UniqueConstraint als
führende Spalte indiziert (Postgres nutzt den führenden Teil eines
zusammengesetzten Unique-Index für Einzelspalten-Lookups genauso):
objektposition.objekt_id, bestand.lagerplatz_id, beladungsvorlage.
objekttyp_id, vorlagenposition.vorlage_id, benutzer_qualifikation.
benutzer_id. dokument.vorgaenger_id hatte bereits einen Index (Migration
0027).

Fund beim Abgleich gegen die database-optimization-Checkliste aus
ai-coding-starter-kit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
2026-09-08 11:31:49 +02:00

121 lines
4.7 KiB
Python

"""Fehlende Indizes auf Fremdschlüssel-Spalten (Postgres legt für FK-Spalten
selbst KEINEN Index an, nur für die referenzierte PK-Seite - ohne expliziten
Index braucht jede Lookup-Query von der "viele"-Seite (z.B. "alle Positionen
zu diesem Objekt", "alle Fehlbestände zu diesem Material") einen Sequential
Scan). Gefunden beim Abgleich gegen ai-coding-starter-kit-Checkliste
(docs/production/database-optimization.md) - 0 explizite Indizes im
gesamten Modellbestand.
Spalten, die bereits über eine UniqueConstraint als führende Spalte indiziert
sind, werden hier bewusst ausgelassen (Postgres nutzt den führenden Teil
eines zusammengesetzten Unique-Index genauso für Einzelspalten-Lookups):
objektposition.objekt_id, bestand.lagerplatz_id, beladungsvorlage.objekttyp_id,
vorlagenposition.vorlage_id, benutzer_qualifikation.benutzer_id.
dokument.vorgaenger_id hat bereits einen Index aus Migration 0027.
Revision ID: 0028_fk_indizes
Revises: 0027_dokument_versionierung
Create Date: 2026-09-08
"""
from typing import Sequence, Union
from alembic import op
revision: str = "0028_fk_indizes"
down_revision: Union[str, None] = "0027_dokument_versionierung"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
_INDIZES = [
("ausgabe", "material_id"),
("ausgabe", "geraet_instanz_id"),
("ausgabe", "empfaenger_id"),
("ausgabe", "ausgegeben_von"),
("benutzer", "einheit_id"),
("benutzer_rolle", "benutzer_id"),
("dokument", "hochgeladen_von"),
("fahrzeugdetails", "objekt_id"),
("fehlbestand", "erzeugt_von_server_id"),
("fehlbestand", "objekt_id"),
("fehlbestand", "material_id"),
("fehlbestand", "standort_id"),
("fehlbestand", "festgestellt_von"),
("fehlbestand", "kontrolle_id"),
("fehlbestand", "verantwortlicher_id"),
("geraet_instanz", "objektposition_id"),
("historie", "erzeugt_von_server_id"),
("historie", "benutzer_id"),
("kontrolle", "erzeugt_von_server_id"),
("kontrolle", "objekt_id"),
("kontrolle", "benutzer_id"),
("kontrollposition", "kontrolle_id"),
("kontrollposition", "material_id"),
("lagerort", "parent_id"),
("lagerplatz", "lagerort_id"),
("bestand", "material_id"),
("materialbewegung", "material_id"),
("materialbewegung", "von_lagerplatz_id"),
("materialbewegung", "nach_lagerplatz_id"),
("materialbewegung", "benutzer_id"),
("lagerbewegung", "objekt_id"),
("lagerbewegung", "von_standort_id"),
("lagerbewegung", "nach_standort_id"),
("lagerbewegung", "benutzer_id"),
("mangel", "objekt_id"),
("mangel", "gemeldet_von"),
("mangel", "erledigt_von"),
("mindermengen_genehmigung", "fehlbestand_id"),
("mindermengen_genehmigung", "genehmigt_von"),
("mindermengen_genehmigung", "ausloesende_kontrolle_id"),
("mindermengen_genehmigung", "beendende_kontrolle_id"),
("nachfuellung", "fehlbestand_id"),
("nachfuellung", "objekt_id"),
("nachfuellung", "material_id"),
("nachfuellung", "benutzer_id"),
("objekt", "objekttyp_id"),
("objekt", "vorlage_id"),
("objekt", "standort_id"),
("objekt", "zustaendiger_server_id"),
("objekt", "fahrzeug_id"),
("objekt", "einheit_id"),
("objektposition", "material_id"),
("rolle_berechtigung", "rolle_id"),
("rolle_berechtigung", "berechtigung_id"),
("benutzer_rolle_zuordnung", "benutzer_id"),
("benutzer_rolle_zuordnung", "rolle_id"),
("einheit", "uebergeordnete_einheit_id"),
("einheit", "standort_id"),
("benutzer_qualifikation", "qualifikationstyp_id"),
("objekttyp_qualifikationsanforderung", "objekttyp_id"),
("objekttyp_qualifikationsanforderung", "qualifikationstyp_id"),
("kategorie", "bereich_id"),
("kategorie", "ueberkategorie_id"),
("objekttyp", "bereich_id"),
("objekttyp", "kategorie_id"),
("fach", "objekttyp_id"),
("material", "kategorie_id"),
("vorlagenposition", "material_id"),
("wartungsplan", "objekttyp_id"),
("wartungsplan_position", "wartungsplan_id"),
("wartungsintervall", "wartungsplan_position_id"),
("wartungsauftrag", "objekt_id"),
("wartungsauftrag", "geraet_instanz_id"),
("wartungsauftrag", "wartungsplan_position_id"),
("wartungsauftrag", "durchgefuehrt_von"),
("zustaendigkeit", "benutzer_id"),
("zustaendigkeit", "standort_id"),
("zustaendigkeit", "objekt_id"),
("kontrollverantwortung", "objekt_id"),
("kontrollverantwortung", "benutzer_id"),
]
def upgrade() -> None:
for tabelle, spalte in _INDIZES:
op.execute(f"CREATE INDEX IF NOT EXISTS ix_{tabelle}_{spalte} ON {tabelle} ({spalte})")
def downgrade() -> None:
for tabelle, spalte in _INDIZES:
op.execute(f"DROP INDEX IF EXISTS ix_{tabelle}_{spalte}")