Skip to content

Scaling and deployment

Partial

Omniglass is one Go binary, and that is a packaging decision, not a scale ceiling. The same artifact runs an all-in-one container on a laptop and a horizontally-scaled fleet on Kubernetes; you scale by topology, not by swapping products.

The binary is a modular monolith: one codebase, one artifact, modules behind clean seams (the Storage Gateway is the only path to the database, coordination rides NATS, collection runs at the edge). It runs two ways, no fork:

  • All-in-one (the modular monolith). One process runs every role, with Postgres and NATS embedded (below), against nothing external. The small-fleet case: download, run, done.
  • Split by run mode (Kubernetes). The same binary launched per mode as separate Deployments, against an external Postgres and an external NATS cluster. A Helm chart wires it up, and each role scales independently.

Splitting a mode onto its own pods is a deployment choice, not a rewrite: the modules already talk over NATS and the gateway. The roles:

  • server: the public HTTP API (API) and the views read path; it serves the SPA embedded in the binary (go:embed), so the web UI is not a separate service. Stateless.
  • node: collection; runs at the sites (outside the cluster) and connects back, plus a central node for cloud-API and SaaS sources (placement: central, nodes).

In all-in-one mode the binary brings its dependencies up in-process, so an operator runs one container, zero external setup:

  • NATS + JetStream, embedded as a library (nats-server in-process, file-backed). The app is always a NATS client; embedded versus external is a config flag, not a code path.

Coordination: NATS moves, Postgres remembers

Section titled “Coordination: NATS moves, Postgres remembers”

Postgres is the relational system of record (entities, samples, events and alarms, audit, and the queries the cascade, fusion, views, and scope need). NATS (JetStream) is the nervous system: work distribution, the durable command queue, the telemetry buffer, and fan-out.

Configuration is two tiers, and platform settings are deliberately centralized:

  • Bootstrap (env, optional). The irreducible minimum needed before the database exists: the Postgres DSN, the NATS embed-or-external choice and address, the SecretProvider key, the run mode, and the listen address. In all-in-one mode these have working defaults, so a desktop run needs no configuration at all; env vars override.
  • The platform settings store (one place). Everything else lives in the single, audited settings engine: feature flags, buffer and retention defaults, CDC routing, integration settings, UI defaults, official-registry overrides, resolved most-specific-wins down the principal hierarchy. An operator settings file (settings.json or YAML) is the GitOps layer, read into memory at boot and mounting cleanly as a Kubernetes ConfigMap (and a future operator); only the override an operator sets through the API is persisted in Postgres (audited), while the file and the embedded defaults are recomputed each boot, so restore is a delete and the file never drifts into a second authoritative copy (ADR-0033).

This is distinct from fleet config and variables, which describe the fleet and resolve down the cascade; the settings store describes the platform itself.

Multi-tenancy: per database, per account, per deployment

Section titled “Multi-tenancy: per database, per account, per deployment”

Tenant isolation is physical, not a row predicate: a tenant is one database, one NATS account, and one deployment. There is no tenant_id column anywhere, no shared row store, and no shared subjects, so per-database isolation (storage) and per-account isolation (messaging) are the same boundary. The data model stays single-tenant-shaped; multi-tenancy lives at the orchestration layer (CNPG-per-tenant). One noisy or compromised tenant cannot reach another: there is nothing shared to reach across (identity and access).