@@ -13,25 +13,54 @@ The script checks:
13131 . ** Slow queries** — ` pg_stat_statements ` top 10 by mean execution time
14142 . ** Index usage** — tables with low index-scan ratio (candidates for new indexes)
15153 . ** API latency** — live data from ` /health/metrics `
16- 4 . ** Container resources** — CPU/memory via ` docker stats `
17- 5 . ** Redis hit rate** — ` keyspace_hits ` vs ` keyspace_misses `
16+ 4 . ** APM bottlenecks** — live data from ` /performance/bottlenecks ` (slow queries + index usage + cache stats)
17+ 5 . ** Container resources** — CPU/memory via ` docker stats `
18+ 6 . ** Redis hit rate** — ` keyspace_hits ` vs ` keyspace_misses `
19+
20+ ### Live Bottleneck API
21+
22+ ` GET /performance/bottlenecks ` returns a JSON report combining:
23+ - Top 10 slow queries from ` pg_stat_statements `
24+ - Tables with low index-scan ratio
25+ - Current cache hit/miss stats
26+
27+ ``` bash
28+ curl http://localhost:3000/performance/bottlenecks | jq .
29+ ```
30+
31+ ### Slow Query Threshold
32+
33+ Queries exceeding ** 100ms** are automatically logged to the ` slow_query_log ` table.
34+ Query the table to find recurring slow patterns:
35+
36+ ``` sql
37+ SELECT query_hash, query_text, round(avg (duration_ms)::numeric , 1 ) AS avg_ms, count (* ) AS hits
38+ FROM slow_query_log
39+ WHERE recorded_at > NOW() - INTERVAL ' 24 hours'
40+ GROUP BY query_hash, query_text
41+ ORDER BY avg_ms DESC
42+ LIMIT 20 ;
43+ ```
1844
1945---
2046
2147## Caching Strategy
2248
2349### Redis API Cache (` indexer/src/cache.rs ` )
2450
25- | Endpoint pattern | TTL | Rationale |
26- | -----------------| -----| -----------|
27- | ` GET /events* ` | 10s | High-frequency reads; Stellar ledger closes every ~ 5s |
28- | ` GET /search* ` | 30s | Search results change infrequently |
29- | ` GET /stats ` | 60s | Aggregate — expensive to compute |
30- | ` POST /events/replay ` | no cache | Mutating |
51+ | Endpoint pattern | TTL | Config key | Rationale |
52+ | -----------------| -----| -----------| -----------|
53+ | ` GET /events* ` | 10s | ` events_ttl_secs ` | High-frequency reads; Stellar ledger closes every ~ 5s |
54+ | ` GET /search* ` | 30s | ` search_ttl_secs ` | Search results change infrequently |
55+ | ` GET /stats ` | 60s | ` stats_ttl_secs ` | Aggregate — expensive to compute |
56+ | ` GET /analytics/dashboard ` | 60s | ` analytics_ttl_secs ` | Heavy aggregation query |
57+ | ` POST /events/replay ` | no cache | — | Mutating |
3158
32- ** Activate:** set ` redis_url ` in ` config.toml ` or ` REDIS_URL ` env var.
59+ ** Activate:** set ` redis_url ` in ` config.toml ` or ` STELLAR_ESCROW__CACHE__REDIS_URL ` env var.
3360** Fallback:** if Redis is unavailable, all requests hit Postgres directly — no errors.
3461
62+ All TTLs are configurable in ` indexer/config.toml ` under ` [cache] ` .
63+
3564### Client-Side Cache (` frontend/performance.js ` )
3665
3766` cachedFetch() ` provides an in-memory TTL cache (default 30s) for API responses.
@@ -50,6 +79,17 @@ Call `invalidateCache(url)` after any write operation to keep the UI consistent.
5079
5180## Resource Allocation
5281
82+ ### Docker Resource Limits
83+
84+ Applied in ` docker-compose.yml ` for all application services:
85+
86+ | Service | CPU limit | Memory limit | CPU reservation | Memory reservation |
87+ | ---------| -----------| -------------| -----------------| -------------------|
88+ | indexer | 1.0 | 512M | 0.25 | 128M |
89+ | api | 0.5 | 256M | 0.1 | 64M |
90+
91+ Adjust limits based on observed usage from ` docker stats ` or the Grafana infrastructure dashboard.
92+
5393### Database Connection Pool
5494
5595Configured in ` indexer/config.toml ` :
@@ -63,6 +103,8 @@ min_connections = 2 # keep warm connections ready
63103** Rule of thumb:** ` max_connections = (2 × CPU cores) + effective_spindle_count `
64104For a 4-core host: set ` max_connections = 10–15 ` .
65105
106+ Monitor pool saturation via ` GET /performance/bottlenecks ` → ` index_usage ` or the Grafana PostgreSQL connections panel.
107+
66108### Nginx (` performance.nginx.conf ` )
67109
68110- ` worker_processes auto ` — one worker per CPU core
@@ -143,8 +185,11 @@ Metrics are collected at three layers:
143185# # Quick Wins Checklist
144186
145187- [ ] Enable Redis (`redis_url` in config) — eliminates repeat DB hits for read-heavy endpoints
146- - [ ] Enable `pg_stat_statements` extension — required for slow query analysis
188+ - [ ] Enable `pg_stat_statements` extension — required for slow query analysis (migration `20260329000001_perf_optimization.sql` does this)
147189- [ ] Set `max_connections` based on actual CPU count
148190- [ ] Include `performance.nginx.conf` in nginx http block
149- - [ ] Add `REDIS_URL ` to docker-compose environment
191+ - [ ] Add `STELLAR_ESCROW__CACHE__REDIS_URL ` to docker-compose environment
150192- [ ] Run `perf-analyze.sh` weekly and track trends
193+ - [ ] Review `GET /performance/bottlenecks` after each deploy
194+ - [ ] Check `slow_query_log` table weekly for recurring slow patterns
195+ - [ ] Tune Docker resource limits based on `docker stats` observations
0 commit comments