Alle Notizen
17. Mai 202611 Min. Lesedauerpostgrespgvectorrlsai-agentsmcpengineering

Die Identity deines Agenten ist eine Postgres-Rolle

Das Meme "pgvector skaliert nicht" war 2023 wahr, 2024 halb wahr und ist heute größtenteils eine Funktion davon, das 0.8.0-Changelog nicht gelesen zu haben. Während die Dedicated-Vector-DB-Fraktion Benchmarks aus einem zwei Jahre alten Commit immer wieder probte, hat Microsoft im Stillen einen First-Party-MCP-Server für Postgres innerhalb von Foundry ausgeliefert, pg_diskann allgemein verfügbar gemacht und auf der Ignite 2025 Azure HorizonDB als Preview gezeigt. Keine dieser Ankündigungen bekam den Keynote-Slot, den sie verdient hätte.

Hier ist die These, gegen die ich dich argumentieren lassen will: Postgres ist still und leise zur Agent Data Plane geworden, und die interessante Frage für 2026 ist nicht "welche Vector DB", sondern "was bekommt das Recht, in derselben Transaktion zu leben wie die Aktionen deines Agenten". Sobald du es so framest, fangen die meisten Polyglot-Stacks, die die AI-Infrastructure-Fraktion seit zwei Jahren verkauft, an, nach Premature Optimization auszusehen.

In diesem Artikel


Die Identity deines Agenten ist eine Postgres-Rolle

Die Verschiebung, die niemand angekündigt hat

Das sauberste Signal, wohin das geht, tauchte in einer siebenminütigen Demo auf der Build 2025 auf. Abe Omorogbe vom Azure-PostgreSQL-Team führte in seiner Session PostgreSQL Like a Pro: Build smart and secure agents durch Microsoft Foundrys native Integration mit Managed Postgres. Ich ging mit der Erwartung eines glatten First-Party-SDK-Reveals rein. Was ich tatsächlich sah, ist eine leisere und wichtigere Architekturentscheidung, und beim zweiten Schauen begann ich, meine Empfehlung für neue Agent-Projekte umzuschreiben.

Was Foundry tatsächlich ausliefert, ist ein MCP-Server für Postgres, verpackt als Azure Container App, den ein Agent über eine standardisierte Tool-Oberfläche aufruft. Der Agent bekommt Schema-Introspection, parametrisierte Queries und Write-Tools über das Model Context Protocol. Das klingt banal, bis dir auffällt, was es ersetzt. Die letzten achtzehn Monate hat die OSS-Community LangChain-SQL-Toolkits, Custom-Function-Calling-Shims und brüchige "Beschreibe-dein-Schema-im-System-Prompt"-Tricks von Hand gestrickt. Microsoft hat das Muster gerade produktisiert und für erledigt erklärt.

Stell dir MCP als ODBC für Agenten vor. Die Protokoll-Oberfläche lebt neben den Daten, nicht neben dem Modell. Jeder Datenbankhersteller, der 2026 agent-nativ sein will, wird einen ausliefern. Der Grund, warum Postgres auf einem Hyperscaler zuerst da war, ist keine technische Überlegenheit. Es ist, dass GPT-4 mehr Postgres gelesen hat als jeden anderen Dialekt und dass es Microsoft lieber ist, dass du innerhalb derselben Netzwerkgrenze wie der Rest deiner Daten bleibst, als Zeilen über die Leitung zu einem separaten Vector-Service zu schicken.

Drei weitere Talks in derselben "PostgreSQL Like a Pro"-Reihe plus das HorizonDB-Reveal auf der Ignite machen die Wette explizit. Microsofts Postgres-Roadmap 2025 benennt das Ziel: Postgres als kanonische Agent Data Plane auf Azure, mit DiskANN, Entra-ID-Auth, der azure_ai-Extension und HorizonDB als Stapel obendrauf.

Was pgvector still gefixt hat

Zwei Jahre lang lautete der Standardeinwand gegen Vector Search innerhalb von Postgres auf einen Benchmark, den jeder wiederholte und niemand neu fuhr. Die Geschichte ging: pgvector kippt jenseits von fünf Millionen Zeilen um, filtered Search kollabiert den Recall, und Index-Builds fressen den ganzen RAM. Bis Ende 2024 hatte jeder dieser Einwände einen echten Fix in main.

