server/docs/infra/synkronisering.md
vegard 8b58d434e9 SpacetimeDB som cache foran PG: arkitekturendring
PG er autoritativ, SpacetimeDB er varm cache. Frontend snakker
kun med SpacetimeDB, worker håndterer toveissynk.

Fase 1 — SpacetimeDB-modul:
- delete_message med SyncOutbox-event
- edit_message reducer
- MessageReaction tabell + add/remove_reaction reducers
- load_messages med JSON-parsing (erstatter pipe-format)
- clear_channel reducer for duplikat-fri warmup
- load_reactions reducer

Fase 2 — Worker:
- warmup.rs: PG→ST oppvarming ved oppstart (100 msg/kanal)
- sync.rs: håndter delete/update/reaction actions
- Sync-intervall redusert til 1s

Fase 3 — Frontend:
- spacetime.svelte.ts: ren SpacetimeDB-adapter, ingen PG-hybrid
- ChatConnection interface med edit/delete/react metoder
- ChatBlock bruker chat.edit/delete/react direkte
- PG-adapter som readonly fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 02:09:33 +01:00

9.4 KiB

Infrastruktur: PostgreSQL ↔ SpacetimeDB Synkronisering

Filsti: docs/infra/synkronisering.md

1. Konsept

SpacetimeDB gir sanntidsopplevelsen, PostgreSQL er langtidsminnet. Denne spec-en definerer hvordan data flyter mellom dem, hvem som eier sannheten, og hva som skjer ved feil.

1.1 Grunnregel: SpacetimeDB er en ren sanntidsbuffer

SpacetimeDB skal aldri være eneste lagringssted for data med varig verdi. All data som SpacetimeDB holder skal enten:

  1. Allerede eksistere i PostgreSQL (read-only cache, f.eks. aktør-navn for autocomplete), eller
  2. Synkes til PostgreSQL innen ~5 sekunder (chat, kanban, markører).

Konsekvens for design: Ethvert SpacetimeDB-modul-skjema skal ha en tilsvarende PostgreSQL-tabell som kan fungere som drop-in erstatning dersom SpacetimeDB fjernes. Frontend-kode som leser fra SpacetimeDB skal kunne peke om til et SvelteKit SSE-endepunkt + REST uten arkitekturendring.

1.2 PostgreSQL-fallback-prinsippet

Dersom SpacetimeDB fjernes fra stacken, skal systemet fungere med følgende erstatning:

  • Sanntidsoppdateringer: PostgreSQL LISTEN/NOTIFY → SvelteKit Server-Sent Events (SSE)
  • Skriving: Direkte til PostgreSQL via SvelteKit server-side
  • Autocomplete/cache: PostgreSQL-spørringer med cursor-basert paginering

Denne fallbacken trenger ikke implementeres på forhånd, men SpacetimeDB-moduler skal designes slik at fallbacken forblir triviell.

2. Strategi: Event-drevet med kort forsinkelse

SpacetimeDB-modulene (Rust) produserer persisterings-events ved dataendringer. En Rust-worker konsumerer disse og skriver til PostgreSQL med ~1 sekunds intervall.

Akseptabelt datatap: Maks 1 sekund ved hard krasj av SpacetimeDB. Dette er akseptabelt for chat, kanban og show notes.

Unntak — kritiske events: Aha-markører fra studioet (live-innspilling) er tidssensitive og vanskelige å gjenskape. Disse bør flushes til PG umiddelbart (ikke batched) via en dedikert sync_critical()-funksjon som skriver direkte til PG i stedet for via sync_outbox. Alternativt kan SpacetimeDB-modulen skrive kritiske events til sin egen WAL/disk umiddelbart. Hvilke event-typer som er "kritiske" defineres per workspace i workspaces.settings.

3. Dataflyt

┌──────────────┐     events      ┌──────────────┐    batch write    ┌──────────────┐
│  SpacetimeDB │ ──────────────► │  Rust Worker  │ ────────────────► │  PostgreSQL  │
│  (sanntid)   │                 │  (sync_to_pg) │                   │  (persistent)│
└──────────────┘                 └──────────────┘                    └──────────────┘

┌──────────────┐    oppvarming (oppstart / reconnect)    ┌──────────────┐
│  PostgreSQL  │ ──────────────────────────────────────► │  SpacetimeDB │
└──────────────┘                                         └──────────────┘

4. Eierskapsmodell

Data Autoritativ kilde Synkretning Merknad
Chatmeldinger SpacetimeDB → PG (event, batched)
Kanban-posisjon SpacetimeDB → PG (event)
Show notes SpacetimeDB → PG (event)
Live studio-markører SpacetimeDB → PG (event)
Kunnskapsgraf PostgreSQL → SpacetimeDB (oppvarming) Read-only i SpacetimeDB
Episodemetadata PostgreSQL Ingen synk
Brukerkontoer PostgreSQL (Authentik) Ingen synk
Statistikk PostgreSQL Ingen synk
Valgomat TBD TBD Konseptet må modnes. Mulig PG-autoritativ med SpacetimeDB som serveringslag

5. Mekanisme

5.1 SpacetimeDB → PostgreSQL (persistering)

  • SpacetimeDB-modulene kaller en intern emit_sync_event()-funksjon ved relevante dataendringer
  • Events bufres i en SpacetimeDB-tabell (sync_outbox) med tidsstempel og payload
  • Rust-workeren poller sync_outbox hvert ~5 sekund, leser alle usynkede events, skriver til PostgreSQL i én transaksjon, og markerer dem som synket
  • Ved PG-nedetid: events akkumuleres i sync_outbox. Workeren prøver igjen ved neste poll. Ingen data tapes så lenge SpacetimeDB kjører

