Your stack runs at http://localhost:8080.
# 1. Stop whatever is currently running
docker-compose -f docker-compose.dev-lb.yml down
# 2. Start fresh with monitoring included
docker-compose -f docker-compose.dev-lb.yml up --build -d
# 3. Wait ~20 seconds, then check all 7 containers are up
docker-compose -f docker-compose.dev-lb.yml psExpected — all 7 should show "Up" or "healthy":
NAME STATUS
syncdoc_nginx Up
syncdoc_app1 Up
syncdoc_app2 Up
syncdoc_postgres Up (healthy)
syncdoc_redis Up (healthy)
syncdoc_prometheus Up
syncdoc_grafana Up
# 1. App responds
curl http://localhost:8080/api/health
# → {"status":"ok","db":"postgres","instance":"app1",...}
# Run again — should sometimes say "app2" (load balancing)
# 2. Metrics endpoint works
curl http://localhost:8080/metrics | head -5
# → # HELP ... (Prometheus text format)
# 3. Prometheus is scraping
# Open http://localhost:9090/targets
# → Both syncdoc-app1 and syncdoc-app2 should show State: UP (green)
# 4. Grafana dashboard loaded
# Open http://localhost:3000
# Login: admin / admin
# You should see the SyncDoc dashboard with 10 panels
# (they'll be mostly empty — needs traffic first)# macOS
brew install k6
# Verify
k6 versionCritical: use port 8080 in BASE_URL
cd loadtest
# Test 1: REST API (~2 min)
k6 run -e BASE_URL=http://localhost:8080 http-api.js
# Test 2: WebSocket sync (~2 min)
k6 run -e BASE_URL=http://localhost:8080 websocket-sync.js
# Test 3: Stress test — ramps to 250 VUs (~4 min)
k6 run -e BASE_URL=http://localhost:8080 stress.jsWatch Grafana at http://localhost:3000 while tests run — you'll see the graphs fill in.
Doc create p50: 8ms ← excellent
Doc create p95: 45ms ← good (under 500ms threshold)
Throughput: 38 req/s
Error rate: 0.00% ← no errors = pass
WS connect p95: 180ms ← good (under 2000ms threshold)
Update RTT p50: 25ms ← excellent for local
Update RTT p95: 85ms ← good (under 300ms threshold)
WS error rate: 0.00% ← pass
| Panel | What to watch |
|---|---|
| WS connections per instance | Should split ~50/50 between app1 and app2 |
| HTTP request rate | Both instances should show traffic |
| Redis messages relayed | Goes up during websocket test — proves cross-server sync |
| HTTP latency p50/p95/p99 | p99 staying under 500ms is healthy |
| Memory heap | Should stabilise, not grow continuously |
| Event loop lag | Should stay under 10ms locally |
This proves your two containers are actually syncing via Redis:
# Terminal 1: watch Redis pub/sub traffic
docker exec syncdoc_redis redis-cli monitor | grep PUBLISH
# Terminal 2: open the app
open http://localhost:8080
# Create a doc, open same doc in a second browser window
# Type something — you should see PUBLISH ydoc:XXXX lines appear in Terminal 1# Check which app each request hits
docker-compose -f docker-compose.dev-lb.yml logs app1 app2 | grep "Connected\|client"
# If you see connections on both app1 and app2, Redis sync is being exercisedk6 shows 100% errors, 0ms response times
→ Wrong port. Make sure you use http://localhost:8080 not http://localhost
Prometheus targets show DOWN → Apps not running or metrics not exposed. Check:
docker-compose -f docker-compose.dev-lb.yml logs app1 | tail -20
curl http://localhost:8080/metrics | head -3Grafana dashboard empty after tests → Go to http://localhost:9090/targets first. If targets are DOWN, Prometheus can't scrape — check app logs. If targets are UP but Grafana is empty, try changing the time range in Grafana to "Last 5 minutes".
Grafana shows "No data"
→ The SyncDoc dashboard may not have auto-loaded.
Go to Dashboards → Browse → SyncDoc folder → SyncDoc dashboard.
Or just go to Explore, select Prometheus datasource, and query:
syncdoc_ws_connections_active to confirm data is flowing.