Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1574,10 +1574,13 @@ jamtart/
│ └── bin/
│ └── tart-dash.rs # Terminal UI dashboard
├── migrations/
│ ├── 001_postgres_schema.sql # Initial schema
│ ├── 002_performance_indexes.sql # Performance indexes
│ ├── 003_frontend_analytics_indexes.sql # Analytics indexes
│ └── 004_frontend_search_indexes.sql # Search indexes
│ ├── 001_core.sql # Extensions, raw events hypertable, nodes, event_types
│ ├── 002_count_tables.sql # 14 per-group count tables (all 115 event types)
│ ├── 003_count_aggregates.sql # _1m/_1h continuous aggregates over count tables
│ ├── 004_union_views.sql # all_event_stats_30s/1m/1h + all_core_stats_1m
│ ├── 005_node_stats_services.sql # node_stats + event_services + their aggregates
│ ├── 006_trackers.sql # convergence, wp_tracking, DA stats & latency hists
│ └── 007_onchain.sql # on-chain core/service/validator stats
├── tests/
│ ├── types_tests.rs # Type system tests
│ ├── events_tests.rs # Event encoding tests
Expand Down
120 changes: 104 additions & 16 deletions migrations/002_event_types.sql → migrations/001_core.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,110 @@
-- TART baseline schema (squashed from historical migrations 001-024).
--
-- The old migration history created continuous aggregates with live refresh
-- policies and later DROPped them (006, 015, 020). On a fresh database the
-- policy jobs start running mid-sequence and DROP MATERIALIZED VIEW ... CASCADE
-- deadlocks against the TimescaleDB job scheduler. This baseline creates only
-- the final schema, so no aggregate is ever dropped during migration.
--
-- RULE FOR FUTURE MIGRATIONS: never DROP a continuous aggregate (or a table
-- with policy jobs) that an EARLIER migration created — on a fresh database
-- its background job may already be running and the DROP can deadlock with
-- the scheduler. If a drop is unavoidable, remove the policies first and
-- accept that the race still exists; prefer additive changes.
--
-- Column order note: several tables list columns in "historical" order
-- (original columns first, later ALTER TABLE ADD COLUMNs last) so that a
-- fresh database is catalog-identical to one that replayed the old history.

-- Enable extensions
CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- ============================================================
-- Nodes table (regular PostgreSQL table - low cardinality, ~1024 rows)
-- ============================================================
CREATE TABLE IF NOT EXISTS nodes (
node_id TEXT PRIMARY KEY,
peer_id TEXT NOT NULL,
implementation_name TEXT NOT NULL,
implementation_version TEXT NOT NULL,
node_info JSONB NOT NULL,
connected_at TIMESTAMPTZ NOT NULL,
disconnected_at TIMESTAMPTZ,
last_seen_at TIMESTAMPTZ NOT NULL,
is_connected BOOLEAN DEFAULT true,
event_count BIGINT DEFAULT 0,
total_events BIGINT DEFAULT 0,
address TEXT,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_nodes_connected ON nodes(is_connected, last_seen_at DESC) WHERE is_connected = true;
CREATE INDEX IF NOT EXISTS idx_nodes_last_seen ON nodes(last_seen_at DESC);

-- ============================================================
-- Raw events hypertable: 1h browsing store with hot columns.
-- All 115 event types are written here; aggregation lives in count tables.
-- Historically created as 'events' and renamed; index names keep the
-- original idx_events_* prefix on purpose.
-- ============================================================
CREATE TABLE IF NOT EXISTS ingested_raw_events (
timestamp TIMESTAMPTZ NOT NULL,
node_id TEXT NOT NULL,
event_id BIGINT NOT NULL,
event_type SMALLINT NOT NULL,
data JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Hot columns: frequently-queried JSONB fields promoted to real columns
slot INT,
core SMALLINT,
submission_id BIGINT
);

-- Convert to hypertable with 1-hour chunks
SELECT create_hypertable('ingested_raw_events', 'timestamp',
chunk_time_interval => INTERVAL '1 hour',
create_default_indexes => FALSE,
if_not_exists => TRUE
);

-- Space partitioning on node_id (32 hash buckets for write distribution)
SELECT add_dimension('ingested_raw_events', by_hash('node_id', 32), if_not_exists => TRUE);

-- Minimal indexes (each index costs write throughput at 3M events/s)
CREATE INDEX IF NOT EXISTS idx_events_node_time ON ingested_raw_events (node_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_events_type_time ON ingested_raw_events (event_type, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_events_slot ON ingested_raw_events (slot, timestamp DESC) WHERE slot IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_events_core ON ingested_raw_events (core, timestamp DESC) WHERE core IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_events_submission_id ON ingested_raw_events (node_id, submission_id, timestamp DESC) WHERE submission_id IS NOT NULL;

-- Pure browsing store — aggressive 1h retention, checked every 5 minutes
SELECT add_retention_policy('ingested_raw_events', INTERVAL '1 hour', schedule_interval => INTERVAL '5 minutes');

-- VIEW alias: legacy endpoints reference 'events'.
-- Created BEFORE the wp_hash column is added so the view's column list
-- (SELECT * expands at creation time) matches the historical schema.
CREATE VIEW events AS SELECT * FROM ingested_raw_events;

-- wp_hash hot column (added after the view on purpose — see above)
ALTER TABLE ingested_raw_events ADD COLUMN IF NOT EXISTS wp_hash BYTEA;
CREATE INDEX IF NOT EXISTS idx_ire_wp_hash
ON ingested_raw_events (wp_hash, timestamp DESC) WHERE wp_hash IS NOT NULL;

-- ============================================================
-- Stats cache table for pre-computed aggregations
-- ============================================================
CREATE TABLE IF NOT EXISTS stats_cache (
key TEXT PRIMARY KEY,
value JSONB NOT NULL,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- ============================================================
-- Event type lookup table for human-readable names and grouping.
-- TODO: Generate this table automatically from src/events.rs definitions.
-- For now, this is a hardcoded list matching the JIP-3 telemetry protocol event types.

-- ============================================================
CREATE TABLE IF NOT EXISTS event_types (
id SMALLINT PRIMARY KEY,
name TEXT NOT NULL,
Expand Down Expand Up @@ -134,18 +237,3 @@ INSERT INTO event_types (id, name, group_name) VALUES
(198, 'PreimageTransferred', 'preimages'),
(199, 'PreimageDiscarded', 'preimages')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, group_name = EXCLUDED.group_name;

-- Convenience view: events pre-joined with type names and groups.
-- Use this in Grafana panels instead of raw events + JOIN.
CREATE OR REPLACE VIEW events_view AS
SELECT
e.timestamp,
e.node_id,
e.event_id,
e.event_type,
et.name AS event_name,
et.group_name AS event_group,
e.data,
e.created_at
FROM events e
LEFT JOIN event_types et ON e.event_type = et.id;
126 changes: 0 additions & 126 deletions migrations/001_timescaledb_schema.sql

This file was deleted.

Loading
Loading