07. Sept. 2025·6 Min. Lesezeit

PostgreSQL Advisory Locks für konkurrenzsichere Workflows

Lerne, wie PostgreSQL Advisory Locks doppelte Verarbeitung bei Genehmigungen, Abrechnung und Scheduler verhindern — mit praktischen Mustern, SQL-Beispielen und einfachen Prüfungen.

PostgreSQL Advisory Locks für konkurrenzsichere Workflows

Das eigentliche Problem: zwei Prozesse erledigen dieselbe Arbeit

Doppelte Verarbeitung entsteht, wenn derselbe Eintrag zweimal bearbeitet wird, weil zwei Akteure jeweils glauben, sie seien zuständig. In echten Apps zeigt sich das als doppelte Abbuchung beim Kunden, eine Genehmigung, die zweimal angewendet wird, oder eine „Rechnung fertig“-E-Mail, die zweimal verschickt wird. Im Test sieht oft alles gut aus — unter realem Traffic bricht es dann.

Oft passiert es, wenn die Timing-Grenze eng wird und mehr als ein Akteur gleichzeitig handeln kann:

Zwei Worker nehmen denselben Job gleichzeitig auf. Ein Retry feuert, weil ein Netzwerkaufruf langsam war, während der erste Versuch noch läuft. Ein Nutzer klickt doppelt auf „Genehmigen“, weil die UI kurz eingefroren ist. Zwei Scheduler überlappen nach einem Deploy oder durch eine Uhrabweichung. Selbst ein Fingertipp kann zwei Requests auslösen, wenn eine Mobile-App nach einem Timeout erneut sendet.

Das Schlimme ist: jeder Akteur verhält sich für sich gesehen „vernünftig“. Der Fehler ist die Lücke dazwischen: keiner weiß, dass der andere das gleiche Datensatz schon verarbeitet.

Das Ziel ist einfach: für ein gegebenes Objekt (eine Bestellung, eine Genehmigungsanfrage, eine Rechnung) darf immer nur ein Akteur die kritische Arbeit gleichzeitig ausführen. Alle anderen sollen entweder kurz warten oder zurückziehen und es später erneut versuchen.

PostgreSQL Advisory Locks helfen dabei. Sie geben dir eine leichte Möglichkeit zu sagen „Ich arbeite an Objekt X“ unter Nutzung der Datenbank, der du ohnehin für Konsistenz vertraust.

Setze aber Erwartungen: ein Lock ist kein vollständiges Queue-System. Er plant keine Jobs für dich, garantiert keine Reihenfolge und speichert keine Nachrichten. Er ist ein Sicherheitsgitter um den Teil des Workflows, der niemals doppelt laufen darf.

Was PostgreSQL Advisory Locks sind (und was nicht)

PostgreSQL Advisory Locks sorgen dafür, dass nur ein Worker einen bestimmten Arbeitsschritt zur selben Zeit ausführt. Du wählst einen Lock-Schlüssel (z. B. „Rechnung 123“), fragst die Datenbank nach dem Lock, führst die Arbeit aus und gibst ihn wieder frei.

Das Wort „advisory“ ist wichtig. Postgres weiß nicht, was dein Schlüssel bedeutet, und schützt nichts automatisch. Es merkt sich nur eins: dieser Schlüssel ist gesperrt oder nicht. Dein Code muss sich auf das Schlüssel-Format einigen und den Lock nehmen, bevor er den riskanten Teil ausführt.

Es hilft auch, Advisory Locks mit Row Locks zu vergleichen. Row Locks (wie SELECT ... FOR UPDATE) schützen tatsächliche Tabellenzeilen. Sie sind super, wenn die Arbeit sauber auf eine Zeile passt. Advisory Locks schützen einen von dir gewählten Schlüssel und sind nützlich, wenn der Workflow viele Tabellen berührt, externe Dienste aufruft oder beginnt, bevor eine Zeile existiert.

Advisory Locks sind nützlich, wenn du brauchst:

  • Einmalige Aktionen pro Entität (eine Genehmigung pro Anfrage, eine Abbuchung pro Rechnung)
  • Koordination über mehrere App-Server ohne einen separaten Lock-Service
  • Schutz rund um einen Workflow-Schritt, der größer ist als ein einzelnes Row-Update

