Skip to main content
Early accessElevarq Analyzer is not yet generally available — this manual documents the current build.Request an evaluation →
Elevarq Analyzer docs · Register a database

How-to guide

Register a database

Registering a database tells Workbench the shape of the machine your PostgreSQL runs on — its Target environment. Findings account for that shape, so a recommendation for a 4 vCPU managed instance differs from one for a 64-core bare-metal box. This is operator-declared metadata; Workbench never connects to the database to discover it.

This page covers how PostgreSQL targets are registered in Workbench and the per-database fields it tracks. Workbench stores identity, presentation, and routing metadata only — it never stores database connection credentials and never connects to your database. The credentials used to collect from a database belong to Signals; the analyzer reaches the database through Signals, not through Workbench.

Most databases register themselves: when the analyzer imports a snapshot, Workbench creates the database record automatically if one does not already exist. You can also register a database manually to pre-configure it before the first snapshot arrives.

What Workbench stores — and what it doesn't

Workbench keeps a database's identity, how you present it, and how its findings are routed. It holds no connection secret of any kind:

  • No password, connection string, DSN, or TLS client key.
  • No live connection test — Workbench never opens a connection to a customer database. (Earlier releases carried a database_connections table and a connection-test endpoint; both were removed.)
  • Collection credentials and connectivity are configured in Signals, which owns them end to end.

How a database gets registered

Automatically, from an analyzer import (the common path). When the analyzer imports a snapshot and no matching record exists yet, Workbench creates one. Identity is the collected cluster (host:port) plus the PostgreSQL database name; a later import for the same identity reuses the existing row rather than creating a duplicate. A freshly auto-created row takes its alias from the database name and an environment of other — both editable afterwards.

Manually, before the first snapshot. Use New database (/databases/new) to pre-register a target so you can set its alias, environment, routing, and Target environment ahead of collection. The manual form is identity-only; it has no connection fields, because Workbench stores none.

Fields: editable vs collected

FieldSourceEditable
Alias (name)you (defaults from the database name)yes
environmentyouyes
Routing / integrationyouyes
Target environment (CPU / RAM / storage / platform / workload)youyes — see below
PostgreSQL database namecollectedread-only
Cluster identity (host:port)collectedread-only
Engine (postgresql)collectedread-only
Versioncollected from the importread-only

Collected fields are observed by Signals and arrive with the import; they identify the target and cannot be hand-edited.

What is the Target environment?

The Target environment is the operator-declared shape of the database the analyzer is reasoning about: CPU / RAM / storage class / IOPS / throughput, plus the platform (self-hosted, RDS, Aurora, Cloud SQL, Azure Flex, AlloyDB) and a workload hint (OLTP / OLAP / mixed).

Operators declare it on the /databases page; the analyzer consumes it when it runs against the target. The contract is deliberately operator-declared, never collected — Signals collectors stay read-only PG-catalog observers. This is enforced by the analyzer-side targetcontext spec (TC-R001 (operator-declared, never collected)).

Every field is optional. When you don't declare a field, the analyzer falls back to a conservative generic recommendation rather than producing a precise but unsupported one — there's no penalty for leaving a field blank.

Editing through the UI

/databases is the read-only registry. Each row carries an Edit target link that opens the form at /databases/<id>/target/edit. The form has two ways to fill in the values:

  • Cloud preset (recommended for managed Postgres) — a curated dropdown of ~25 popular RDS / Aurora / Azure Flex / Cloud SQL / AlloyDB SKUs. Picking a SKU auto-fills the numeric
    • platform + storage fields from the catalogue (web/data/cloud-presets.json). Every field stays editable after the dropdown applies, so operators can correct any drift between the curated value and their actual instance.
  • Manual entry — operators that don't see their SKU in the dropdown (self-hosted, exotic instance, or a SKU not in our curated list) leave the dropdown on "— Manual entry —" and fill in the fields directly. This is a first-class path; the dropdown is an ergonomics shortcut, not a gate.

Save updates the database's target row; the analyzer reads it on the next run.

Editing through the API

For automation, use the target-only PATCH endpoint:

