Files
racket-wiki/architecture/pages/data-en-versies.md
T

5.5 KiB

Data, transacties en versiebeheer

PostgreSQL als bron van waarheid

De database bevat zowel de huidige toestand als de auditgeschiedenis. De browsercache, Undo/Redo-stacks en ingesloten CMaps zijn afgeleide of tijdelijke weergaven en mogen nooit als enige bron van inhoud gelden.

Tabel Betekenis
users Identiteit, profiel, rol, status en wachtwoordhash
sessions Gehashte sessietokens, CSRF-token en vervaltijd
pages Actuele pagina, namespace, slug, tags, versieteller en zoekvector
page_versions Volledige onveranderlijke snapshots van titel, Markdown en tags
page_aliases Oude adressen die naar dezelfde pagina-id blijven verwijzen
todo_items Afgeleide index van huidige todo(...)-markeringen
bookmarks Gebruikersspecifieke verwijzingen naar pagina-id's
attachments Metadata en volledige binaire inhoud van uploads
attachment_references Huidige en historische verwijzingen vanuit paginaversies
concept_maps Actuele titel, JSONB-document en versieteller per CMap
concept_map_versions Volledige onveranderlijke CMap-snapshots
password_reset_tokens Gehashte, tijdelijke en eenmalige herstelcodes
wiki_settings Beheerinstellingen, momenteel vooral e-mail
wiki_schema Geïnstalleerde migratieversie

Pagina-identiteit

Een pagina wordt intern door pages.id geïdentificeerd. Het externe adres is namespace:slug, of alleen slug in de rootnamespace. Namespace en slug zijn afzonderlijke kolommen en samen uniek. Titelwijziging verandert de slug niet automatisch. Bij een echte adreswijziging blijft het oude adres als alias naar dezelfde id bestaan.

Deze scheiding voorkomt dat links bij iedere redactionele titelwijziging breken. Code die relaties bewaart, gebruikt waar mogelijk de id; Markdown en CMaps gebruiken het externe, leesbare paginareferentieformaat.

Schrijftransactie van een pagina

Een paginaopslag is één atomaire transactie:

  1. Zoek en vergrendel de actuele pagina met FOR UPDATE.
  2. Vergelijk current_version met de basisversie van de editor.
  3. Verhoog de versie en werk de actuele projectie bij.
  4. Voeg dezelfde inhoud toe aan page_versions.
  5. Bouw de todo-index voor deze pagina opnieuw op.
  6. Vervang huidige bijlageverwijzingen en leg historische verwijzingen voor de nieuwe versie vast.
  7. Commit alles, of niets.

Een CMap-opslag volgt hetzelfde kernpatroon: rij vergrendelen, versienummer vergelijken, actuele JSONB bijwerken en dezelfde volledige toestand aan concept_map_versions toevoegen.

Waarom volledige snapshots

Volledige snapshots zijn eenvoudig te begrijpen, herstellen en vergelijken. Er is geen keten van patches nodig om versie 37 te reconstrueren en een defecte diff kan de historie niet onleesbaar maken. Voor de verwachte wikiomvang is deze eenvoud belangrijker dan maximale opslagcompactheid.

De prijs is lineaire databasegroei met het aantal versies maal de documentgrootte. Vooral autosave van grote CMaps kan daardoor op termijn veel JSONB opslaan. Bewaarbeleid of deduplicatie hoort pas te worden ontworpen nadat echte datagroei is gemeten; zie Verwachte performance.

Afgeleide gegevens

pages.search_document, todo_items en attachment_references.current_reference zijn afgeleide gegevens. Zij moeten in dezelfde transactie als hun bron worden aangepast. Een los herstelcommando mag ze opnieuw kunnen opbouwen, maar gewone reads mogen niet afhankelijk zijn van een toevallig later achtergrondproces.

De gecombineerde navigatiegraaf is momenteel volledig afgeleid in de browser. Zij wordt niet als databasegraaf bewaard.

Bijlagen

Een upload hoort bij de id van de eigenaarpagina en krijgt een veilige opgeslagen naam. PostgreSQL bewaart zowel metadata als bytes. Markdown verwijst via de pagina en opgeslagen naam. Huidige en historische referenties worden afzonderlijk gevolgd, zodat beheer kan zien of een bestand alleen nog in oude versies voorkomt.

Het huidige downloadpad leest de volledige BYTEA in geheugen voordat de response wordt gemaakt. Dit is eenvoudig en correct voor gebruikelijke wiki-afbeeldingen en documenten, maar vraagt begrenzing of streaming wanneer grote bestanden een doel worden.

Migraties

private/migrations.rkt bevat een oplopende keten. Iedere stap moet herhaalbare SQL gebruiken waar dat nodig is, de nieuwe schemaversie pas na succesvolle omzetting registreren en binnen de omvattende transactie blijven. Oude stappen worden niet achteraf inhoudelijk herschreven, omdat bestaande installaties ze al uitgevoerd kunnen hebben.

Een nieuw schema vereist:

  1. een nieuwe migratiestap;
  2. aanpassing van migrate-database!;
  3. controles voor een verse database én een upgrade vanaf de vorige versie;
  4. documentatie van nieuwe tabellen, kolommen, indexen en herstelgedrag;
  5. een back-up- en terugrolnotitie wanneer de omzetting niet triviaal omkeerbaar is.

Invarianten

De volgende regels mogen niet alleen in de browser staan:

  • iedere actuele versie heeft een overeenkomstige onveranderlijke snapshot;
  • versienummers nemen per object strikt toe;
  • een stale editor overschrijft geen nieuwere toestand;
  • e-mailadressen zijn hoofdletterongevoelig uniek wanneer ingevuld;
  • sessies en herstelcodes worden alleen gehasht opgeslagen;
  • een bijlage die nog vanuit huidige inhoud wordt gebruikt, wordt niet als orphan verwijderd;
  • een gearchiveerde pagina of CMap verschijnt niet in normale lees- en lijstoperaties.

Zie Beveiliging en vertrouwen voor geheimen en Testbaarheid voor de tests die deze invarianten moeten afdekken.