Sie ersetzen keine anderen Sicherheitswerkzeuge. Sie machen Operationen nicht automatisch idempotent, erzwingen keine Geschäftsregeln und verhindern keine Duplikate, wenn ein Codepfad vergisst, den Lock zu nehmen.

Man nennt sie oft „leichtgewichtig“, weil du sie ohne Schema-Änderungen oder zusätzliche Infrastruktur nutzen kannst. In vielen Fällen lässt sich doppelte Verarbeitung beheben, indem du an einer kritischen Stelle nur einen Lock-Aufruf einfügst und sonst das Design beibehältst.

Lock-Typen, die du tatsächlich verwenden wirst

Wenn Leute „PostgreSQL Advisory Locks“ sagen, meinen sie meist einen kleinen Satz von Funktionen. Die Wahl beeinflusst, was bei Fehlern, Timeouts und Retries passiert.

Session- vs. Transaktions-Locks

Ein Session-Lock (pg_advisory_lock) hält so lange, wie die Datenbankverbindung besteht. Das ist bequem für langlaufende Worker, bedeutet aber auch, dass ein Lock hängen bleiben kann, wenn deine App auf eine Weise crasht, die eine gepoolte Verbindung zurücklässt.

Ein Transaktions-Lock (pg_advisory_xact_lock) ist an die aktuelle Transaktion gebunden. Beim Commit oder Rollback gibt PostgreSQL ihn automatisch frei. Für die meisten Request-Response-Workflows (Genehmigungen, Abrechnungs-Klicks, Admin-Aktionen) ist das die sichere Voreinstellung, weil man das Freigeben kaum vergessen kann.

Blocking vs. Try-Lock

Blocking-Aufrufe warten, bis der Lock verfügbar ist. Einfach, aber sie können eine Web-Anfrage blockierend wirken lassen, wenn eine andere Session den Lock hält.

Try-Lock-Aufrufe geben sofort zurück:

  • pg_try_advisory_lock (Session-Level)
  • pg_try_advisory_xact_lock (Transaction-Level)

Try-Lock ist oft besser für UI-Aktionen. Wenn der Lock belegt ist, kannst du sofort eine klare Meldung wie „Wird bereits verarbeitet“ zurückgeben und den Nutzer zum erneuten Versuch auffordern.

Shared vs. Exclusive

Exklusive Locks erlauben „one-at-a-time“. Shared Locks erlauben mehrere Inhaber, blockieren aber einen exklusiven Lock. Die meisten Double-Processing-Probleme lösen sich mit exklusiven Locks. Shared Locks sind nützlich, wenn viele Leser gleichzeitig arbeiten können, aber ein seltener Schreiber allein laufen muss.

Wie Locks freigegeben werden

Die Freigabe hängt vom Typ ab:

  • Session-Locks: bei Disconnect oder explizit mit pg_advisory_unlock
  • Transaktions-Locks: automatisch beim Ende der Transaktion

Den richtigen Lock-Key wählen

Ein Advisory Lock funktioniert nur, wenn jeder Worker genau denselben Schlüssel für dieselbe Arbeit versucht zu sperren. Wenn ein Pfad invoice 123 sperrt und ein anderer customer 45, kannst du trotzdem Duplikate bekommen.

Beginne damit, das „Ding“ zu benennen, das du schützen willst. Mach es konkret: eine Rechnung, eine Genehmigungsanfrage, eine Ausführung eines geplanten Tasks oder der monatliche Abrechnungszyklus eines Kunden. Diese Wahl bestimmt, wie viel Parallelität du erlaubst.

Wähle einen Scope passend zum Risiko

Die meisten Teams nutzen eines der folgenden:

  • Pro Datensatz: am sichersten für Genehmigungen und Rechnungen (Lock nach invoice_id oder request_id)
  • Pro Kunde/Account: nützlich, wenn Aktionen pro Kunde seriell laufen müssen (Abrechnung, Kreditänderungen)
  • Pro Workflow-Schritt: wenn verschiedene Schritte parallel laufen können, aber jeder Schritt einzeln ausgeführt werden muss

