@@ -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
@@ -94,14 +136,46 @@ Metrics are collected at three layers:
94136| Layer | Mechanism | Endpoint |
95137|-------|-----------|---------|
96138| Infrastructure | ` HealthMonitor` (Rust) | `GET /health/metrics` |
139+ | APM (request-level) | `PerformanceService` (Rust) | `GET /performance/dashboard` |
140+ | APM alerts | `PerformanceService` alert rules | `GET /performance/alerts` |
141+ | APM history | Hourly rollup (materialized view) | `GET /performance/history` |
142+ | Prometheus scrape | `MonitoringService` + APM metrics | `GET /metrics` |
97143| Web Vitals | `observeWebVitals()` (JS) | beacons → `POST /api/metrics` |
98144| CDN | `observeCdnPerformance()` (JS) | beacons → `POST /api/metrics` |
99145| SSL | `monitorTlsConnection()` (JS) | beacons → `POST /api/metrics` |
100146
147+ # ## APM Endpoints
148+
149+ | Endpoint | Description |
150+ |----------|-------------|
151+ | `GET /performance/dashboard` | Full APM snapshot : per-route stats, overall P95/P99, active alerts |
152+ | `GET /performance/alerts` | Active performance alerts only |
153+ | `GET /performance/history` | Hourly rollup from `perf_metrics_hourly` (last 24h) |
154+ | `POST /performance/record` | Ingest a sample `{ route, method, status, duration_ms }` |
155+ | `GET /metrics` | Prometheus text format (monitoring + APM metrics) |
156+
157+ # ## Grafana Dashboards
158+
159+ | Dashboard | UID | Description |
160+ |-----------|-----|-------------|
161+ | StellarEscrow Platform | `stellar-escrow-main` | Business metrics (trades, compliance, fraud) |
162+ | APM — Performance | `stellar-escrow-apm` | Latency (avg/P95/P99), error rate, throughput, DB query time |
163+ | Code Quality | `code-quality` | CI lint/security metrics |
164+
165+ # ## Alert Rules
166+
167+ | File | Group | Alerts |
168+ |------|-------|--------|
169+ | `alert_rules.yml` | `stellar_escrow_platform` | Error rate, disputes, fraud |
170+ | `alert_rules_security.yml` | `stellar_escrow_compliance` | AML, compliance blocks |
171+ | `alert_rules_performance.yml` | `stellar_escrow_performance` | Latency (avg/P95), error rate, DB queries, throughput |
172+
101173# ## Key Metrics to Watch
102174
103175- **TTFB** < 200ms (good), < 800ms (acceptable)
104176- **LCP** < 2.5s
177+ - **Avg response time** < 500ms (warning at 500ms, critical at 2000ms)
178+ - **P95 response time** < 1000ms (warning at 1000ms, critical at 5000ms)
105179- **DB query mean** < 10ms for hot paths (`get_events`, `search_trades`)
106180- **Redis hit rate** > 80% under normal load
107181- **Indexer memory** < 256MB under normal load
@@ -111,8 +185,11 @@ Metrics are collected at three layers:
111185# # Quick Wins Checklist
112186
113187- [ ] Enable Redis (`redis_url` in config) — eliminates repeat DB hits for read-heavy endpoints
114- - [ ] 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)
115189- [ ] Set `max_connections` based on actual CPU count
116190- [ ] Include `performance.nginx.conf` in nginx http block
117- - [ ] Add `REDIS_URL ` to docker-compose environment
191+ - [ ] Add `STELLAR_ESCROW__CACHE__REDIS_URL ` to docker-compose environment
118192- [ ] 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