Kontakt
Engineering

Postgres-Griffe, die bei uns sitzen

Vier Funktionen, die wir in fast jedem Projekt einsetzen, und die eine, die wir nicht mehr anfassen, seit sie uns gebissen hat.

Von Ovadev3 Min. Lesezeit

Alle Beiträge
  • postgres
  • sql
  • januna
WhatsAppLinkedInX

Wir sind keine Datenbank-Maximalisten. Aber eine Regel, die im Anwendungscode steht, hält genau so lange, bis der zweite Dienst dazukommt, und jedes Produkt bekommt irgendwann einen zweiten Dienst: einen Cronjob, ein Importskript, ein schnell hingeschriebenes Admin-Werkzeug.

Die vier, nach denen wir greifen

  • Row Level Security
  • Generierte Spalten
  • Partielle Indizes
  • Advisory Locks

Row Level Security, für echte Mandantentrennung#

Januna ist mandantenfähig. Jede Abfrage in der Anwendung filtert nach tenant_id, und jeder dieser Filter ist eine Gelegenheit, es zu vergessen. Row Level Security macht daraus statt einer Konvention eine Einschränkung, welche die Datenbank durchsetzt, egal wer fragt.

sql
alter table sale enable row level security;

create policy tenant_isolation on sale
  using (tenant_id = current_setting('app.tenant', true)::uuid);

-- die App setzt das einmal pro Verbindung
set local app.tenant = '…';

Generierte Spalten, für alles, was nicht auseinanderlaufen darf#

Alles Abgeleitete, ein Suchvektor, eine kleingeschriebene E-Mail-Adresse, ein Total mit Steuer, ist eine Gelegenheit für zwei Codepfade, sich uneinig zu sein. Eine generierte Spalte kann nur eine Antwort haben, und berechnet wird sie von dem, was sie auch speichert.

sql
alter table product
  add column search tsvector
  generated always as (
    to_tsvector('simple', coalesce(name,'') || ' ' || coalesce(sku,''))
  ) stored;

create index product_search_idx on product using gin (search);

Partielle Indizes, für die Abfrage, die wirklich läuft#

Die meisten Tabellen werden mit einem Filter gelesen: offene Bestellungen, aktive Sitzungen, nicht archivierte Zeilen. Ein partieller Index deckt genau diesen Ausschnitt ab und lässt den Rest weg. Auf der Verkaufstabelle von Januna heisst das: ein Index mit einem Zwanzigstel der Grösse beantwortet die Abfrage, die tausendmal pro Stunde läuft.

sql
create index sale_open_idx on sale (tenant_id, created_at desc)
  where closed_at is null;

Advisory Locks, für den Job, der nicht zweimal laufen darf#

Bevor Sie zu einer Queue oder einem Scheduler mit Leader Election greifen, probieren Sie das Lock, das die Datenbank ohnehin schon hat. Ein Advisory Lock auf Session-Ebene gibt Ihnen wechselseitigen Ausschluss über jeden Prozess, der auf dieselbe Datenbank zeigt, und es verschwindet mit der Verbindung.

sql
select pg_try_advisory_lock(hashtext('nightly-rollup'));
-- false → jemand anderes macht es schon, ab nach Hause

Die eine, die wir nicht mehr anfassen#

listen/notify zur Cache-Invalidierung. Es ist elegant, es ist sofort, und es wirft bei einem Reconnect still alles weg. Der Cache ist also korrekt bis genau zu dem Moment, in dem das Netz es nicht ist, und danach ist er falsch, ganz ohne Signal. Wir sind auf kurze TTLs plus eine Versionsspalte umgestiegen. Weniger elegant, dafür ohne Fehlerfall, den man erst bemerken muss.

Ovadev

Ein Softwareunternehmen in Baar, Zug. Wir bauen eigene Produkte, betreiben sie danach selbst und schreiben auf, was uns das beibringt.

Kontakt aufnehmen
Weiterlesen

Mehr aus dem Studio: zuerst Engineering, dann der Rest.

Alle Beiträge
© 2026 Ovadev GmbHAus der Schweiz