Behandle den Scope als Produktentscheidung, nicht als Datenbankdetail. „Pro Datensatz“ verhindert doppelte Klicks, die zu doppelten Abbuchungen führen. „Pro Kunde“ verhindert, dass zwei Hintergrundjobs sich beim Erstellen von Abrechnungen überschneiden.

Wähle eine stabile Schlüssel-Strategie

In der Praxis hast du meist zwei Optionen: zwei 32‑Bit-Integer (Namespace + id) oder ein 64‑Bit-Integer (bigint), manchmal erzeugt durch Hashen eines String-IDs.

Zwei-Integer-Schlüssel lassen sich leicht standardisieren: wähle eine feste Namespace-Nummer pro Workflow (z. B. Genehmigungen vs. Abrechnung) und nutze die Record-ID als zweiten Wert.

Hashing ist praktisch bei UUIDs, aber du musst ein kleines Kollisionsrisiko akzeptieren und überall konsistent bleiben.

Was immer du wählst: schreibe das Format auf und zentralisiere es. „Fast derselbe Schlüssel“ an zwei Stellen zu haben, ist ein häufiger Weg, Duplikate wieder einzuführen.

Schritt für Schritt: ein sicheres Muster für „one-at-a-time"-Verarbeitung

In deine Umgebung deployen
Deploy auf AppMaster Cloud oder in deiner eigenen AWS-, Azure- oder Google-Cloud-Umgebung.
Jetzt deployen

Ein guter Advisory-Lock-Workflow ist simpel: Lock, prüfen, handeln, festhalten, committen. Der Lock ist nicht die Geschäftsregel selbst. Er ist eine Leitplanke, die die Regel zuverlässig macht, wenn zwei Worker auf denselben Datensatz treffen.

Ein praktisches Muster:

  1. Öffne eine Transaktion, wenn das Ergebnis atomar sein muss.
  2. Erwirb den Lock für die spezifische Arbeitseinheit. Bevorzuge einen transaktionsgebundenen Lock (pg_advisory_xact_lock), damit er automatisch freigegeben wird.
  3. Prüfe den Zustand in der Datenbank erneut. Geh nicht davon aus, dass du der Erste bist. Bestätige, dass der Datensatz noch berechtigt ist.
  4. Führe die Arbeit aus und schreibe einen dauerhaften "done"-Marker in die DB (Status-Update, Ledger-Eintrag, Audit-Row).
  5. Committe und lasse den Lock los. Wenn du einen Session-Lock benutzt hast, unlocke vor der Rückgabe der Verbindung an den Pool.

Beispiel: Zwei App-Server erhalten innerhalb derselben Sekunde „Rechnung #123 genehmigen“. Beide starten, aber nur einer bekommt den Lock für 123. Der Gewinner prüft, ob Rechnung #123 noch pending ist, markiert sie approved, schreibt Audit-/Zahlungsdatensatz und committet. Der zweite Server kann entweder schnell fehlschlagen (Try-Lock) oder wartet kurz, prüft erneut und beendet sich ohne Duplikat zu erzeugen.

Wo Advisory Locks passen: Genehmigungen, Abrechnung, Scheduler

Advisory Locks passen am besten, wenn die Regel einfach ist: für ein bestimmtes Ding darf nur ein Prozess die „entscheidende" Arbeit zur Zeit ausführen. Du behältst deine bestehende Datenbank und App-Logik, fügst aber ein kleines Tor ein, das Rennbedingungen deutlich schwerer macht.

Genehmigungen

Genehmigungen sind klassische Konkurrenzfallen. Zwei Reviewer (oder dieselbe Person per Doppelklick) können innerhalb von Millisekunden auf Genehmigen klicken. Mit einem Lock, der auf die Request-ID schlüsselt, führt nur eine Transaktion die Statusänderung aus. Alle anderen sehen das Ergebnis schnell und können eine klare Meldung wie „bereits genehmigt“ anzeigen.