Der größte sind Iterative Index Scans, veröffentlicht in pgvector 0.8.0. Vor 0.8 sah der Post-Filter-Failure-Mode so aus: du fragst den Index nach den Top-10-Nearest-Neighbors, wendest dann einen WHERE tenant_id = $1-Filter an, und die Ergebnismenge schrumpft auf drei Zeilen, weil sieben der Nearest Neighbors zu anderen Tenants gehörten. Der Fix sind hnsw.iterative_scan und ivfflat.iterative_scan. Der Planner fetcht weiter Kandidaten, bis dein Filter erfüllt ist oder ein konfigurierbares Cap erreicht ist. Die "wir fragten 10, bekamen 3"-Klippe ist weg.

Das andere stille Upgrade ist halfvec. Embeddings als 16-Bit-Floats statt 32-Bit zu speichern, verdoppelt das Dimension-Cap auf HNSW- und IVFFlat-Indizes von 2.000 auf 4.000 mit faktisch null Recall-Impact für OpenAI-Klasse-Embeddings. Wenn du deine 1.536-dim- oder 3.072-dim-Spalten noch nicht migriert hast, zahlst du doppelten Storage und doppelte Indexgröße für keinen messbaren Genauigkeitsgewinn.

Der letzte Einwand, den es zu pensionieren lohnt, ist die Index-Build-Memory-Story. Ja, einen HNSW-Index auf 5 Mio. × 1536-dim-Vektoren zu bauen, braucht 8 bis 16 GB maintenance_work_mem, sonst läuft der Build auf Disk 10- bis 50-mal langsamer, ein Tradeoff, den Niles pgvector-Deep-Dive konkret gemessen hat. Die ehrliche Antwort ist: setz ihn einmal richtig, baue einmal und mach weiter. The New Stacks Methodikkritik an pgvector-Benchmarks ist hier Pflichtlektüre. Die meisten "pgvector ist langsam"-Posts konfigurieren maintenance_work_mem und ef_construction falsch und veröffentlichen dann QPS-Zahlen, als wären das Defaults. Ich habe zwei Beratungsklienten gesehen, die ein Quartal mit Shopping für eine Vector DB verbrachten, um einer Problemklasse zu entkommen, die eine Postgres-Config-Änderung an einem Nachmittag gefixt hätte.

Die kontrarische Zusammenfassung: das meiste der stärksten Kritik an pgvector wurde gegen Verhalten geschrieben, das nicht mehr existiert. Sie lohnt weiterhin das Lesen. Sie lohnt das Zitieren ohne Fußnote nicht mehr.

Eine Sache noch, die es zu pensionieren lohnt: die Annahme, Hybrid Search zwinge dich von Postgres weg. Der sparsevec-Typ plus eine tsvector-Spalte geben dir BM25- und Dense-Vector-Retrieval im selben Query-Plan, mit denselben WHERE-Prädikaten und denselben RLS Policies. Niles pgvector-Myths-Post geht das Muster im Detail durch. Die Zwei-System-Steuer, die die meisten Teams für "echte" Hybrid Search zahlen, ist 2026 fast immer optional.

DiskANN vs HNSW: der echte Tradeoff

Die Headline-Zahl von Azure für pg_diskann lautet "bis zu 10x schneller, 4x günstiger, 96x weniger Memory als pgvector HNSW". Das ist eine Marketingaussage, und wie die meisten Marketingaussagen technisch wahr unter einem Workload und irreführend unter den meisten anderen. Der echte Tradeoff ist interessanter als der Slogan.

DiskANN ist derselbe ANN-Algorithmus, der Bing und M365 Copilot Vector Search antreibt. Er hält einen kleinen In-Memory-Cache und lässt den Hauptteil des Graphen auf NVMe leben. HNSW erwartet im Gegensatz dazu, den ganzen Graphen in RAM zu halten. Sobald du dieses Framing akzeptierst, ist der Vergleich nicht "welcher ist schneller", sondern "welcher passt zur Kostenform deines Workloads".

