Postgres Best Practices im Zeitalter der KI-Agenten! Nutzen Sie die von Supabase veröffentlichten „Agent Skills"
Inhaltsverzeichnis
Dieser Artikel ist eine erläuternde Zusammenfassung der wichtigsten Punkte, basierend auf dem offiziellen Supabase-Blog und der öffentlichen Dokumentation von supabase/agent-skills. Die Befehls- und SQL-Beispiele enthalten umstrukturierte Muster zum besseren Verständnis.
Wir leben in einer Zeit, in der KI-Coding-Agenten Code für uns schreiben. Es ist wirklich hilfreich, dass Tools wie Cursor, Claude Code oder GitHub Copilot uns bei Datenbankabfragen und sogar beim Schema-Design unterstützen.
Es gibt jedoch eine große Herausforderung. KI-Agenten sind zwar gut darin, Code zu schreiben, aber das bedeutet nicht zwangsläufig, dass sie „korrekten" Code schreiben, der auf einem tiefen Verständnis des Zielsystems basiert. Besonders bei Datenbanken wie Postgres hört man oft, dass Abfragen entstehen, die zwar funktionieren, aber eine schlechte Performance aufweisen oder Sicherheitslücken im Design enthalten.
Aus diesem Grund hat Supabase die Initiative „Postgres Best Practices for AI Agents" ins Leben gerufen. In diesem Artikel stellen wir die Inhalte vor und schauen uns konkrete Methoden an, um Postgres sicherer und schneller zu nutzen.
Was sind die „Agent Skills" von Supabase?
Die von Supabase veröffentlichten Agent Skills sind eine Wissensdatenbank, um KI-Coding-Agenten die Best Practices für Postgres beizubringen.
Das System stellt eine Gruppe von Dokumenten bereit, die in einem für KI-Agenten optimierten Format vorliegen. 31 Referenzdateien sind in 8 Kategorien unterteilt. Wenn ein Agent Abfragen schreibt oder ein Schema entwirft, kann er diese Regeln referenzieren, um korrekten Code zu generieren (die Anzahl der Referenzen kann sich durch zukünftige Updates ändern).
Die Installation ist einfach und kann mit einem einzigen Befehl durchgeführt werden:
npx skills add https://github.com/supabase/agent-skills --skill supabase-postgres-best-practices
Schauen wir uns nun die wichtigsten Punkte aus den veröffentlichten Richtlinien nach ihrer Priorität an.
Die 8 Kategorien und ihre Priorität
In den Agent Skills sind die Regeln in die folgenden 8 Kategorien unterteilt, sortiert nach ihrer Auswirkung.
| Priorität | Kategorie | Auswirkung |
|---|---|---|
| 1 | Abfrage-Performance | KRITISCH |
| 2 | Verbindungsmanagement | KRITISCH |
| 3 | Sicherheit & RLS | KRITISCH |
| 4 | Schema-Design | HOCH |
| 5 | Nebenläufigkeit & Sperren | MITTEL-HOCH |
| 6 | Datenzugriffsmuster | MITTEL |
| 7 | Monitoring & Diagnose | NIEDRIG-MITTEL |
| 8 | Fortgeschrittene Funktionen | NIEDRIG |
Die ersten drei Kategorien sind als „KRITISCH" eingestuft. Werden diese vernachlässigt, kann dies zu erheblichen Performance-Einbußen oder Sicherheitsrisiken führen. Gehen wir tiefer auf die jeweiligen Inhalte ein.
KRITISCH: Optimierung der Abfrage-Performance
Fehlende Indizes sind der größte Feind
Hinweis: Die SQL-Beispiele in diesem Artikel sind vereinfachte Muster, um die Konzepte der Originalquelle zu erläutern.
Das häufigste Performance-Problem in Postgres sind fehlende Indizes. Wenn Spalten, die in WHERE-Klauseln oder JOINs verwendet werden, keinen Index haben, tritt ein „Full Table Scan" auf, bei dem die gesamte Tabelle durchsucht wird. Je größer die Datenmenge, desto schwerwiegender sind die Auswirkungen, was zu einer Verlangsamung um das 100- bis 1000-fache führen kann.
Betrachten wir zunächst ein vereinfachtes Beispiel für ein typisches Anti-Pattern.
-- Anti-Pattern: Filtern ohne Index
select * from orders where customer_id = 123;
-- → Die gesamte Tabelle wird gescannt
Die Lösung ist einfach: Erstellen Sie einen Index für die Spalten, die in Filterbedingungen verwendet werden.
-- Best Practice: Index für Spalten in der WHERE-Klausel erstellen
create index orders_customer_id_idx on orders (customer_id);
Dasselbe gilt für JOINs: Erstellen Sie Indizes auf der Fremdschlüsselseite.
-- Index auch für Spalten in JOINs erstellen
create index orders_customer_id_idx on orders (customer_id);
select c.name, o.total
from customers c
join orders o on o.customer_id = c.id;
Effizienzsteigerung durch partielle Indizes
Ein Index, der nicht alle Zeilen einer Tabelle, sondern nur Zeilen abdeckt, die eine bestimmte Bedingung erfüllen, wird als „partieller Index" (Partial Index) bezeichnet. Dadurch kann die Indexgröße auf ein 5-tel bis 20-stel reduziert werden, was sowohl Schreibvorgänge als auch Abfragen beschleunigt.
-- Anti-Pattern: Index über alle Zeilen
create index users_email_idx on users (email);
-- Best Practice: Partieller Index nur für aktive Benutzer
create index users_email_active_idx on users (email)
where deleted_at is null;
Partielle Indizes sind besonders effektiv bei Tabellen mit vielen logisch gelöschten Datensätzen (Soft Delete) oder Daten mit bestimmten Statuswerten. Vergessen Sie nicht, die WHERE-Klausel der Abfrage mit der Bedingung des Index abzugleichen.
KRITISCH: Verbindungsmanagement
Postgres-Verbindungen sind kostspielig
Jede Postgres-Verbindung verbraucht etwa 1 bis 3 MB Arbeitsspeicher. Bei 500 gleichzeitigen Benutzern wären allein dafür 500 Verbindungen erforderlich, was die Datenbank zum Absturz bringen könnte.
Die gängige Lösung für dieses Problem ist das Connection Pooling. Zwischen der Anwendung und der Datenbank wird ein Pooler (wie PgBouncer) platziert, der eine kleine Anzahl tatsächlicher Verbindungen mit vielen Clients teilt.
Formel für die Pool-Größe
Die optimale Pool-Größe lässt sich mit der folgenden Formel berechnen:
Pool-Größe = (Anzahl der CPU-Kerne × 2) + Anzahl der Festplatten-Spindeln
Bei einem System mit 4 Kernen beträgt die Pool-Größe beispielsweise etwa 10 Verbindungen, was ausreicht, um mehr als 500 gleichzeitige Benutzer zu bedienen.
Transaction Mode und Session Mode
Connection Pooler verfügen über zwei Modi:
- Transaction Mode (Transaktionsmodus): Gibt die Verbindung nach Abschluss der Transaktion an den Pool zurück. Ideal für temporäre Clients wie Serverless Functions oder Edge Functions.
- Session Mode (Sitzungsmodus): Hält die Verbindung über die gesamte Sitzung aufrecht. Erforderlich bei der Verwendung von Prepared Statements oder temporären Tabellen.
Wenn Sie Supabase verwenden, wird empfohlen, die Pool-Größe bei Projekten mit intensiver Nutzung der PostgREST-API auf etwa 40 % zu begrenzen und in anderen Fällen bis zu 80 % zuzuweisen.
KRITISCH: Sicherheit und Row Level Security (RLS)
Filterung nur auf der Anwendungsebene ist gefährlich
In Multi-Tenant-Umgebungen ist ein Design, das „nur über Bedingungen in der App steuert", gefährlich. Ein einziger Bug oder Umgehungsweg reicht aus, um Zugriff auf Daten anderer Mandanten zu erhalten.
Hinweis: „Multi-Tenant" bezieht sich hier auf eine Konfiguration, bei der eine App von mehreren Kunden (Firmen/Teams) geteilt wird. „Andere Mandanten" sind Daten, die nicht dem eigenen Kunden gehören.
Row Level Security (RLS) ist ein Mechanismus, der die Zugriffskontrolle auf Datenbankebene erzwingt. Da Zugriffsrechte pro Zeile festgelegt werden können, bleiben die Daten geschützt, selbst wenn die Anwendung einen Bug enthält.
-- RLS aktivieren
alter table orders enable row level security;
-- RLS auch für den Tabellenbesitzer erzwingen
alter table orders force row level security;
-- Richtlinie erstellen, damit Benutzer nur ihre eigenen Daten sehen können
create policy "Users can only see their own orders"
on orders for select
using (user_id = (select auth.uid()));
Optimierung der RLS-Performance
RLS ist mächtig, kann aber bei falscher Implementierung die Performance erheblich beeinträchtigen. Besonders zu beachten sind die Aufrufkosten von Authentifizierungsfunktionen.
-- Anti-Pattern: auth.uid() wird für jede Zeile aufgerufen
create policy "bad_policy" on orders
for select using (auth.uid() = user_id);
-- Best Practice: Mit select umschließen, um Caching zu ermöglichen
create policy "good_policy" on orders
for select using ((select auth.uid()) = user_id);
Dieser kleine Unterschied kann bei großen Tabellen einen Geschwindigkeitsunterschied vom über 100-fachen ausmachen. Indem man auth.uid() in (select auth.uid()) einschließt, wird die Funktion nur einmal ausgeführt und das Ergebnis zwischengespeichert.
Zudem ist es wichtig, Indizes für Spalten hinzuzufügen, die in RLS-Richtlinien referenziert werden (wie user_id).
-- Index für Spalten in RLS hinzufügen
create index orders_user_id_idx on orders (user_id);
Vermeidung von RLS-Rekursion durch Security-Definer-Funktionen
Bei komplexen Berechtigungsprüfungen (z. B. Prüfung der Team-Mitgliedschaft) ist es stabiler, die Logik in eine security definer-Funktion auszulagern.
Wenn Tabellen, die innerhalb einer RLS-Richtlinie referenziert werden, ebenfalls RLS aktiviert haben, kann dies während der Richtlinienauswertung zu einer Kette weiterer Auswertungen führen, was die Abfrage verlangsamt oder zu unerwartetem Verhalten führt.
Durch die Bündelung der Prüfung in einer security definer-Funktion können diese rekursiven Auswertungen vermieden und die Richtlinie selbst einfach gehalten werden.
-- Funktion zur Prüfung der Team-Mitgliedschaft
create or replace function is_team_member(team_id bigint)
returns boolean
language sql
security definer
set search_path = ''
as $$
select exists (
select 1 from public.team_members
where team_members.team_id = $1
and team_members.user_id = (select auth.uid())
);
$$;
Es gibt jedoch einen wichtigen Punkt: security definer-Funktionen werden mit den „Rechten des Erstellers" ausgeführt und umgehen daher teilweise die normalen RLS-Prüfungen.
Daher besteht das Risiko, dass versehentlich weitreichende Rechte nach außen freigegeben werden, wenn sie in öffentlichen Schemas (Exposed Schemas) platziert werden, auf die direkt über die API zugegriffen werden kann.
Solche Funktionen sollten grundsätzlich in Schemas platziert werden, die nicht öffentlich zugänglich sind, sodass nur die erforderlichen Prozesse sicher aufgerufen werden können.
HOCH: Best Practices für das Schema-Design
Wahl der richtigen Datentypen
Die Wahl der Datentypen wirkt sich direkt auf die Speichereffizienz und die Abfrageleistung aus. Hier sind häufige Fehler und empfohlene Typen zusammengefasst.
| Verwendung | Anti-Pattern | Empfehlung | Grund |
|---|---|---|---|
| ID | int | bigint | int läuft bei 2,1 Mrd. Zeilen über |
| Text | varchar(255) | text | Kein Performance-Unterschied in Postgres |
| Datum/Zeit | timestamp | timestamptz | Speichert Zeitzoneninformationen |
| Boolean | text ('true'/'false') | boolean | 1 Byte vs. variable Länge |
| Beträge | text | numeric(10,2) | Ermöglicht präzise Berechnungen |
Besonders wenn int für ID-Spalten verwendet wird, tritt bei etwa 2,1 Milliarden Datensätzen ein Überlauf auf. Mit bigint sind bis zu 9 Trillionen Einträge möglich, was für zukünftige Erweiterungen sicher ist.
MITTEL-HOCH: Nebenläufigkeit und Sperren
Transaktionen kurz halten
Lang laufende Transaktionen halten Sperren aufrecht und blockieren andere Abfragen. Ein häufiger Fehler ist der Aufruf externer APIs innerhalb einer Transaktion.
-- Anti-Pattern: API-Aufruf innerhalb einer Transaktion
begin;
select * from orders where id = 1 for update; -- Sperre wird gesetzt
-- Hier externer API-Aufruf (dauert 2-5 Sek.)
update orders set status = 'paid' where id = 1;
commit; -- Sperre wird die ganze Zeit gehalten
-- Best Practice: API-Aufruf außerhalb der Transaktion
-- 1. Zuerst externen API-Aufruf (außerhalb der Transaktion)
-- 2. Nach Erhalt des Ergebnisses Transaktion starten
begin;
update orders set status = 'paid' where id = 1;
commit; -- Sperre wird nur für Millisekunden gehalten
Indem Nicht-Datenbank-Prozesse aus der Transaktion ausgelagert werden, ist eine Verbesserung des Durchsatzes um das 3- bis 5-fache möglich.
Es wird auch empfohlen, ein statement_timeout als Sicherheitsmaßnahme festzulegen.
-- Globale Einstellung
set statement_timeout = '30s';
-- Einstellung pro Sitzung
set local statement_timeout = '5s';
MITTEL: Datenzugriffsmuster
Behebung des N+1-Problems
Das N+1-Problem tritt auf, wenn für jedes Ergebnis einer ersten Abfrage in einer Schleife N zusätzliche Abfragen ausgeführt werden. Die Anzahl der Datenbank-Roundtrips erhöht sich dadurch um das 10- bis 100-fache.
-- Anti-Pattern: Abfrage einzeln in einer Schleife
-- 1. Mal: select * from users limit 100;
-- 2.-101. Mal: select * from orders where user_id = ?; (100 Wiederholungen)
-- → Insgesamt 101 Abfragen
-- Best Practice: Batch-Abruf
select * from orders where user_id = any($1::bigint[]);
-- → Alle IDs in einem Array übergeben, fertig in einer Abfrage
-- Oder JOIN verwenden
select u.id, u.name, o.*
from users u
left join orders o on o.user_id = u.id;
Entscheiden Sie sich entweder dafür, IDs auf der Anwendungsseite zu sammeln und mit any() gesammelt abzurufen, oder alles mit einem JOIN zu erledigen.
Monitoring: Probleme mit EXPLAIN ANALYZE identifizieren
Wenn Performance-Probleme auftreten, ist es wichtig, Entscheidungen auf der Grundlage tatsächlicher Daten und nicht auf Vermutungen zu treffen. Mit EXPLAIN ANALYZE können Sie den Ausführungsplan der Abfrage und die tatsächliche Verarbeitungszeit überprüfen.
explain (analyze, buffers, format text)
select * from orders where customer_id = 123;
Achten Sie im Ergebnis auf folgende Punkte:
- Seq Scan (Sequential Scan): Möglicherweise fehlt ein Index.
- Rows Removed by Filter: Wenn viele Zeilen durch den Filter entfernt wurden, sollten Sie einen Index hinzufügen.
- Buffer reads >> cache hits: Es treten viele Festplatten-I/Os auf, was auf zu wenig Arbeitsspeicher hindeuten kann.
- Übermäßige Wiederholungen von Nested Loops: Die JOIN-Methode muss möglicherweise überdacht werden.
Verwendung in Kombination mit KI-Agenten
Wie lassen sich diese Inhalte effektiv in die tägliche Entwicklung integrieren? Hier sind einige praktische Methoden:
1. Agent Skills im Projekt installieren
Wenn Sie die Skills mit dem zuvor vorgestellten Befehl installieren, werden Referenzdateien im Projektverzeichnis abgelegt.
npx skills add https://github.com/supabase/agent-skills --skill supabase-postgres-best-practices
KI-Agenten können diese Dateien lesen und bei der Generierung oder Überprüfung von Abfragen die Best Practices referenzieren.
2. Installation als Plugin in Claude Code
Wenn Sie Claude Code verwenden, gibt es auch eine Methode zur direkten Installation als Plugin.
/plugin marketplace add supabase/agent-skills
/plugin install postgres-best-practices@supabase-agent-skills
Nach der Installation als Plugin erkennt Claude Code automatisch datenbankbezogene Aufgaben und zieht die Best Practices heran. Mit Anweisungen wie „Optimiere diese Postgres-Abfrage" oder „Füge der Tabelle passende Indizes hinzu" erhalten Sie Vorschläge, die auf den Regeln der Agent Skills basieren.
Natürlich kann auch der oben genannte Befehl npx skills add verwendet werden. Wenn die Dateien im Projektverzeichnis liegen, ist dies praktisch, um die Best Practices mit allen Teammitgliedern zu teilen.
3. Nutzung als Checkliste bei Code-Reviews
Wenn Sie einen KI-Agenten um ein Code-Review bitten, ist es ratsam, ihn anzuweisen, die folgenden Aspekte zu berücksichtigen:
- Gibt es Indizes für Spalten in WHERE-Klauseln oder JOIN-Bedingungen?
- Ist
auth.uid()in RLS-Richtlinien in(select auth.uid())eingeschlossen? - Enthält eine Transaktion Aufrufe externer APIs?
- Liegt ein N+1-Muster vor?
- Sind die Datentypen angemessen (insbesondere
bigintfür ID-Spalten)?
Es ist jedoch nicht notwendig, jedes Mal so detaillierte Anweisungen zu schreiben. Wenn der Skill supabase-postgres-best-practices installiert ist, reicht oft eine Aufforderung wie „Reviewe diese Änderungen im Hinblick auf Postgres Best Practices". Falls der Review-Umfang groß ist oder andere Regelsätze verwendet werden, ist es klarer, explizit zu sagen: „Nutze den Skill supabase-postgres-best-practices für das Review". Die obige Checkliste dient als zusätzlicher Prompt, um die Gründlichkeit des Reviews weiter zu erhöhen.
4. Schrittweise Verbesserung basierend auf Prioritäten
Ideal wäre es, alle Kategorien gesammelt zu überprüfen, nachdem die Auswirkungen geprüft wurden. Da es in der Realität jedoch oft Einschränkungen bei Zeit und Risiko gibt, ist es effektiv, prioritär die KRITISCHEN Kategorien (Abfrage-Performance, Verbindungsmanagement, Sicherheit) anzugehen.
Fazit
Wir haben die Inhalte von Supabases „Postgres Best Practices for AI Agents" vorgestellt. Hier sind die wichtigsten Punkte:
- Die Herausforderung besteht darin, dass KI-Agenten Best Practices zwar als Wissen besitzen, sie aber nicht immer konsequent anwenden.
- Durch die Einführung von Agent Skills können Sie dem Agenten die korrekte Schreibweise für Postgres beibringen.
- Die drei Kategorien mit der höchsten Priorität sind Abfrage-Performance, Verbindungsmanagement und Sicherheit/RLS.
- Das Hinzufügen von Indizes, die Einführung von Connection Pooling und die korrekte Implementierung von RLS sind die Maßnahmen mit der größten Wirkung.
- Kleine Kniffe wie
(select auth.uid())können in manchen Fällen Geschwindigkeitsverbesserungen um das über 100-fache bewirken.
Die Zusammenarbeit mit KI-Agenten bei Datenbanken wird in Zukunft stetig zunehmen. Wenn Sie diese Best Practices in Ihre Projekte integrieren, können Sie die Qualität des von der KI generierten Codes erheblich steigern. Probieren Sie es aus!
Referenz-Links
- Dieser Artikel wurde unter Verwendung der folgenden Primärquellen verfasst (Inhalte zusammengefasst und neu strukturiert).
- Introducing: Postgres Best Practices - Supabase Blog
- supabase/agent-skills - GitHub
- Supabase Postgres Best Practices - Skills.sh
- Row Level Security - Supabase Docs
- Connection Management - Supabase Docs
- RAG with Permissions - Supabase Docs