Das ist häufig in Kundenportalen und Admin-Panels, wo viele Leute dieselbe Warteschlange beobachten.

Abrechnung

Bei Abrechnung brauchst du oft striktere Regeln: ein Zahlungsversuch pro Rechnung, auch wenn Retries auftreten. Ein Netzwerk-Timeout kann den Nutzer dazu bringen, erneut auf Bezahlen zu klicken, oder ein Hintergrund-Retry läuft, während der erste Versuch noch nicht abgeschlossen ist.

Ein Lock auf die Invoice-ID stellt sicher, dass nur ein Pfad gleichzeitig mit dem Payment-Provider spricht. Der zweite Versuch kann „Zahlung in Arbeit“ zurückgeben oder den aktuellen Zahlungsstatus lesen. Das verhindert doppelte Arbeit und reduziert das Risiko von Doppelabbuchungen.

Scheduler und Background-Worker

In Multi-Instance-Setups können Scheduler versehentlich dasselbe Fenster parallel laufen lassen. Ein Lock auf Jobname plus Zeitfenster (z. B. daily-settlement:2026-01-29) sorgt dafür, dass nur eine Instanz es ausführt.

Der gleiche Ansatz funktioniert für Worker, die Items aus einer Tabelle ziehen: sperre auf die Item-ID, damit nur ein Worker es verarbeiten kann.

Gängige Keys sind eine einzelne Genehmigungs-ID, eine einzelne Invoice-ID, ein Jobname plus Zeitfenster, eine Customer-ID für "ein Export zur Zeit" oder ein eindeutiger Idempotency-Key für Retries.

Ein realistisches Beispiel: doppelte Genehmigungen im Portal verhindern

Standardisiere eure Lock-Keys
Erstelle einen gemeinsamen Lock-Key-Helfer in deinem Backend, damit alle Workflows dieselben Schlüssel verwenden.
Backend bauen

Stell dir eine Genehmigungsanfrage im Portal vor: eine Bestellung wartet, und zwei Manager klicken innerhalb derselben Sekunde auf Genehmigen. Ohne Schutz lesen beide "pending" und schreiben beide "approved", wodurch doppelte Audit-Einträge, doppelte Benachrichtigungen oder ausgelöste Folgeprozesse entstehen.

PostgreSQL Advisory Locks geben dir einen einfachen Weg, diese Aktion pro Genehmigung einzeln abzuarbeiten.

Der Ablauf

Wenn die API eine Approve-Aktion erhält, nimmt sie zuerst einen Lock basierend auf der Approval-ID (so können unterschiedliche Genehmigungen weiterhin parallel bearbeitet werden).

Ein gängiges Muster ist: Lock auf approval_id, aktuellen Status lesen, Status updaten, dann einen Audit-Eintrag schreiben — alles in einer Transaktion.

BEGIN;

-- One-at-a-time per approval_id
SELECT pg_try_advisory_xact_lock($1) AS got_lock;  -- $1 = approval_id

-- If got_lock = false, return "someone else is approving, try again".

SELECT status FROM approvals WHERE id = $1 FOR UPDATE;

-- If status != 'pending', return "already processed".

UPDATE approvals
SET status = 'approved', approved_by = $2, approved_at = now()
WHERE id = $1;

INSERT INTO approval_audit(approval_id, actor_id, action, created_at)
VALUES ($1, $2, 'approved', now());

COMMIT;

(Anmerkung: der Codeblock bleibt unverändert.)

Was der zweite Klick erlebt

Die zweite Anfrage kann entweder den Lock nicht bekommen (und liefert schnell „wird bereits verarbeitet“) oder sie bekommt den Lock erst nach Abschluss des ersten, sieht dann aber, dass der Status bereits genehmigt ist und beendet sich ohne Änderung. In beiden Fällen vermeidest du doppelte Verarbeitung und hältst die UI reaktionsfähig.

Zur Fehlersuche logge genug, um jeden Versuch nachzuvollziehen: Request-ID, Approval-ID und berechneter Lock-Key, Actor-ID, Ergebnis (lock_busy, already_approved, approved_ok) und Timing.

Warten, Timeouts und Retries handhaben ohne die App einzufrieren

