Feature/enable horizental scalability backend#5
Merged
Conversation
Adds an opt-in local horizontal-scale setup while leaving the plain
'npm run dev' flow untouched.
- apps/backend/Dockerfile — multi-stage Alpine build. Build stage runs
npm ci + nest build; runtime stage installs --omit=dev deps, runs
as the built-in 'node' user, and ships a HEALTHCHECK that curls
/health. Final image ~150MB.
- apps/backend/.dockerignore — keeps node_modules, dist, .env, and
Docker metadata out of the build context.
- docker-compose.yml — adds backend-1, backend-2, and nginx services
gated behind the 'cluster' compose profile. YAML anchor deduplicates
the two backend definitions. Both connect to the mongo service via
'mongodb://mongodb:27017/bliq?directConnection=true' (directConnection
skips topology discovery because rs.initiate advertises the primary
as 'localhost:27017', which isn't resolvable from sibling containers).
Neither backend publishes to the host — only nginx does, on 8090
(avoiding a common Windows reservation on 8080).
- nginx/nginx.conf — round-robin load balancer with a shared upstream
pool for HTTP and socket.io traffic. The socket.io location enables
WebSocket upgrade via 'proxy_set_header Connection $connection_upgrade'
driven by the map that promotes Upgrade requests to 'upgrade' and
everything else to 'close'. Sticky sessions intentionally omitted:
the frontend uses transports: ['websocket'] so socket.io never falls
back to long-polling, and a live WebSocket is pinned to one upstream
by TCP semantics regardless of the LB algorithm. Access log format
'lb' includes upstream_addr so traffic distribution is visible in
the logs.
- package.json — new scripts:
cluster:up docker compose --profile cluster up -d --wait --build
cluster:down docker compose --profile cluster down
cluster:logs follow logs across nginx + both backends
cluster:reset down -v then up (fresh volumes + rebuild)
Two related additions for the horizontally scaled setup.
- New HealthModule exposes GET /health returning { status: 'ok' }. Used
by the Docker HEALTHCHECK on each backend container and available for
any upstream load balancer that wants a readiness probe. Registered
as the first feature module in AppModule so it's available before
any Mongoose connection setup finishes.
- VehiclesGateway now stamps a stable instanceId on the vehicles.snapshot
payload sent to each new client. The id is derived once at gateway
construction time as (process.env.HOSTNAME ?? randomUUID().slice(0,8))
— Docker sets HOSTNAME to the short container id, so in a cluster each
replica advertises its own identity. The change event stream stays as
the raw domain event; the client already knows the instance from the
snapshot because a WebSocket is pinned to one upstream for its life.
Connect/disconnect log lines gain the instanceId so container logs
are self-identifying at a glance.
Complements the backend gateway change that now stamps an instanceId on
the initial vehicles.snapshot payload:
- types.ts adds VehiclesSnapshot ({ vehicles, instanceId }) matching the
new WS envelope. VehicleChangedEvent stays as the raw domain event —
the client only needs the instance once per connection.
- useFleetState tracks instanceId in state, populates it from the
snapshot event, resets to null when the WebSocket disconnects or when
the user toggles to polling mode. Returned as part of FleetState.
- Dashboard renders 'You are <name> · backend <id>' in the header
whenever the socket is connected. Hidden in polling mode where there
is no single backend answering (each poll may hit either replica).
Useful as a live indicator when the cluster profile is running two
replicas behind nginx — opening two browser sessions typically shows
two different instance ids, visibly proving the load balancer is
distributing WS handshakes.
…atency
Adds an opt-in local monitoring stack behind the 'monitoring' compose
profile. Grafana lands on :3001 with two datasources and one dashboard
provisioned from disk, so a reviewer clicks a URL and sees data — no
manual imports.
Backend
- @willsoto/nestjs-prometheus + prom-client added as deps.
- New MetricsModule (apis/metrics/) mounts GET /metrics for scraping,
enables prom-client default metrics (Node runtime: event loop lag,
heap, GC, file descriptors, ...), and registers a global
HttpMetricsInterceptor that records every request's duration into an
http_request_duration_seconds histogram labeled by method, route
template (not raw URL — bounds cardinality), and status.
- Bucket layout (5ms .. 10s) chosen to give useful p50/p95/p99 for
a Node HTTP workload.
Infra
- docker-compose gains 4 services under 'profiles: [monitoring]':
prometheus (:9090) — scrapes both backend replicas every 15s.
loki (:3100) — log store with filesystem backend.
promtail — tails Docker container logs and ships to Loki,
labeling by container/service name.
grafana (:3001) — anonymous viewer + admin/admin login.
- Each service is volume-backed for restart survival
(prometheus_data, loki_data, grafana_data).
- monitoring/ folder holds all configs: prometheus scrape config,
loki + promtail configs, grafana datasource + dashboard provisioning.
Datasources have explicit uid: prometheus / uid: loki so the
provisioned dashboard's panel references resolve reliably (Grafana
auto-generates random UIDs otherwise).
Dashboard 'Bliq — Overview'
- HTTP latency p95 and p99 per route (histogram_quantile over
the rate of the _bucket series)
- Request rate by status
- Node event loop lag p95
- Live backend log tail from Loki
npm scripts
- monitoring:up / monitoring:down / monitoring:reset — start / stop
/ wipe only the monitoring stack.
- observed:up / observed:down — start both 'cluster' and 'monitoring'
profiles in one shot with --build. This is the 'give me everything'
command.
maaref-insat
approved these changes
Jul 19, 2026
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
Changes
Testing
npm testinapps/backendpassesnpx tsc --noEmitpasses in both apps/docsNotes for reviewers