Skip to content

feat(db): materialized view trident_contract_stats to power the analytics API #145

Description

@Depo-dev

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

  • Migration creates the materialized view and the unique index
  • Background goroutine refreshes every 60 seconds; logs refresh duration
  • Goroutine exits cleanly on context cancellation (process shutdown)
  • Refresh failure logs a warning but does not crash the process
  • GET /v1/analytics/contracts returns results from the view in under 10ms for a 1M-row table
  • Integration test: seed 50 events for 3 contracts, trigger refresh, assert view returns correct counts for each contract

Files

database/migrations/000X_contract_stats_view.sql (new)

Dependencies

Metadata

Metadata

Assignees

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave program

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions