Skip to content
GitHubRSS

Schema Overview

BigBrotr uses PostgreSQL 16 with a schema designed around immutability, content-addressed deduplication, and cascade atomicity. All mutations go through stored functions. All timestamps are stored as BIGINT Unix epoch seconds, and binary identifiers use BYTEA.

relay event
├─ url (PK, TEXT) ├─ id (PK, BYTEA)
├─ network (TEXT) ├─ pubkey (BYTEA)
└─ discovered_at (BIGINT) ├─ created_at (BIGINT)
│ ├─ kind (INTEGER)
│ ├─ tags (JSONB)
│ ├─ tagvalues (TEXT[], generated)
│ ├─ content (TEXT)
│ └─ sig (BYTEA)
│ │
├──────────┐ ┌───────────────┘
▼ ▼ ▼
relay_metadata event_relay
├─ relay_url (FK) ├─ event_id (FK → event)
├─ metadata_id (FK) ├─ relay_url (FK → relay)
├─ metadata_type(FK) └─ seen_at (BIGINT)
└─ generated_at
metadata service_state
├─ id (PK, BYTEA) ├─ service_name (TEXT)
├─ metadata_type (PK,TEXT) ├─ state_type (TEXT)
└─ data (JSONB) ├─ state_key (TEXT)
├─ state_value (JSONB)
└─ updated_at (BIGINT)

The canonical relay registry. Every validated relay has exactly one row.

ColumnTypeDescription
urlTEXT (PK)WebSocket URL (wss:// or ws://)
networkTEXTNetwork type: clearnet, tor, i2p, loki, local
discovered_atBIGINTUnix epoch timestamp when the relay was first discovered

Nostr events identified by their SHA-256 event ID.

ColumnTypeDescription
idBYTEA (PK)Nostr event ID (SHA-256 hash, binary)
pubkeyBYTEAAuthor’s public key (binary)
created_atBIGINTEvent creation Unix timestamp
kindINTEGEREvent kind number
tagsJSONBEvent tags array
tagvaluesTEXT[]Generated column: searchable tag values extracted via tags_to_tagvalues()
contentTEXTEvent content
sigBYTEASchnorr signature (binary)

Junction table tracking which relays have which events.

ColumnTypeDescription
event_idBYTEA (FK → event)Event ID
relay_urlTEXT (FK → relay)Relay URL
seen_atBIGINTUnix timestamp when event was first seen on this relay

Composite PK: (event_id, relay_url). Both foreign keys cascade on delete.

Content-addressed metadata store. Same data always produces the same ID via SHA-256.

ColumnTypeDescription
idBYTEA (composite PK)SHA-256 hash of canonical JSON data field
metadata_typeTEXT (composite PK)Metadata type (nip11_info, nip66_rtt, nip66_ssl, etc.)
dataJSONBMetadata payload

The composite primary key (id, metadata_type) means deduplication operates within each metadata type.

Junction table linking relays to their metadata with timestamps.

ColumnTypeDescription
relay_urlTEXT (FK → relay)Relay URL
metadata_idBYTEA (compound FK)References metadata.id
metadata_typeTEXT (compound FK)References metadata.metadata_type
generated_atBIGINTUnix timestamp when metadata was generated

Composite PK: (relay_url, generated_at, metadata_type). Compound FK (metadata_id, metadata_type) references metadata(id, metadata_type).

Service checkpoint storage for cursor-based operations and service state tracking.

ColumnTypeDescription
service_nameTEXTService name (seeder, finder, validator, etc.)
state_typeTEXTState type identifier
state_keyTEXTState key (e.g., relay URL, cursor name)
state_valueJSONBState payload (default '{}')
updated_atBIGINTUnix timestamp of last update

Composite PK: (service_name, state_type, state_key).

Validation happens in the Python model layer, not in the database. This keeps the schema simple and avoids duplicating validation logic.

All timestamps are stored as BIGINT Unix epoch seconds, not TIMESTAMPTZ. This avoids timezone ambiguity and aligns with the Nostr protocol’s timestamp format.

Event IDs, public keys, and signatures are stored as BYTEA (binary), not hex-encoded TEXT. Metadata IDs (SHA-256 hashes) are also BYTEA. This is more space-efficient and matches the protocol’s binary format.

SHA-256 hashes for content-addressed metadata are computed in Python, not via pgcrypto. This ensures consistency across the application and makes the hashing algorithm explicit in the codebase.

No raw INSERT/UPDATE/DELETE statements in application code. All mutations go through the 25 stored functions, which accept bulk array parameters for efficiency.

Two cascade functions handle atomic multi-table inserts:

  • event_relay_insert_cascade — inserts across relay, event, and event_relay in a single call.
  • relay_metadata_insert_cascade — inserts across relay, metadata, and relay_metadata in a single call.