| Achse | pgvector HNSW | pg_diskann (Azure) | |---|---|---| | Wo der Index lebt | RAM | NVMe mit kleinem RAM-Cache | | Practitioner p95 bei 99 % Recall | ~12ms (M=48, ef=96) | ~15ms | | Memory für 50M × 768-dim | ~55 GB RAM | ~16 GB RAM + 350 GB NVMe | | Build Memory | 8-16 GB maintenance_work_mem | Ähnlich, weniger sensitiv | | Dimension Cap | 2.000 (4.000 mit halfvec) | 16.000 ab v0.6 | | Verfügbarkeit | Upstream, jedes Postgres | Nur Azure Flexible Server | | Upstream-Ziel | Bereits da | Geplant für PG18 | | Best Fit | < 20M Vektoren, latenz-bound, RAM-reich | 20M+ Vektoren, cost-bound, NVMe-reich |

Lies die Zahlen ehrlich. HNSW ist schneller bei roher Latenz im Practitioner-Medium-Benchmark, der in der breiteren Forschung zitiert wird. DiskANN gewinnt bei Memory-Kosten. Wenn du auf einem Hyperscaler läufst, wo RAM der dominierende Posten auf deiner Rechnung ist, übersetzt sich der "96x weniger Memory"-Claim in echtes Geld. Wenn du einen Datensatz hast, der in RAM passt, und dich p95 zuerst kümmert, ist HNSW weiterhin der richtige Default.

Es gibt eine weitere Achse, die es zu benennen lohnt: gefilterte Vector Search. HorizonDB hat Predicate Pushdown in den DiskANN-Index als Preview gezeigt, der architektonische Zug, der die Lücke zu Qdrant bei der Filtered-Query-Latenz endlich schließt. Ob das in GA so ausgeliefert wird, wie es demonstriert wurde, ist die Wette, die du 2026 beobachten solltest.

Identity ist der Hebel: Agent gleich Postgres-Rolle plus RLS

Das ist der Teil der Geschichte, den die meisten Agent-Tutorials überspringen, und es ist der Teil, der für jeden mit Multi-Tenant-Workloads am meisten zählt. Die Foundry-Demo zeigt das richtige Muster fast aus Versehen. Als Abel den Agenten zuerst bittet, Tables aufzulisten, gibt er "no tables" zurück, weil die Postgres-Rolle des MCP-Servers noch keine SELECT-Grants hat. Das ist kein Bug. Das ist das gesamte Security-Modell, kondensiert in eine Fehlermeldung.

Lies die Interaktion noch einmal. Der Agent hat ein Modell, ein Tool und einen Connection String. Er kann trotzdem kein einziges Byte Daten sehen, bis ein Mensch der Rolle das Recht gewährt, eine bestimmte Tabelle zu lesen. Es gibt keinen separaten Agent-Permissions-Service, den man falsch konfigurieren könnte, keine IAM Policy, die in einer anderen Cloud-Console lebt, keine JSON-Rollendefinition, die aus dem Takt mit dem läuft, was tatsächlich in der Datenbank ist. Die Permission-Grenze und die Daten leben am selben Ort, und du kannst das mit einem SELECT * FROM information_schema.role_table_grants auditieren.

💡 Die Postgres-Rolle ist die Identity des Agenten. Hör auf, RBAC auf das Agent Framework draufzuschrauben. Kollabiere es in die Datenbankrolle, und Audit, Revoke und Rotate leben in pg_roles statt in der Config-UI deiner Agent-Plattform.

Das kanonische Multi-Tenant-Muster, kodifiziert in Supabases RLS-Guide und verstärkt in Supabases Best-Practices-Post für Agenten, sieht so aus:

-- 1. Jede tenant-scoped Tabelle trägt eine tenant_id.
ALTER TABLE products ADD COLUMN tenant_id uuid NOT NULL;
ALTER TABLE product_embeddings ADD COLUMN tenant_id uuid NOT NULL;

-- 2. Aktiviere RLS und schreibe eine Policy, die auf einer Session-GUC keyed ist.
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
ALTER TABLE product_embeddings ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON products
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

CREATE POLICY tenant_isolation ON product_embeddings
  USING (tenant_id = current_setting('app.tenant_id')::uuid);

-- 3. Setze die GUC am Start jeder Agent Session.
SELECT set_config('app.tenant_id', $1, true);