5.2 PostgreSQL → SpacetimeDB (oppvarming)

  • Ved oppstart (eller reconnect) av SpacetimeDB laster Rust-workeren aktive data fra PG:
    • Aktive temaer med siste N chatmeldinger
    • Kanban-state for pågående episoder
    • Aktør/Tema-navn for autocomplete (read-only cache)
  • Dette er en enveis-last, ikke kontinuerlig synk. Kunnskapsgrafen oppdateres i SpacetimeDB kun ved oppstart eller eksplisitt refresh

6. Feilhåndtering

  • SpacetimeDB krasjer: Data siden siste synk (~5 sek) tapes. Ved restart oppvarmes fra PG
  • PostgreSQL nede: Sanntidsfunksjoner fortsetter å fungere. sync_outbox vokser. Workeren logger advarsler. Ved PG-recovery synkes backloggen automatisk
  • Rust-worker krasjer: sync_outbox akkumuleres. Ved restart plukker workeren opp der den slapp (usynkede events har ingen markering)

7. Workspace-isolasjon

SpacetimeDB-synkronisering er fullstendig workspace-scopet:

  • SpacetimeDB-tilkoblinger bærer workspace_id som context-token. Modulen partisjonerer minnet og kringkaster kun til klienter i samme workspace.
  • sync_outbox-events inkluderer workspace_id i payloaden, slik at Rust-workeren skriver til riktig silo i PostgreSQL.
  • Ved oppvarming (PG → SpacetimeDB) laster workeren kun data for den aktuelle workspace-en.

8. Konflikthåndtering

8.1 Strategi: Last-Write-Wins (LWW) med reservasjoner

SpacetimeDB er autoritativ for sanntidsdata. Konflikter kan oppstå i to scenarioer:

Scenario A — Samtidig redigering i SpacetimeDB: SpacetimeDB er single-threaded per modul. Reducer-funksjoner serialiseres automatisk. Ingen klassiske race conditions.

Scenario B — PG-oppvarming kolliderer med fersk SpacetimeDB-state: Ved restart av SpacetimeDB lastes data fra PG (oppvarming). Hvis en klient skriver til SpacetimeDB under oppvarming, kan PG-dataen overskrive ferske endringer.

Løsning: Oppvarming setter et warming_up-flagg i SpacetimeDB-modulen. Klienttilkoblinger aksepteres, men skrivinger bufres til oppvarmingen er fullført. Buffrede skrivinger appliseres deretter i rekkefølge.

8.2 Kanban: Posisjonskonflikter

Kanban-kort har en position-kolonne (float). To brukere som drar kort samtidig kan skape overlappende posisjoner. Løsning: SpacetimeDB-modulen re-beregner posisjoner (midtpunkts-strategi) og kringkaster oppdatert rekkefølge til alle klienter.

8.3 Chat: Ingen konflikter

Meldinger er append-only. Redigering av egne meldinger er last-write-wins — akseptabelt fordi kun én bruker eier meldingen.

9. Implementeringsstatus (mars 2026)

Ferdig

  • SpacetimeDB som cache foran PG: PG er autoritativ, SpacetimeDB er varm cache. Frontend snakker kun med SpacetimeDB.
  • SpacetimeDB Rust-modul (spacetimedb/src/lib.rs): ChatMessage, MessageReaction og SyncOutbox-tabeller. Reducers: send_message, delete_message, edit_message, add_reaction, remove_reaction, load_messages, load_reactions, clear_channel, mark_synced.
  • Worker warmup (worker/src/warmup.rs): Ved oppstart lastes siste 100 meldinger + reaksjoner per kanal fra PG → SpacetimeDB. Kanaler ryddes først med clear_channel for å unngå duplikater.
  • Worker sync (worker/src/sync.rs): Poller sync_outbox hvert 1. sekund. Håndterer insert/delete/update for meldinger og insert/delete for reaksjoner.
  • SpacetimeDB-adapter (web/src/lib/chat/spacetime.svelte.ts): Ren SpacetimeDB-adapter. Ingen PG API-kall. Bruker onInsert/onUpdate/onDelete callbacks for sanntid. Reaksjoner bygges fra message_reaction-tabellen.
  • PG-fallback (web/src/lib/chat/pg.svelte.ts): Brukes kun når SpacetimeDB ikke er konfigurert. Markert som readonly: true.
  • Adapter-mønster: ChatConnection-interface med send, edit, delete, react metoder. Factory velger basert på env-variabel.

Gjenstår

  • Workspace-partisjonering (§7): SpacetimeDB-modulen har workspace_id-felt men bruker ikke workspace-token på tilkobling ennå.
  • Pin/konvertering via SpacetimeDB: Pin og kanban/kalender-konvertering går fortsatt direkte til PG API.
  • Lazy warmup per kanal: Alle aktive kanaler oppvarmes ved oppstart. Kan optimaliseres til per-kanal ved tilkobling.

10. Instruks for Claude Code

  • sync_outbox-tabellen i SpacetimeDB bør ha et synced-flagg og created_at-tidsstempel
  • Workeren skal bruke jobbkø-infrastrukturen (se docs/infra/jobbkø.md) for sin egen helse/observabilitet, men selve pollingen er en egen loop — ikke en vanlig jobb i køen
  • Hold sync-payloaden enkel: { "table": "chat_messages", "action": "insert", "data": {...} } — workeren mapper dette til riktig PG-tabell
  • Ikke optimaliser for store datamengder ennå. Enkle INSERTs er bra nok til volumet stabiliserer seg
  • Alle sync-payloads må inkludere workspace_id. Workeren bruker dette til å sette RLS-kontekst (SET app.current_workspace_id) før PG-skriving, eller kjører som superuser og filtrerer eksplisitt