Architecture
What AccelerUp is made of, how the pieces talk, and why it is one application rather than several.
The shape
┌──────────────────────────────┐
browser ─────────►│ nginx │
│ monitoring.qrdev.org │
└───┬──────────────────┬────────┘
│ │
┌──────────▼────────┐ ┌─────▼──────────────┐
│ frontend │ │ backend │
│ Next.js 16 / R19 │ │ Go 1.25 monolith │
└───────────────────┘ │ · HTTP API │
│ · workers │
│ · agent websocket │
└─────┬──────────────┘
│
┌──────────────────────┼────────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌───────▼────────┐
│ Postgres │ │ Redis │ │ agents │
│ (state) │ │ (cache, │ │ outbound WS │
└───────────┘ │ optional) │ └────────────────┘
└─────────────┘
│
external: Cloudflare · ClickHouse · Jenkins · Jira · GitHub · GA4 · ELKOne backend
The Go backend is a monolith: HTTP API, background workers and the agent websocket in one process, one binary, one deploy.
This is a deliberate choice, and the reason is in the product. An alert can say
"errors rose on /checkout eleven minutes after release 4.18.2 went to
production, on the three servers that got it first" only because the error
groups, the release changeset and the per-server rollout are rows in the same
database that can be joined. Split those across services and the correlation
becomes an integration project.
The one planned exception is feature flags, which are read on the request path of other people's applications and have a different availability profile. See Zero-downtime deploys.
Two replicas, one leader
The backend runs two replicas behind the load balancer. Both serve HTTP.
Background workers are leader-gated: they are registered through a helper that ensures exactly one replica runs each worker. Without that, every scheduled rule would evaluate twice and every notification would be sent twice.
Workers
Around fifty background workers: pollers, alert evaluators, sync jobs, digesters and cleanup. See Background workers.
They share a scheduler and each one is independent — a worker that fails or hangs does not stop the others, and the ones that talk to slow external systems carry their own timeouts.
Data
Postgres holds everything: state, metrics, logs metadata, alerts, audit, delivery records. Around 257 migrations, applied on start.
Redis is optional and used for caching. AccelerUp works without it.
ClickHouse and other warehouses are external — they are things AccelerUp queries, not things it stores in. See Connections.
Frontend
Next.js 16 with React 19. Server components for the shell, client components for anything interactive. Styling is inline — there is no CSS framework, and the design tokens live in the theme.
Two conventions matter if you are contributing:
- Every dropdown is
FieldSelect. Native<select>is gone. - Fleet pages import layout primitives from
agent-ui.tsxand never redefine them locally.
The agent
accelerated, a small Go binary on each server, dialling out. See
Agent reference.
Principles the code actually follows
Code knows shapes, data knows vendors. No branch anywhere says "if this is <vendor>". Collectors and coverage work from the shape of what they find.
Pure decisions, tested separately. Anything that decides — is this rule due, should this retry, has this recovered, which pool served this region — is extracted into a pure function with its own tests, rather than being inlined in the worker that calls it. This is why the same decision cannot drift between the worker and the screen that displays it.
Fail towards more alerting, not less. When a classification is ambiguous, the code chooses the branch that results in somebody being told.
Where this behaviour lives: ARCHITECTURE.md, backend/cmd/server/main.go. If the code and this page disagree, the code is right — please fix the page.
Part of Running AccelerUp — Operating the platform itself: architecture, deploys, backups.