Ein produktionsreifes Portal ausliefern
Generiere Backend-, Web- und Mobile-Apps aus einem Projekt in AppMaster.
Portal bauen

Auf einen Lock zu warten klingt harmlos, bis es zu einem drehenden Button, einem hängen gebliebenen Worker oder einem Backlog führt, das nie abnimmt. Wenn du den Lock nicht bekommst, schlage schnell fehl, wo ein Mensch wartet, und warte nur dort, wo es sicher ist.

Für Nutzeraktionen: Try-Lock und klare Antwort

Wenn jemand auf Genehmigen oder Bezahlen klickt, blockiere die Anfrage nicht für Sekunden. Nutze Try-Lock, damit die App sofort antworten kann.

Eine praktische Herangehensweise: versuche den Lock; wenn er fehlschlägt, gib eine klare "busy, try again"-Antwort (oder aktualisiere den Item-Zustand). So vermeidest du Timeouts und wiederholte Klicks.

Halte den gesperrten Abschnitt kurz: Zustand validieren, Zustand anwenden, committen.

Für Background-Jobs: blockieren ist OK, aber begrenzen

Für Scheduler und Worker kann Blockieren in Ordnung sein, weil kein Mensch wartet. Trotzdem brauchst du Limits, sonst kann ein langsamer Job eine ganze Flotte aufhalten.

Setze Timeouts, damit ein Worker aufgeben und weiterziehen kann:

SET lock_timeout = '2s';
SET statement_timeout = '30s';
SELECT pg_advisory_lock(123456);

Setze außerdem eine maximale erwartete Laufzeit für den Job selbst. Wenn Abrechnung normalerweise unter 10 Sekunden fertig ist, behandle 2 Minuten als Vorfall. Verfolge Startzeit, Job-ID und wie lange Locks gehalten werden. Wenn dein Job-Runner Abbruch unterstützt, breche Tasks ab, die das Limit überschreiten, damit die Session endet und der Lock freigegeben wird.

Plane Retries bewusst. Wenn ein Lock nicht erworben wird, entscheide: sofort mit Backoff neu planen (und etwas Zufall einbauen), Best-Effort-Arbeit für diesen Zyklus überspringen, oder das Item als kontendiert markieren, wenn wiederholte Fehler Aufmerksamkeit benötigen.

Häufige Fehler, die festhängende Locks oder Duplikate verursachen

Am häufigsten überrascht man sich durch Session-Locks, die nie freigegeben werden. Connection-Pools halten Verbindungen offen, sodass eine Session die Lebenszeit einer Anfrage überdauern kann. Wenn du einen Session-Lock nimmst und vergisst zu unlocken, bleibt der Lock gehalten, bis die Verbindung recycelt wird. Andere Worker warten (oder schlagen fehl) und es ist schwer nachzuvollziehen, warum.

Eine weitere Ursache für Duplikate ist: Sperren, aber den Zustand nicht prüfen. Ein Lock stellt nur sicher, dass ein Worker die kritische Sektion zur Zeit ausführt. Er garantiert nicht, dass der Datensatz noch berechtigt ist. Prüfe immer innerhalb derselben Transaktion erneut (z. B. bestimme, ob pending noch gilt), bevor du änderst.

Lock-Keys bringen Teams ebenfalls durcheinander. Wenn ein Service auf order_id sperrt und ein anderer einen anders berechneten Schlüssel für dieselbe reale Ressource nutzt, hast du zwei Locks. Beide Pfade können gleichzeitig laufen, was ein falsches Sicherheitsgefühl erzeugt.

Lange Lock-Halts sind meist selbst verschuldet. Wenn du langsame Netzwerkaufrufe während des gehaltenen Locks machst (Zahlungsanbieter, E-Mail/SMS, Webhooks), wird aus einer kurzen Leitplanke ein Flaschenhals. Halte den gesperrten Abschnitt auf schnelle DB-Arbeit beschränkt: Zustand validieren, neuen Zustand schreiben, festhalten, und Side-Effects nach dem Commit auslösen.

