Skip to content

Commit 62ed71e

Browse files
committed
feat: add PostgreSQL events table with analytics indexes
- Add migration 001_create_events_table.sql with events table schema - Add composite index on (group_id, event_type, created_at) for cycle analytics - Add composite index on (member_address, event_type) for member history - Add EXPLAIN ANALYZE benchmark script to measure query performance - Add database README with usage instructions Closes #[Performance] Add database query indexing for analytics
1 parent cae0977 commit 62ed71e

3 files changed

Lines changed: 135 additions & 0 deletions

File tree

database/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Database
2+
3+
Off-chain PostgreSQL layer for indexing Soroban contract events to power the analytics dashboard.
4+
5+
## Structure
6+
7+
```
8+
database/
9+
├── migrations/
10+
│ └── 001_create_events_table.sql # events table + analytics indexes
11+
└── benchmarks/
12+
└── explain_analyze_indexes.sql # EXPLAIN ANALYZE before/after comparison
13+
```
14+
15+
## Applying the migration
16+
17+
```bash
18+
psql -U <user> -d <dbname> -f database/migrations/001_create_events_table.sql
19+
```
20+
21+
## Running benchmarks
22+
23+
Seed the table first (see the seed helper comment in the benchmark file), then:
24+
25+
```bash
26+
psql -U <user> -d <dbname> -f database/benchmarks/explain_analyze_indexes.sql
27+
```
28+
29+
Compare `Planning Time` and `Execution Time` in the output before and after the migration.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
-- explain_analyze_indexes.sql
2+
-- Measures query execution time before and after adding the analytics indexes.
3+
--
4+
-- Usage:
5+
-- 1. Run the BEFORE blocks BEFORE applying the migration (drop indexes if needed).
6+
-- 2. Apply 001_create_events_table.sql
7+
-- 3. Run the AFTER blocks and compare Planning/Execution times.
8+
--
9+
-- Requires: a populated `events` table with representative data.
10+
-- Tip: seed with at least 100k rows for meaningful results.
11+
12+
-- ─── Seed helper (optional) ───────────────────────────────────────────────────
13+
-- INSERT INTO events (group_id, member_address, event_type, amount, cycle,
14+
-- ledger_sequence, transaction_hash, created_at)
15+
-- SELECT
16+
-- (random() * 999 + 1)::BIGINT,
17+
-- 'G' || substr(md5(random()::text), 1, 55),
18+
-- (ARRAY['ContributionMade','PayoutExecuted','MemberJoined','GroupCreated'])[ceil(random()*4)::int],
19+
-- (random() * 10000)::NUMERIC(20,7),
20+
-- (random() * 11 + 1)::INTEGER,
21+
-- (random() * 1000000)::BIGINT,
22+
-- md5(random()::text),
23+
-- NOW() - (random() * INTERVAL '365 days')
24+
-- FROM generate_series(1, 100000);
25+
26+
-- ─────────────────────────────────────────────────────────────────────────────
27+
-- QUERY 1: Cycle analytics (group_id, event_type, created_at)
28+
-- ─────────────────────────────────────────────────────────────────────────────
29+
30+
-- BEFORE (sequential scan expected without index)
31+
EXPLAIN ANALYZE
32+
SELECT group_id, event_type, created_at, amount, cycle
33+
FROM events
34+
WHERE group_id = 42
35+
AND event_type = 'ContributionMade'
36+
AND created_at >= NOW() - INTERVAL '30 days'
37+
ORDER BY created_at DESC;
38+
39+
-- AFTER (index scan expected on idx_events_cycle_analytics)
40+
-- Run the same query again after migration — output should show:
41+
-- "Index Scan using idx_events_cycle_analytics on events"
42+
EXPLAIN ANALYZE
43+
SELECT group_id, event_type, created_at, amount, cycle
44+
FROM events
45+
WHERE group_id = 42
46+
AND event_type = 'ContributionMade'
47+
AND created_at >= NOW() - INTERVAL '30 days'
48+
ORDER BY created_at DESC;
49+
50+
-- ─────────────────────────────────────────────────────────────────────────────
51+
-- QUERY 2: Member history (member_address, event_type)
52+
-- ─────────────────────────────────────────────────────────────────────────────
53+
54+
-- BEFORE (sequential scan expected without index)
55+
EXPLAIN ANALYZE
56+
SELECT member_address, event_type, created_at, amount, group_id
57+
FROM events
58+
WHERE member_address = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5'
59+
AND event_type = 'PayoutExecuted'
60+
ORDER BY created_at DESC;
61+
62+
-- AFTER (index scan expected on idx_events_member_history)
63+
-- Run the same query again after migration — output should show:
64+
-- "Index Scan using idx_events_member_history on events"
65+
EXPLAIN ANALYZE
66+
SELECT member_address, event_type, created_at, amount, group_id
67+
FROM events
68+
WHERE member_address = 'GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5'
69+
AND event_type = 'PayoutExecuted'
70+
ORDER BY created_at DESC;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-- Migration: 001_create_events_table.sql
2+
-- Creates the events table for off-chain indexing of Soroban contract events.
3+
-- This table powers the analytics dashboard and leaderboard queries.
4+
5+
CREATE TABLE IF NOT EXISTS events (
6+
id BIGSERIAL PRIMARY KEY,
7+
group_id BIGINT NOT NULL,
8+
member_address VARCHAR(56) NOT NULL, -- Stellar address (G... format, max 56 chars)
9+
event_type VARCHAR(64) NOT NULL, -- e.g. 'ContributionMade', 'PayoutExecuted'
10+
amount NUMERIC(20,7), -- XLM amount in stroops / 10^7
11+
cycle INTEGER,
12+
ledger_sequence BIGINT NOT NULL,
13+
transaction_hash VARCHAR(64) NOT NULL,
14+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
15+
raw_payload JSONB -- full event payload for flexibility
16+
);
17+
18+
-- ─── Indexes ──────────────────────────────────────────────────────────────────
19+
20+
-- Index 1: Cycle analytics queries
21+
-- Supports dashboard queries that filter by group, event type, and time range.
22+
-- e.g. "show all ContributionMade events for group 42 in the last 30 days"
23+
CREATE INDEX IF NOT EXISTS idx_events_cycle_analytics
24+
ON events (group_id, event_type, created_at DESC);
25+
26+
-- Index 2: Member history queries
27+
-- Supports per-member history lookups filtered by event type.
28+
-- e.g. "show all PayoutExecuted events for member G...XYZ"
29+
CREATE INDEX IF NOT EXISTS idx_events_member_history
30+
ON events (member_address, event_type);
31+
32+
-- ─── Comments ─────────────────────────────────────────────────────────────────
33+
34+
COMMENT ON TABLE events IS 'Off-chain index of Soroban contract events for analytics.';
35+
COMMENT ON INDEX idx_events_cycle_analytics IS 'Speeds up cycle analytics dashboard queries (group_id, event_type, created_at).';
36+
COMMENT ON INDEX idx_events_member_history IS 'Speeds up member history queries (member_address, event_type).';

0 commit comments

Comments
 (0)