Summary
The analytics endpoints (#73 and #74) need to return per-contract event counts and indexer throughput metrics. Running COUNT(*) GROUP BY contract_id against the full soroban_events table on every analytics request is a full table scan that will take seconds on a table with millions of rows. A materialized view pre-computes these aggregates and is refreshed periodically, making analytics queries instant regardless of table size.
Context
PostgreSQL materialized views store the result of a query as a physical table and can be refreshed on demand. With REFRESH MATERIALIZED VIEW CONCURRENTLY, refreshes do not lock the view for reads — analytics queries continue to work during the refresh. The tradeoff is that the data is slightly stale (up to 60 seconds old), which is entirely acceptable for an analytics endpoint that is shown on a dashboard.
What needs to be built
A migration that creates:
CREATE MATERIALIZED VIEW trident_contract_stats AS
SELECT
contract_id,
COUNT(*) AS event_count,
MAX(ledger_sequence) AS latest_ledger,
MAX(ledger_timestamp) AS last_event_at
FROM soroban_events
GROUP BY contract_id;
CREATE UNIQUE INDEX ON trident_contract_stats (contract_id);
The unique index on contract_id is required for concurrent refresh.
A background goroutine in the Go API that calls REFRESH MATERIALIZED VIEW CONCURRENTLY trident_contract_stats every 60 seconds.
The GET /v1/analytics/contracts handler (part of issue #73) should query this view directly.
Acceptance Criteria
Files
database/migrations/000X_contract_stats_view.sql (new)
Dependencies
Summary
The analytics endpoints (#73 and #74) need to return per-contract event counts and indexer throughput metrics. Running
COUNT(*) GROUP BY contract_idagainst the fullsoroban_eventstable on every analytics request is a full table scan that will take seconds on a table with millions of rows. A materialized view pre-computes these aggregates and is refreshed periodically, making analytics queries instant regardless of table size.Context
PostgreSQL materialized views store the result of a query as a physical table and can be refreshed on demand. With
REFRESH MATERIALIZED VIEW CONCURRENTLY, refreshes do not lock the view for reads — analytics queries continue to work during the refresh. The tradeoff is that the data is slightly stale (up to 60 seconds old), which is entirely acceptable for an analytics endpoint that is shown on a dashboard.What needs to be built
A migration that creates:
The unique index on
contract_idis required for concurrent refresh.A background goroutine in the Go API that calls
REFRESH MATERIALIZED VIEW CONCURRENTLY trident_contract_statsevery 60 seconds.The
GET /v1/analytics/contractshandler (part of issue #73) should query this view directly.Acceptance Criteria
GET /v1/analytics/contractsreturns results from the view in under 10ms for a 1M-row tableFiles
database/migrations/000X_contract_stats_view.sql(new)Dependencies