-- 4. Verbinde dich über eine Least-Privilege-Rolle, niemals die Service-Rolle.
CREATE ROLE agent_runtime LOGIN PASSWORD :'pw';
GRANT SELECT, INSERT ON products, product_embeddings TO agent_runtime;
-- Entscheidend: gewähre NICHT BYPASSRLS.

-- 5. Vector-Indizes leben auf derselben Tabelle, die die Policy schützt.
CREATE INDEX ON product_embeddings
  USING hnsw (embedding halfvec_cosine_ops)
  WITH (m = 16, ef_construction = 64);

Das ist die gesamte Isolations-Story für einen Multi-Tenant-Agenten auf Postgres. Fünf Schritte, eine Extension, null neue Services.

Jetzt der Footgun. MCP-Server, die sich mit Service-Role-Credentials verbinden, umgehen RLS still. Die meisten Agent-Tutorials, einschließlich einiger offizieller, geben dem MCP-Server einen Superuser-äquivalenten Connection String, weil das der Pfad des geringsten Widerstands ist. Die Supabase-MCP-Docs warnen davor. Die meisten anderen nicht. Wenn dein Agent einen Service-Role-Key nutzt, um deinen MCP-Server aufzurufen, sind deine RLS Policies dekorativ. Sie werden Tests bestehen, Audits scheitern und beim ersten Mal Daten über Tenants hinweg leaken, wenn ein Agent eine Query generiert, die das tenant_id-Prädikat auslässt.

Zwei angrenzende Footguns, die zu kennen sich lohnt. Erstens: SECURITY DEFINER-Funktionen umgehen RLS, sofern sie die Policy nicht explizit neu prüfen, sodass jede Stored Procedure, die dein Agent aufruft, ein potenzielles Cross-Tenant-Leak wird. Zweitens: PgBouncer im Transaction-Pool-Modus kann die app.tenant_id-GUC über Sessions hinweg leaken, weil die gepoolte Connection ihren Session-State zwischen Checkouts behält. Der Fix ist entweder Session-Pool-Modus zu nutzen, DISCARD ALL bei jedem Checkout zu erzwingen oder die GUC mit set_local innerhalb einer umschließenden Transaktion zu setzen, sodass sie mit dem Commit stirbt. Wähle eines und schreib es auf. Beide haben Teams, denen ich vertraue, schon gebissen, und beide passieren still. RLS wirft keinen Fehler, wenn es umgangen wird. Es gibt einfach Zeilen zurück, die es nicht sollte.

Der Fix ist langweilig und tragend: erstelle eine dedizierte Least-Privilege-Rolle für den MCP-Server, gewähre niemals BYPASSRLS und mach set_config('app.tenant_id', ...) zur nicht verhandelbaren Präambel jeder Query in der Session des Agenten.

Wann Postgres trotzdem verliert

Die ehrliche Version dieses Arguments hat Grenzen, und sie zu überspringen verwandelt einen nützlichen Rahmen in einen Sales Pitch. Hier ist, wo Postgres als Agent Data Plane die falsche Wahl ist.

➡️ Jenseits von rund 100M Vektoren. Timescales pgvectorscale-Benchmark kommt auf 50M Cohere-Vektoren mit 28x niedrigerem p95 und 16x höherem QPS als Pinecones s1-Tier bei 99 % Recall, aber das ist Timescales Zahl gegen Pinecones langsamste Tier. Jenseits von 100M verdienen für Milliarden ausgelegte Dedicated Vector Stores ihren Platz, und das Operative-Simplicity-Argument fängt an, gegen das "ich brauche diese Query in 8ms, sonst bricht meine UX"-Argument zu verlieren.

➡️ Extremer Write-Durchsatz auf Vector-Spalten. HNSW-Index-Inserts sind teuer. Wenn du Zehntausende neue Embeddings pro Sekunde nachhaltig ingestest, gibst du mehr für maintenance_work_mem und Lock Contention aus, als du an architektonischer Einfachheit sparst. Eine purpose-built Vector DB mit asynchronen Index-Updates schlägt dich auf Kosten pro Write.

