From 307372fdfe7cae398fa4bd768ec27a106efef3fb Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Fri, 13 Mar 2026 20:17:32 +0100 Subject: [PATCH] docs: lock postgres architecture defaults before implementation --- docs/ARCH.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/ARCH.md b/docs/ARCH.md index bd9feab..0a9c862 100644 --- a/docs/ARCH.md +++ b/docs/ARCH.md @@ -109,9 +109,9 @@ Ordered stages: 2. `id` recomputation and signature verification 3. NIP semantic checks (timestamps, tag forms, size limits) 4. Policy checks (banlists, kind allowlists, auth-required, NIP-70, PoW) -5. Storage write (or no-store for ephemeral policy) +5. Storage write (including ephemeral events with short TTL retention) 6. Live fanout to matching subscriptions -7. Return canonical `OK` response with machine prefix when needed +7. Return canonical `OK` response with machine prefix when needed, **only after durable DB commit succeeds** ### 5.4 Subscription index + fanout @@ -151,7 +151,7 @@ Initial adapter: `Parrhesia.Storage.Adapters.Postgres` with Ecto. Schema outline: -- `events` (id PK, pubkey, created_at, kind, content, sig, d_tag, deleted_at, expires_at) +- `events` (partitioned by `created_at`; `id`, `pubkey`, `sig` stored in compact binary form; `kind`, `content`, `d_tag`, `deleted_at`, `expires_at`) - `event_tags` (event_id, name, value, idx) - moderation tables (banned/allowed pubkeys, banned events, blocked IPs) - relay/group membership tables @@ -163,13 +163,23 @@ Indexing strategy: - `(pubkey, created_at DESC)` - `(created_at DESC)` - `(name, value, created_at DESC)` on `event_tags` -- partial/unique indexes for replaceable and addressable semantics +- partial/unique indexes and deterministic upsert paths for replaceable `(pubkey, kind)` and addressable `(pubkey, kind, d_tag)` semantics +- targeted partial indexes for high-traffic single-letter tags (`e`, `p`, `d` first), with additional tag indexes added from production query telemetry Retention strategy: -- Optional table partitioning by time for hot pruning +- Mandatory time partitioning for `events` (monthly default, configurable) +- Partition-aligned pruning for expired/deleted data where possible - Periodic purge job for expired/deleted tombstoned rows +### 6.3 Postgres operating defaults (locked before implementation) + +- **Durability invariant:** relay returns `OK` only after transaction commit for accepted events. +- **Pool separation:** independent DB pools/queues for ingest writes, REQ/COUNT reads, and maintenance/admin operations. +- **Server-side guardrails:** enforce `max_filter_limit`, max filters per REQ, max entries for `ids`/`authors`/`#tag`, and bounded `since/until` windows. +- **Deterministic conflict resolution:** tie-break replaceable/addressable collisions by `created_at`, then lexical `id` (NIP-01-consistent). +- **Conformance lock-in:** treat `since <= created_at <= until`, newest-first initial query ordering, and single `EOSE` emission as fixed behavior. + ## 7) Feature-specific implementation notes ### 7.1 NIP-11 @@ -239,7 +249,8 @@ Targets (initial): 5. **NIP conformance tests**: machine-prefix responses, ordering, EOSE behavior 6. **MLS scenario tests**: keypackage/group-event acceptance and policy handling 7. **Performance tests**: soak + burst + large fanout profiles -8. **Fault-injection tests**: DB outage, slow query, connection churn, node restart +8. **Query-plan regression tests**: representative `EXPLAIN (ANALYZE, BUFFERS)` checks for core REQ/COUNT shapes +9. **Fault-injection tests**: DB outage, slow query, connection churn, node restart ## 10) Implementation principles