feat: decouple indexer and web server via Redis pub/sub - #412
Merged
Osuochasam merged 4 commits intoAug 25, 2026
Merged
Conversation
Bring in native XDR decoder and regenerated package-lock.json from main. Co-authored-by: Cursor <cursoragent@cursor.com>
Unblock and after merging main: repair corrupted/duplicate TypeScript, Next 16 async route params, and missing modules so lint and docs CI pass.
Treat .server-dist the same as dist/ so start:decoupled and start:worker are not flagged as dead references before the server build runs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR implements the decoupled microservices architecture for Open-Audit: indexing and web serving run as separate processes connected by Redis pub/sub. The indexer worker streams Stellar events, translates them, and publishes to Redis; the web server subscribes and fans events out over WebSocket to dashboard clients.
This addresses the known limitations called out in
server.ts's deprecation notice: indexing starving the HTTP/WebSocket server under load, no fault isolation when the indexer crashes, and inability to scale components independently.The legacy monolithic path (
server.ts+npm run dev:ws) is preserved as a documented fallback for simple deployments.Problem
Today,
server.tsruns Next.js, WebSocket, and the Stellar indexer in a single Node.js process. Under heavy load:The worker side (
src/worker/indexer.ts) already published to Redis, but there was no corresponding web subscriber, no shared server utilities, and no deployment tooling to run them as separate services.Solution
Architecture
New files
server-decoupled.tslib/server/ws-server.tslib/server/csp.tslib/redis/config.tsREDIS_URL,REDIS_CHANNEL, heartbeat keylib/redis/publisher.tslib/redis/subscriber.tslib/events/message-envelope.tsserialize/parse/toWebSocketPayload)docker-compose.microservices.ymlDockerfile.webDockerfile.workerecosystem.config.jsRefactored files
server.tsws-serverandcspmodules; legacy monolith preservedsrc/worker/indexer.tsRedisPublisherand message envelopeARCHITECTURE.mdpackage.jsondev:decoupled,worker:indexer,build:server, PM2/Docker scriptslib/metrics.tsstartTelemetry()andrecordTranslationDuration()hooks used by both serversRedis message format
The worker publishes an envelope; the web server unwraps it and broadcasts only the
TranslatedEvent(whatuseLiveFeedexpects):{ "type": "event", "timestamp": 1234567890, "workerId": "worker-1", "raw": { /* RawEvent */ }, "translated": { /* TranslatedEvent */ } }How to run
Local development
Docker Compose
Brings up three services:
open-audit-redis,open-audit-worker,open-audit-web(port 3000).PM2 (non-Docker production)
npm run build && npm run build:server pm2 start ecosystem.config.jsBoth
open-audit-webandopen-audit-workerrestart automatically on crash.Fault isolation
This is the core value of the split and is covered in two ways:
Automated test —
lib/server/__tests__/fault-isolation.test.tsverifies WebSocket connections stay alive when the Redis subscriber reports errors (web server logs the error and continues).Manual verification:
npm run dev:decoupledws://localhost:3000/ws/eventskill <pid>ordocker stop open-audit-worker)Test plan
npm run validate:docspasses (no dead script or doc links)npm run dev:decoupled— both web and worker start; events flow indexer → Redis → web → WebSocket → dashboardnpm run worker:indexer— indexer runs standalone and publishes to Redisdocker compose -f docker-compose.microservices.yml up --build— all three services reach healthy statenpm run build && npm run build:server && pm2 start ecosystem.config.js— both processes restart after simulated crashnpm test -- lib/events/__tests__/message-envelope.test.ts lib/server/__tests__/fault-isolation.test.tscloses #403