➡️ Multi-Region Active-Active. Logical Replication in Postgres hat bekannte scharfe Kanten, und Aurora-artiger Multi-Master ist kein kostenloses Upgrade. Wenn dein Agent simultan Sub-50ms-Latenz von drei Kontinenten braucht, ist der Polyglot-Stack mit einem global verteilten Vector-Service plus einem regionalen Postgres-Write-Leader weiterhin die sicherere Wette. HorizonDBs Sub-ms-Multi-Zone-Commit verkleinert die Lücke innerhalb einer Region. Cross-Region ist weiterhin ein offenes Problem.

➡️ In-DB-LLM-Calls als Hot-Path-Dependency. Azures azure_ai-Extension lässt dich OpenAI aus einem SELECT-Statement aufrufen. Das ist ein großartiges Muster für Batch-Backfills von Embeddings. Es ist ein gefährlicher Default für alles user-facing, weil du gerade deine Datenbank-Transaktionen an OpenAIs Verfügbarkeit und Tail Latency gekoppelt hast. Nutze es für ETL. Nutze es nicht in einem Request-Pfad, der ein SLO hat.

Keine dieser Grenzen ändert die These. Sie schärfen sie. Postgres ist der richtige Default für den Long Tail der Agent-Workloads. Die exotischen Fälle brauchen weiterhin exotische Infrastruktur, und so zu tun, als wäre es anders, ist der Weg, wie eine "Einfacher-Stack"-Entscheidung in Woche eins zur sechsmonatigen Migration in Jahr zwei wird. Der ehrliche Test ist: hast du tatsächlich die Skala, die Write Rate oder die Geografie, die die Eine-DB-Story bricht, oder optimierst du für ein Problem, das du noch nicht hast?

Wie ich es Montagmorgen bauen würde

Wenn du mir heute ein Greenfield-Agent-Projekt übergeben und um den langweiligen, verteidigbaren Stack bitten würdest, wäre das, was ich auf die Rückseite eines Briefumschlags schreiben würde:

Managed Postgres 17+ mit pgvector 0.8 und halfvec-Spalten. Wähle pg_diskann, wenn deine Daten auf Azure liegen und dein Datensatz jenseits von 20M Vektoren ist; andernfalls ist HNSW mit m=16, ef_construction=64 der vernünftige Default.

Eine dedizierte agent_runtime-Postgres-Rolle pro Tenant, mit expliziten Per-Table-GRANTs, ohne BYPASSRLS und einer Connection-Pool-Config, die RESET ALL beim Checkout erzwingt.

RLS Policies auf jeder tenant-scoped Tabelle, einschließlich der Embedding-Tabelle. Setze app.tenant_id über set_config als erstes Statement jeder Agent Session.

MCP-Server vor der Datenbank, der als Sidecar oder Container Service läuft, verbunden mit der Least-Privilege-Rolle oben. Behandle den Connection String des MCP-Servers wie ein Produktionsgeheimnis, nicht wie einen Demo-Platzhalter.

Embedding-Generierung in einem Background Job, nicht in einem Request Handler. Nutze azure_ai oder einen dedizierten Worker, deine Entscheidung. Halte den Request-Pfad frei von synchronen LLM-Dependencies.

Eine Audit-Log-Tabelle, geschrieben in derselben Transaktion wie die Aktion des Agenten, mit tenant_id, agent_id, Tool-Name und vollem Query-Text. Das ist die wertvollste einzelne Telemetrie für Agent Governance, die du je besitzen wirst, und sie kostet nichts, sie unter dem ACID-Schirm zu erfassen, den du schon hast.

❌ Schicke Vektoren nicht in einen separaten Service, bevor du das Produkt ausgeliefert hast. Der Polyglot-Stack ist das zweite System, das du baust, sobald das erste dir genau sagt, wo es wehtut.

Dieser Stack wird nicht jeden Benchmark gewinnen. Er wird die meisten Argumente über Operativkosten gewinnen, und er lässt dich jede Agent-Aktion atomar mit dem Audit-Eintrag machen, der sie rechtfertigt. Die Eine-DB-Story ist nicht romantisch. Sie ist nur sehr schwer zu schlagen auf den Achsen, die zählen, sobald ein System Nutzer hat.

Ich fahre seit sechs Monaten Variationen dieses Musters in Kundenarbeit und habe Meinungen dazu, was in Produktion bricht, die ich lieber nicht in einen Blogpost schreibe. Wenn du dasselbe entwirfst, gleiche ich gerne Notizen ab und teile, was funktioniert hat und was nicht.