curl -fsS -X PATCH "$WORKBENCH/api/databases/$DB_ID/target" \
  -b /tmp/wb.cookies \
  -H 'Content-Type: application/json' \
  -H "X-Workbench-CSRF: $(grep workbench_csrf /tmp/wb.cookies | awk '{print $7}')" \
  -d '{
        "cpu_cores": 8,
        "ram_gb": 32,
        "storage_type": "cloud_managed",
        "iops": 12000,
        "throughput_mbps": 750,
        "platform": "rds",
        "workload": "oltp",
        "cloud_preset_sku": "db.m6i.2xlarge"
      }'

Semantics:

  • Empty body / all-empty target — clears the row. Useful for resetting a declaration back to "operator did not declare".
  • Populated body — upserts. Fields not supplied keep their empty-string / NULL sentinel.

Validation rejects out-of-enum strings (storage_type, platform, workload) with DATABASE_VALIDATION_ERROR. Numeric fields accept wide-but-pragmatic ranges (1..4096 cores, 0..32 TB RAM, 1..10M IOPS) — the goal is to catch obvious nonsense without second-guessing high-end servers.

Field reference

FieldTypeAllowed valuesEmpty / unknown
cpu_coresinteger1..4096absent / 0
ram_gbfloat> 0..32768absent / 0
storage_typeclosed enumnvme | ssd | hdd | cloud_managed""
iopsinteger1..10000000absent / 0
throughput_mbpsinteger1..1000000absent / 0
platformclosed enumself_hosted | rds | aurora | cloud_sql | azure_flex | alloydb""
workloadclosed enumoltp | olap | mixed""
cloud_preset_skustring≤ 64 printable bytes (the dropdown's SKU label, e.g. db.m6i.xlarge)""

The closed-enum strings here are the wire-and-storage contract. A Vitest drift gate (web/components/databases/__tests__/target-context-drift.test.ts) pins the TypeScript constants against the Go source-of-truth (internal/workbench/databases/validate.go); any drift fails CI.

Which rule consumes which field

The Target environment exists so analyzer rules can produce hardware-aware findings instead of generic ones. As of this release the consumer wiring is:

Currently consumed

  • storage_type — consumed by io.cost.calibration.v1 (analyzer-side detector). The rule reads TargetContext.Storage to recommend a storage-class-appropriate random_page_cost — NVMe drives a lower value (typically 1.1), SSD a moderate one (1.5..2.0), HDD the conservative default (4.0). When storage_type is blank, the rule only fires when the observed cache-hit ratio is high enough to recommend the safe middle-ground value without making a storage-class claim. Spec: io-cost-calibration (analyzer detection specification).

  • platform — used by the analyzer's multi-platform DDL dialect: when a recommendation involves changing a parameter that's set via cloud-provider parameter groups (RDS, Aurora, Cloud SQL) the rule emits a DDL snippet in the right dialect rather than a generic ALTER SYSTEM that the operator can't apply.

Operator-declared today, consumed by future rules

The remaining fields are persisted on the analyzer envelope (the analyzer-workbench-import-v1 specification § Target context, AWI-R250..R259) and surfaced to the analyzer, but no named v0.1 detector consumes them yet. They're operator-declared today so the data is in place when the rules that need them ship:

  • cpu_cores — future parallelism / worker-count tuning.
  • ram_gb — future shared_buffers / work_mem / effective_cache_size recommendations.
  • iops + throughput_mbps — future effective_io_concurrency + checkpoint pacing rules, plus stricter io.cost.calibration.v1 priors for cloud-managed elastic-IOPS volumes.
  • workload — future OLAP-specific recommendations (parallel scan tuning, partitioning hints) versus OLTP-only ones.

Filling these in early costs you nothing — the analyzer ignores unknown fields cleanly — and means hardware-aware findings start appearing automatically as new rules ship.

Cloud-preset catalogue maintenance

The dropdown's catalogue lives at web/data/cloud-presets.json and is curated on a quarterly review cadence (the file carries its own last_reviewed field). The curated list is pragmatic (~25 SKUs covering the popular tiers across RDS / Aurora / Azure Flex / Cloud SQL / AlloyDB) — not an exhaustive cloud reference. A missing SKU is not a registration failure: the manual-entry fields below the dropdown are the canonical input surface.

Audit trail

Every target update emits a database.target_updated audit event carrying user_id + database_id. The closed-enum allowlist (internal/workbench/audit/audit.go) prevents any other field leaking into the audit row.

Run Workbench

docker pull ghcr.io/elevarq/workbench:v0.1.0

Pin a digest in production — verify the image.