Advisory Locks ersetzen nicht Idempotenz oder Datenbank-Constraints. Betrachte sie als Ampel, nicht als Beweis-System. Nutze Unique Constraints, wo sie passen, und Idempotency-Keys für externe Aufrufe.

Kurze Checkliste vor dem Rollout

Logik bauen, die Rennen vermeidet
Nutze den Business Process Editor, um kritische Abschnitte kurz und prüfbar zu halten.
Ausprobieren

Behandle Advisory Locks wie einen kleinen Vertrag: im Team sollte jeder wissen, was der Lock bedeutet, was er schützt und was währenddessen erlaubt ist.

Eine kurze Checkliste, die die meisten Probleme auffängt:

  • Ein klarer Lock-Key pro Ressource, dokumentiert und überall wiederverwendet
  • Den Lock erwerben, bevor etwas Irreversibles passiert (Zahlungen, E-Mails, externe API-Aufrufe)
  • Zustand nach dem Lock erneut prüfen und erst dann Änderungen schreiben
  • Den gesperrten Abschnitt kurz und messbar halten (Lock-Wartezeit und Ausführungszeit loggen)
  • Festlegen, was „lock busy" für jeden Pfad bedeutet (UI-Nachricht, Retry mit Backoff, überspringen)

Nächste Schritte: das Muster anwenden und wartbar halten

Wähle einen Bereich, in dem Duplikate am meisten schaden, und fange dort an. Gute erste Ziele sind Aktionen, die Geld kosten oder permanenten Zustand ändern, wie "Rechnung belasten" oder "Anfrage genehmigen". Umschließe nur diesen kritischen Abschnitt mit einem Advisory Lock und erweitere schrittweise, wenn das Verhalten vertrauenswürdig ist.

Füge früh grundlegende Observability hinzu. Logge, wenn ein Worker keinen Lock bekommt, und wie lange gesperrte Arbeit dauert. Wenn Lock-Wartezeiten ansteigen, deutet das meist darauf hin, dass der kritische Abschnitt zu groß ist oder eine langsame Abfrage darin verborgen ist.

Locks funktionieren am besten zusätzlich zur Datensicherheit, nicht stattdessen. Halte klare Status-Felder (pending, processing, done, failed) und stütze sie mit Constraints, wo möglich. Wenn ein Retry im ungünstigsten Moment passiert, kann ein Unique Constraint oder ein Idempotency-Key die zweite Verteidigungslinie sein.

Wenn du Workflows in AppMaster (appmaster.io) baust, kannst du dasselbe Muster anwenden, indem du die kritische Zustandsänderung in einer Transaktion belässt und einen kleinen SQL-Schritt hinzufügst, der vor dem „finalize“-Schritt einen transaktionsbasierten Advisory Lock setzt.

Advisory Locks sind eine gute Lösung, bis du wirklich Queue-Funktionen brauchst (Prioritäten, verzögerte Jobs, Dead-Letter-Handling), du starke Kontention und feinere Parallelismus-Strategien brauchst, du über mehrere Datenbanken ohne gemeinsames Postgres koordinieren musst oder strengere Isolation erforderlich ist. Ziel ist langweilige Zuverlässigkeit: halte das Muster klein, konsistent, sichtbar in Logs und unterstützt durch Constraints.

FAQ

Wann sollte ich PostgreSQL advisory locks benutzen statt nur auf meine App-Logik zu vertrauen?

Verwende einen Advisory Lock, wenn für eine bestimmte Einheit der Arbeit nur „ein Akteur zur Zeit“ zulässig sein soll — zum Beispiel eine Genehmigung, eine Rechnungszahlung oder ein geplantes Zeitfenster. Es ist besonders nützlich, wenn mehrere App-Instanzen dasselbe Objekt berühren können und du keinen separaten Locking-Service hinzufügen willst.

Worin unterscheiden sich advisory locks von SELECT ... FOR UPDATE Row Locks?

Row Locks schützen tatsächliche Tabellenzeilen und sind ideal, wenn die ganze Operation sauber auf ein einzelnes Zeilen-Update abgebildet werden kann. Advisory Locks schützen einen von dir gewählten Schlüssel, deswegen eignen sie sich auch dann, wenn der Workflow viele Tabellen berührt, externe Dienste aufruft oder beginnt, bevor die finale Zeile existiert.

