# Collector Installation

DBGorilla reads metrics from your infrastructure through the **DBGorilla Collector**. Wherever you
run it, the collector needs the same three things: a database login with the right grants, a
`collector.toml`, and outbound network access. This page covers all three. Pick your deployment
target from the pages that follow.

The collector reads PostgreSQL statistics directly and, for host statistics, talks to the Prometheus
`node_exporter`. You need one collector per network segment, and `node_exporter` on each database
host you want host metrics from.

On managed platforms like AWS RDS, where you have no access to the database host, the collector
reads equivalent metrics from CloudWatch and `node_exporter` is not required.

## What the collector is

The collector is a custom OpenTelemetry collector, shipped as a Docker image, that runs anywhere a
Linux container runs. You configure it with a single file mounted into the container. The app
shows you that file when you add a collector.

A collector reads from one or more **components**. A component is a logical database endpoint: a
single server, a cluster with readers and writers, or a cloud database endpoint. The collector must
be able to reach each component on the database port (5432 for PostgreSQL) and, for physical hosts,
the `node_exporter` port (9100 by default).

The collector needs outbound access to `otlp.dbgorilla.com` and `auth.dbgorilla.com` on port 443,
and nothing else. It listens on no inbound port. Metrics travel over the
[OpenTelemetry](https://opentelemetry.io/) Protocol (OTLP); management traffic uses the Open Agent
Management Protocol (OpAMP).

## Database grants

Create a dedicated read-only login for the collector. **"Read-only" is several grants, not one:**

```sql
CREATE ROLE dbg_readonly LOGIN PASSWORD 'replace-me';
GRANT pg_monitor TO dbg_readonly;                          -- cluster-wide stats
GRANT USAGE ON SCHEMA public TO dbg_readonly;              -- per schema you want captured
GRANT SELECT ON ALL TABLES IN SCHEMA public TO dbg_readonly;
ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public
  GRANT SELECT ON TABLES TO dbg_readonly;                  -- tables created later
```

Replace `app_owner` with the role that creates your tables, and repeat the statement for each one.
**`ALTER DEFAULT PRIVILEGES` without `FOR ROLE` covers only tables created by the role running it.**
If your application creates tables as its own role and you ran the command as `postgres`, new
tables are not covered, and you land in the failure below the first time your application migrates.
List the roles that own tables with:

```sql
SELECT DISTINCT tableowner FROM pg_tables WHERE schemaname NOT IN ('pg_catalog','information_schema');
```

`CREATE ROLE` and `GRANT pg_monitor` are cluster-wide, so run them once. **The `USAGE` and `SELECT`
grants are not.** They apply only to the database you are connected to and the schema you name, so
run them again for every database you list in `databases`, and once per schema inside it. Grant
them in one database only and you land in the failure below.

`pg_monitor` alone is not enough, and the failure is quiet: the collector connects and reports
metrics normally, then fails every schema capture with `permission denied for table …`, logged as
a warning that retries forever. The collector looks healthy and captures no schema at all. Schema
capture runs `pg_dump`, which must `SELECT` every table it reads.

## pg_stat_statements

**`pg_stat_statements` is required for full functionality.** It is the PostgreSQL extension that
records how long each statement takes and how often it runs. Everything DBGorilla tells you about
query performance comes from it.

Without it, the collector still connects and still reports host metrics, database size, table
statistics and cluster topology. What you lose is every query-level feature: slow query analysis,
query-level recommendations, and anything that ranks statements by cost. The collector will not
tell you this is missing once it is running, so check it up front:

```sql
SELECT * FROM pg_available_extensions WHERE name = 'pg_stat_statements';
SHOW shared_preload_libraries;
```

Loading it takes a **full server restart**, not a reload, because it hooks into the executor at
startup. One caution before you run it: **`ALTER SYSTEM SET` replaces the whole value, it does not
append.** Read the current setting first and include everything already there, or you will unload
another extension at the next restart.

```sql
SHOW shared_preload_libraries;
-- if it is empty:
ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';
-- if it already lists something, repeat every existing entry, for example:
ALTER SYSTEM SET shared_preload_libraries = 'pg_cron,pg_stat_statements';

-- restart PostgreSQL, then:
CREATE EXTENSION pg_stat_statements;
```

On managed platforms you set it through the provider instead: a parameter group on AWS RDS and
Aurora, server parameters on Azure Database for PostgreSQL, or database flags on Cloud SQL. Each
still needs a restart.

:::note[Plan the restart before you install]

The restart is the only step in collector installation that interrupts your database. Doing it
first means the whole install is non-disruptive from that point on.
:::

## Configuration

The example below runs one collector against one PostgreSQL cluster
(`db-cluster1.internal.example.com`), restricted to a single database.

The agent ID and secret are issued per collector; the tenant ID identifies your organization and is
shared across all of its collectors. All three are shown when you add a collector in the app.

:::caution
The secret is shown once and cannot be recovered afterward. Capture it when it is displayed.
:::

Keep secrets out of the file. Reference them as `${VAR}` and supply the values as environment
variables, from whatever secrets manager you already use.

```toml
[dbgorilla]
agent_id  = "a8c1cde3-3e91-4ecf-b615-26bae0c35f02"
tenant_id = "fd4c6676-c1a1-4d9a-babb-54958c26369d"
secret    = "${DBG_SERVER_SECRET}"

# One [[component]] block per database system to monitor.
[[component]]
name   = "example_db"        # the name your system appears under in DBGorilla
engine = "postgres"

[component.provider]
type = "self_hosted"

[component.auth]
method   = "password"
user     = "dbg_readonly"
password = "${DB_PASSWORD}"

[component.connect]
host      = "db-cluster1.internal.example.com"
port      = 5432
databases = ["example_db"]   # empty = all non-template databases
ssl_mode  = "verify-full"    # disable | require | verify-ca | verify-full

[commands]
# Optional. When enabled, allows explain and execute_query (see below).
enabled = true
# allowed = ["explain"]   # optional: further restrict to a subset
```

The endpoints default to `otlp.dbgorilla.com` and `auth.dbgorilla.com`, so most configurations do
not set them at all.

### Choosing `ssl_mode`

`verify-full` is the right choice whenever the database is reached over a network, and it works
against managed databases whose certificates chain to a public authority.

If your server presents a certificate from an internal authority, which is what most
Kubernetes-operator-managed PostgreSQL does, then `verify-full` and `verify-ca` fail on trust, because
the container has no reason to hold your CA. Either mount your CA into the container and point
`ca_cert` at it, or use `require`, which keeps the connection encrypted but skips certificate
verification.

Use `disable` only for a database on the same machine as the collector. Over a network it sends the
password and every query in clear text.

### Commands

The `[commands]` block is optional. When enabled, it lets the control plane run a fixed set of
read-oriented operations through the collector:

- `explain`: plan-only `EXPLAIN`. Never `EXPLAIN ANALYZE`; the query is not executed.
- `execute_query`: a single `SELECT`, capped at 1,000 rows with a 30-second statement timeout.

Disable it and the collector cannot execute either. Anything that does run is still bounded by the
privileges of the database user you configured. The collector cannot escalate beyond that user.