Skip to main content

Explanation

Architecture

Elevarq Signals is a single, self-contained collector that a customer runs inside their own environment. It connects to one or more PostgreSQL instances with a read-only role, runs a fixed catalog of diagnostic queries on a schedule, keeps the results in a local store, and packages them as portable ZIP snapshots. This page explains how those parts fit together and why the design is shaped the way it is.

Two binaries: a daemon and a thin CLI

Signals ships as two programs. signals is the long-running daemon: it loads the configuration, connects to your targets, and collects on a schedule without further input. signalsctl is a thin command-line client that does not touch PostgreSQL or the store at all — it talks to the daemon over a local HTTP control API to ask for status, trigger a collection, or export a snapshot.

The split exists because collection is inherently a long-lived, stateful activity and operation is inherently a short-lived, interactive one. A daemon owns the schedule, the open connections, the circuit breakers, and the store; making the operator interface a separate process means there is exactly one writer to that state and one place where credentials are resolved. You run the daemon once and leave it running — you do not invoke a fresh binary per snapshot, and signalsctl never needs database access of its own.

The seam between them is a local HTTP API the daemon exposes on 127.0.0.1:8081. Every endpoint — status, collect, reload, export, metrics — is authenticated with a bearer token shared by the daemon and the CLI, and the token is compared in constant time; only GET /health is open so that liveness probes need no credential. Because the listener is bound to loopback and gated by a token, the control plane is reachable by an operator on the same host (or, in a container, by signalsctl running inside it) but is not a network service exposed to anyone else. The full surface is documented in the CLI reference.

The pieces inside the daemon

Inside the single daemon process, a handful of components each own one responsibility:

  • HTTP control API — the local control plane on :8081. It authenticates every request and hands work to the collector or the exporter; it is how an operator or signalsctl drives a running daemon.
  • Scheduler and collector loop — drives per-target collection at the configured cadence, resolves the connection credential just in time, opens the read-only connection, runs the catalog, and writes the results.
  • Query catalog — the fixed, in-binary set of read-only diagnostic queries. It is the only SQL the daemon ever runs, and it is auditable directly from source.
  • Read-only safety layers — four independent guards (static SQL linting, per-target role-attribute validation, a session READ ONLY transaction, and transaction-scoped timeouts) that together make a write to your database structurally impossible. Their rationale is the subject of the read-only safety model.
  • Local store — an embedded SQLite database holding collected diagnostic rows and snapshot state, and nothing else.
  • Exporter — packages collected data into portable ZIP snapshots for download via the API or to a local file.

The per-target collection model

Signals treats each configured database as an independent target, and the collection model is built around that independence. The scheduler picks a target that is due, and the collector runs a full cycle against it: resolve the credential at connect time, open a TLS connection, validate that the role is safe, run the catalog inside a single READ ONLY transaction with defensive timeouts, and write the structured results to the store. A per-cycle audit event records that the cycle happened — metadata only, never credentials or sensitive row payloads.

Because targets are independent, the daemon collects from several at once and isolates their failures from one another. A per-target circuit breaker means that a database which is unreachable, slow, or misconfigured stops only its own collection — the other targets keep running on their own cadence. This is what lets one daemon observe a fleet without a single bad endpoint stalling the whole run.

Cadence is deliberately coarse. Signals is periodic diagnostic evidence, not real-time monitoring: most of what it surfaces — bloat, vacuum lag, wraparound risk, configuration drift — moves over hours and days, so the collection interval is chosen to match how often a team acts on the output rather than for freshness. For an incident you keep the steady-state interval calm and request an immediate cycle through the CLI. The cadence keys and their defaults live in the configuration reference.

Where the data lives, and why it is local-first

Every collection is appended to a single embedded SQLite database on the daemon's host — a pure-Go engine built with no C dependency, so the daemon stays a single static binary with no external database to run. That store holds diagnostic data only. Credentials are read at connect time, used for one connection, and never written to the store, the logs, the metrics, the audit events, or the exports.

Keeping the store local is a deliberate choice, not an incidental one. There is no central collection service for snapshots to flow into, so there is no Elevarq-side place where your diagnostic data could accumulate, and nothing to fail open to the network. A snapshot exists as a file only once you ask the exporter for one; until then the data sits in the local database, pruned on a retention window. At the application layer the store is plaintext, so encryption at rest is provided by the customer's own encrypted volume — an encrypted EBS volume, a persistent-volume storage class, or host full-disk encryption.

Snapshots themselves are intentionally portable and inert: a plain ZIP of structured JSON and NDJSON, with no proprietary container and no embedded credentials, so it can be inspected with ordinary tools or carried across an approved transfer path. The exact files and schema are documented in the snapshot format reference.

The trust boundary

The defining property of the architecture is its trust boundary. Everything — the daemon, the scheduler, the store, the exporter, the control API — runs inside the customer's own account or cluster. The only outbound connections the daemon makes are to the customer's own configured PostgreSQL targets. No data ever leaves that boundary on its own:

  • No egress to Elevarq.There is no telemetry, no analytics, no phone-home, and no auto-update. Elevarq operates nothing on the customer's behalf and receives no customer data, so there is no Elevarq-side data-processing surface at all.
  • Outbound only to your databases. The collector connects to the targets you configure and nothing else. The optional Prometheus /metrics endpoint is off by default and, when enabled, is local-only.
  • Data moves only when you move it. The store and any exports stay on the host; a snapshot leaves the boundary only when an operator deliberately exports a ZIP and carries it out.
Cloud authentication methods are the one deliberate exception to "outbound only to your databases." If you opt into RDS IAM or a cloud secret store, the daemon reaches the cloud APIs those methods need (instance metadata, STS, Secrets Manager, and so on) to mint a token or fetch a credential. A fully air-gapped deployment uses a local password credential, which triggers no such calls.

Deployment shape

Operationally the daemon is meant to run as a long-lived service rather than a cron job that launches a one-shot, because it owns its own schedule. It ships as a single non-root container — digest-pinned, multi-arch, with a read-only root filesystem and all capabilities dropped — and the same binary runs equally well as a host service or as a Kubernetes Deployment via the Helm chart. The connection credential and the API token are injected from the environment's secret machinery; the collector configuration is mounted read-only. Setting this up is covered in Run as a service.

How Signals relates to Elevarq Analyzer

Signals and Elevarq Analyzer are separate products with a clean division of labour. Signals collects evidence: it gathers the raw, read-only diagnostic statistics PostgreSQL already exposes and packages them into a portable snapshot. It does not interpret them, score them, or recommend anything. Analyzer interprets that evidence: you hand it an exported snapshot and it produces diagnosis and recommendations.

Keeping the two apart is what preserves the trust boundary described above. Collection stays a small, auditable, read-only program running inside your environment with no analytical logic and no need to reach out anywhere; interpretation happens as a separate step, against a portable artifact, wherever you choose to run it. The snapshot is the contract between them — Signals writes it, Analyzer reads it — so neither product needs privileged access to the other's environment.

If you want to act on any of this rather than understand it, the how-to and reference sections are where to go: the configuration reference for the keys named here, and the read-only safety model for why a write is impossible by construction.