Scaling
cavs-api
Section titled “cavs-api”Stateless — scales horizontally. No in-memory session state; every request re-authenticates from its bearer token. Run N replicas behind a load balancer.
Design choices that keep it cheap under load:
- No write on the hot path. An authenticated request does a single indexed read to resolve the caller. The user row is written only on first sign-in and on session bootstrap — never per request.
- Bytes bypass the API entirely. Uploads and downloads return presigned URLs, so transfer throughput is bounded by object storage, not by API CPU. Doubling your transfer volume doesn’t require doubling API capacity.
- Per-instance connection pool —
DB_MAX_POOL_SIZE/DB_MIN_POOL_SIZE. - Per-instance JWKS cache for Firebase verification, refreshed hourly. No shared state.
Bottleneck order
Section titled “Bottleneck order”object storage → MongoDB → API CPUScale API replicas first (cheap, stateless). Add MongoDB read replicas or shard by
organization_id when reads dominate. Object storage is almost never your problem —
that’s the point of presigned URLs.
cavs-worker
Section titled “cavs-worker”Competing consumers — scales horizontally. Multiple workers share one Pub/Sub subscription and the broker distributes messages. Per-repository ordering is preserved via the message ordering key.
- Every handler is idempotent (deduplicated by
processed_eventson the event id), so at-least-once redelivery and overlapping replicas are both safe. MaxOutstandingMessagesbounds in-flight work per worker. Raise it, or add replicas.- The cleanup ticker runs on every replica. Its operations are idempotent
UpdateManys, so duplicate runs are harmless — but with many replicas, gate it behind a leader lock or move it to a scheduled job rather than paying for N× the work. - GC, repack and index compaction are the CPU-heavy stages. Today they’re
coordinators; when the Rust
cavs-maintenancebinary lands, size that pool independently from the event workers.
To split responsibilities, give each worker class its own subscription id and filter by
event_type:
cavs-worker-notificationscavs-worker-usagecavs-worker-maintenancecavs-web
Section titled “cavs-web”A static Vite build. Serve it from any CDN or static host; it’s infinitely cacheable and has no scaling concern beyond CDN configuration.
The one caveat: in the reference deployment the web tier also reverse-proxies /api to
the API service so the dashboard and API share an origin (Firebase persists auth per
origin). If you front the SPA with a pure CDN, put that proxy at the edge instead.
MongoDB
Section titled “MongoDB”- Indexes for every hot query are created on boot: unique keys for identity, slug and token lookups; compound indexes for membership, repository listing, usage rollups and the audit/notification feeds.
- Transactions — the control plane runs multi-document operations sequentially on standalone MongoDB (organization + member + subscription creation, invitation accept, upload finalize). On a replica set those can be wrapped in session transactions; the operations are already grouped in the store methods for exactly that.
- Shard key candidate:
organization_id. Tenant isolation aligns with the natural query boundary, so it shards cleanly.
Object storage
Section titled “Object storage”Content-addressed keys spread objects across prefixes
(…/objects/aa/bb/<oid>), which is exactly what S3-style partitioning wants — no hot
prefix, no manual key randomization.
Put a CDN in front of downloads with S3_PUBLIC_BASE_URL. It cuts both latency and
egress cost, and it’s especially effective for public repositories and for the
static chunk-tree export, where every
path is immutable and therefore aggressively cacheable.
What to watch
Section titled “What to watch”| Metric | Tells you |
|---|---|
cavs_http_request_duration_seconds p95 by route |
Which endpoint is slowing down |
cavs_http_in_flight_requests |
Whether replicas are saturated |
cavs_http_rate_limited_total |
Clients hitting the limiter — raise it or fix the client |
cavs_queue_depth, cavs_queue_oldest_job_age_seconds |
Whether workers keep up |
cavs_job_duration_seconds by stage |
Which stage is the expensive one |
cavs_chunk_lookup_duration_seconds |
Dedup index performance |
| MongoDB connections in use | How close you are to the cluster limit |
cavs_storage_ledger_discrepancy_bytes |
Metering diverging from reality |
Capacity notes
Section titled “Capacity notes”| Dimension | Practical guidance |
|---|---|
| API replicas | 2 minimum for availability. Add on p95 latency or in-flight saturation. |
| Workers | Start at 1. Add on queue depth or oldest-job age. |
| MongoDB | Replica set from day one. Shard on organization_id when a single primary can’t take the writes. |
| Object storage | Effectively unbounded. Watch cost, not capacity. |
| Rate limits | Size for your largest repository’s push burst — the transfer agent HEAD-guards every tree file and does not back off on 429. |
- Observability — the metrics behind these decisions.
- Configuration reference — the knobs.
- Architecture — why it’s shaped this way.

