feat(laeufe): Live-Aktualisierung per Ereignisstrom statt Polling (#53)
Die Abarbeitungsansicht fragte alle zehn Sekunden nach dem Zustand. Jetzt melden vier Datenbank-Trigger `NOTIFY lauf_geaendert, '<laufId>'`, eine LISTEN-Verbindung je Prozess hoert zu, und der Route Handler schiebt weiter. Gemessen: der Haken des Kollegen ist nach 485 ms beim Zuschauer (am Handy 487 ms), vorher bis zu zehn Sekunden. Zwanzig offene Laschen kosten nichts mehr statt zwanzig Abfragen je zehn Sekunden. Das Ereignis kommt aus der Datenbank und nicht aus dem Dienst, aus demselben Grund, aus dem auch die Regeln dort stehen: ein Trigger sieht auch den Seed, eine Migration und die Reparatur per psql. Und NOTIFY erreicht jede Instanz der Anwendung, ohne einen zweiten Dienst. Welche Tabellen melden, haengt an dem, was `laufZustand` liefert — nicht daran, was das Issue vorgeschlagen hat. Mit Triggern nur auf Schrittergebnis und Lauf waere der Strom ein **Rueckschritt** gewesen: das Polling verglich den ganzen Stand und sah deshalb auch Kommentare, ein Strom sieht nur, was gemeldet wird. Der Kommentar des Kollegen waere nie angekommen, und die Seite haette ausgesehen wie immer. Es melden jetzt vier Tabellen, und `laufZustand` traegt `anzahlAnhaenge` — ein Ereignis ohne Entsprechung im Zustand loest nichts aus. Das Polling bleibt als Rueckfall an **derselben** Adresse, unterschieden nur am Accept-Kopf: eine Rechtepruefung, ein Lesemodell, eine Nutzlast. Ein zweiter Endpunkt waere eine zweite Stelle, an der jemand rechtErforderlich vergessen kann. Bei erreichter Obergrenze antwortet der Handler gewoehnlich mit JSON; EventSource meldet darauf einen Fehler und versucht es nicht wieder, also faellt der Client von sich aus zurueck — ohne Sonderfall. Nebenbei zwei Fehler in docs/betrieb/reverse-proxy.md behoben, beide aus #52: `client_max_body_size 8m` bei `ANHANG_MAX_MB=10` haette ein 10-MB-Foto mit 413 abgewiesen, bevor die eigene Meldung zum Zuge kommt. Closes #53 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3cbd5026f8
commit
4aa79c93ff
@@ -0,0 +1,84 @@
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- HANDGESCHRIEBEN. Prisma erzeugt hier zusaetzlich Loeschbefehle fuer die
|
||||
-- zusammengesetzten Fremdschluessel (Mandantengrenze), die Trigramm-Indizes
|
||||
-- (Archivsuche), die partiellen eindeutigen Indizes und alle Trigger: es kennt
|
||||
-- sie nicht, weil sie sich im Schema nicht ausdruecken lassen. Diese
|
||||
-- Loeschbefehle sind bewusst gestrichen.
|
||||
--
|
||||
-- tests/datenbankregeln.test.ts prueft, dass alle diese Objekte vorhanden sind –
|
||||
-- ein versehentlich stehengelassener Loeschbefehl faellt dort sofort auf.
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- Live-Aktualisierung per SSE statt Polling (#033).
|
||||
--
|
||||
-- Die Datenbank sagt selbst, wenn sich an einem Lauf etwas geaendert hat:
|
||||
-- `NOTIFY lauf_geaendert, '<laufId>'`. Der Route Handler haelt eine
|
||||
-- LISTEN-Verbindung und schiebt weiter.
|
||||
--
|
||||
-- **Warum in der Datenbank und nicht im Dienst.** Ein Ereignis, das der Service
|
||||
-- verschickt, entsteht nur auf dem Weg durch den Service. Die Regeln dieses
|
||||
-- Projekts stehen aber absichtlich auch in der Datenbank, weil jemand an der
|
||||
-- Service-Schicht vorbei schreiben kann — der Seed, eine Migration, eine
|
||||
-- Reparatur per psql. Ein Trigger sieht all das.
|
||||
--
|
||||
-- **Warum es mit mehreren Instanzen funktioniert.** NOTIFY geht an alle
|
||||
-- Verbindungen, die LISTEN auf dem Kanal haben, also an jede Instanz der
|
||||
-- Anwendung. Ein zweiter Dienst (Redis, ein Nachrichtenbus) waere dafuer nicht
|
||||
-- noetig.
|
||||
--
|
||||
-- **Zugestellt wird erst beim COMMIT.** Das ist hier genau richtig: ein
|
||||
-- Abonnent, der eine noch nicht festgeschriebene Aenderung meldet, wuerde die
|
||||
-- Seite mit Daten neu bauen lassen, die es gleich wieder nicht gibt.
|
||||
|
||||
CREATE OR REPLACE FUNCTION laufzettel_lauf_geaendert() RETURNS trigger AS $$
|
||||
DECLARE
|
||||
betroffener TEXT;
|
||||
BEGIN
|
||||
/*
|
||||
* Bei DELETE gibt es kein NEW. Der Kanalname ist fest, die Nutzlast nicht –
|
||||
* deshalb pg_notify() und nicht NOTIFY, das keine Variablen nimmt.
|
||||
*/
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
betroffener := OLD."laufId";
|
||||
ELSE
|
||||
betroffener := NEW."laufId";
|
||||
END IF;
|
||||
|
||||
IF betroffener IS NOT NULL THEN
|
||||
PERFORM pg_notify('lauf_geaendert', betroffener);
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Derselbe Rumpf fuer die Laufzeile selbst: dort heisst der Schluessel "id".
|
||||
CREATE OR REPLACE FUNCTION laufzettel_lauf_geaendert_selbst() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('lauf_geaendert', NEW."id");
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
/*
|
||||
* **Welche Tabellen melden, haengt daran, was `laufZustand` liefert** — nicht
|
||||
* daran, was sich "irgendwie" aendert. Das Lesemodell traegt Status,
|
||||
* Schrittzustaende, die Zahl der Kommentare und (seit #033) die Zahl der
|
||||
* Anhaenge. Wuerde eine davon nicht melden, waere der Strom gegenueber dem
|
||||
* Polling, das alle zehn Sekunden vollstaendig vergleicht, ein **Rueckschritt**:
|
||||
* ein neuer Kommentar kaeme nie an.
|
||||
*/
|
||||
CREATE TRIGGER "Lauf_geaendert_melden"
|
||||
AFTER UPDATE ON "Lauf"
|
||||
FOR EACH ROW EXECUTE FUNCTION laufzettel_lauf_geaendert_selbst();
|
||||
|
||||
CREATE TRIGGER "Schrittergebnis_geaendert_melden"
|
||||
AFTER INSERT OR UPDATE OR DELETE ON "Schrittergebnis"
|
||||
FOR EACH ROW EXECUTE FUNCTION laufzettel_lauf_geaendert();
|
||||
|
||||
CREATE TRIGGER "Kommentar_geaendert_melden"
|
||||
AFTER INSERT OR UPDATE OR DELETE ON "Kommentar"
|
||||
FOR EACH ROW EXECUTE FUNCTION laufzettel_lauf_geaendert();
|
||||
|
||||
CREATE TRIGGER "Anhang_geaendert_melden"
|
||||
AFTER INSERT OR DELETE ON "Anhang"
|
||||
FOR EACH ROW EXECUTE FUNCTION laufzettel_lauf_geaendert();
|
||||
Reference in New Issue
Block a user