Sollte ich transaktions- oder session-level advisory locks verwenden?

Wenn möglich, setze pg_advisory_xact_lock (Transaktions-Level) als Default für Request/Response-Aktionen, weil er automatisch beim Commit oder Rollback freigegeben wird. Verwende pg_advisory_lock (Session-Level) nur, wenn der Lock tatsächlich über die Lebenszeit einer Transaktion hinaus bestehen muss und du sicherstellen kannst, dass pg_advisory_unlock zuverlässig aufgerufen wird.

Ist es besser, auf einen Lock zu warten oder einen Try-Lock zu verwenden?

Für UI-gesteuerte Aktionen empfiehlt sich ein Try-Lock (pg_try_advisory_xact_lock), damit die Anfrage schnell fehlschlagen und eine klare "Bereits in Verarbeitung"-Antwort liefern kann. Für Hintergrundjobs kann ein blockierender Lock in Ordnung sein, aber setze lock_timeout, damit ein hängen bleibender Task nicht die ganze Flotte ausbremst.

Worauf sollte ich sperren: Record ID, Customer ID oder etwas anderes?

Sperre das kleinstmögliche Objekt, das nicht zweimal gleichzeitig ausgeführt werden darf — meist "eine Rechnung" oder "eine Genehmigungsanfrage". Zu grobe Sperren (z. B. pro Kunde) reduzieren den Durchsatz; zu enge oder uneinheitlich definierte Schlüssel können Duplikate nicht verhindern.

Wie wähle ich einen Lock-Key, sodass alle Dienste genau denselben verwenden?

Wähle ein stabiles Schlüssel-Format und verwende es überall dort, wo dieselbe kritische Aktion ausgeführt werden kann. Eine gängige Methode sind zwei Integer: ein fester Namespace für den Workflow plus die Entity-ID, damit unterschiedliche Workflows sich nicht gegenseitig blockieren, aber Aktionen zum selben Objekt koordiniert werden.

Ersetzen advisory locks Idempotency-Checks oder Unique Constraints?

Nein. Ein Lock verhindert nur gleichzeitige Ausführung; er beweist nicht, dass eine Operation sicher wiederholt werden kann. Prüfe den Zustand innerhalb der Transaktion erneut (z. B. ob das Item noch pending ist) und nutze dort, wo es passt, Unique Constraints oder Idempotency-Keys.

Was sollte ich innerhalb des gesperrten Abschnitts tun, um alles nicht zu verlangsamen?

Halte den gesperrten Abschnitt kurz und datenbankfokussiert: Lock setzen, Anspruch prüfen, neuen Zustand schreiben und committen. Langsame Nebeneffekte (Zahlungen, E-Mails, Webhooks) sollten nach dem Commit oder über ein Outbox-Muster ausgelöst werden, damit der Lock nicht während Netzwerkwartezeiten gehalten wird.

Warum wirken advisory locks manchmal „festhängend“, obwohl die Anfrage beendet ist?

Die häufigste Ursache sind Session-Locks, die durch Connection-Pools hängen bleiben, weil ein Unlock vergessen wurde. Bevorzuge Transaktions-Locks; wenn Session-Locks nötig sind, stelle sicher, dass pg_advisory_unlock zuverlässig ausgeführt wird, bevor die Verbindung zurück in den Pool geht.

Was sollte ich loggen oder überwachen, damit ich weiß, dass advisory locks funktionieren?

Logge die Entity-ID und den berechneten Lock-Key, ob der Lock erworben wurde, wie lange das Warten gedauert hat und wie lange die Transaktion lief. Protokolliere außerdem das Ergebnis wie lock_busy, already_processed oder processed_ok, damit du Kontention von echten Duplikaten unterscheiden kannst.

Einfach zu starten
Erschaffe etwas Erstaunliches

Experimentieren Sie mit AppMaster mit kostenlosem Plan.
Wenn Sie fertig sind, können Sie das richtige Abonnement auswählen.

Starten