diff --git a/backend/alembic/versions/0028_fk_indizes.py b/backend/alembic/versions/0028_fk_indizes.py new file mode 100644 index 0000000..1c25e27 --- /dev/null +++ b/backend/alembic/versions/0028_fk_indizes.py @@ -0,0 +1,120 @@ +"""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}")