TimescaleDB connector
TimescaleDB connector
The TimescaleDB connector writes each rule result as one hypertable row. It sends bound parameters through the maintained driver with one round trip per row. It buffers rows and flushes full or stale batches.
Use it when metrics must land in TimescaleDB hypertables. You get one UPSERT per row in each batch with your own hypertable mapping.
flowchart LR Rule["rule engine"] -- "INTO connector id" --> Sink["timescaledb sink"] Sink -- "batch rows" --> Pool["driver pool"] Pool -- "bound UPSERT" --> Tbl["hypertable"] Sink -- "fault" --> Back["backoff retry"] Back -- "retry" --> SinkBefore you start
- Run TimescaleDB. Note the connection URL.
- Create the hypertable with time, device, topic, and metrics columns.
- This kind ships in the default build. No feature flag is necessary.
Settings
Set kind to timescaledb. The broker also accepts the alias timescale. It stores the kind as timescaledb.
| Setting | Required | Meaning |
|---|---|---|
connection_url | yes | URL postgresql://[user[:password]@]host[:port][/dbname][?params]. The driver also accepts the postgres:// spelling. Default user is postgres. Default port is 5432. Default database is the user name. A missing host is refused. |
hypertable | yes | Target hypertable. It must match [A-Za-z0-9_]+. |
time_column | no | Time partition column. Default time. It must match [A-Za-z0-9_]+. |
sql_template | yes | UPSERT statement with $1 (time), $2 (device id), $3 (topic), and $4 (metrics). Markers inside strings, quoted names, and comments do not count. Any other set is refused. |
pool_size | no | Driver pool size. Default 10. A value of 0 is refused. |
batch_size | no | Rows per batch. Default 200. A value of 0 is refused. |
batch_timeout_ms | no | Linger before a stale batch flushes. Default 50. |
Example:
{ "id": "ts-sink-1", "kind": "timescaledb", "config": { "connection_url": "postgresql://u:p@127.0.0.1:5432/db", "hypertable": "sensor_metrics", "time_column": "time", "sql_template": "INSERT INTO sensor_metrics (time, device_id, topic, metrics) VALUES ($1, $2, $3, $4::jsonb) ON CONFLICT (time, device_id) DO UPDATE SET metrics = EXCLUDED.metrics", "pool_size": 2, "batch_size": 50, "batch_timeout_ms": 25 }}[!NOTE] The docs run creates this shape. Creation validates only; it writes no row. The send path needs a TimescaleDB server. See the failure record in Verification failures.
Check that it works
Create the connector. The broker answers 201 with the kind.
[node]id = "docs-connector-timescaledb"
[listeners.tcp]bind = "0.0.0.0:1883"
[listeners.api]bind = "0.0.0.0:18083"
[logging]level = "warn"
[auth]allow_anonymous = truepython3 - <<'PY'import json, os, urllib.requestport = os.environ.get("API_PORT", "18083")key = os.environ["API_KEY"]body = json.dumps({"id": "ts-sink-1", "kind": "timescaledb", "config": {"connection_url": "postgresql://u:p@127.0.0.1:5432/db", "hypertable": "sensor_metrics", "time_column": "time", "sql_template": "INSERT INTO sensor_metrics (time, device_id, topic, metrics) VALUES ($1, $2, $3, $4::jsonb) ON CONFLICT (time, device_id) DO UPDATE SET metrics = EXCLUDED.metrics", "pool_size": 2, "batch_size": 50, "batch_timeout_ms": 25}}).encode()req = urllib.request.Request( f"http://127.0.0.1:{port}/api/v1/connectors", data=body, method="POST", headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"})with urllib.request.urlopen(req, timeout=5) as res: assert res.status == 201 print(json.load(res)["kind"])PYtimescaledbLimits
- A
connection_urlwithout thepostgresql://orpostgres://prefix is refused with 400. A missing host is refused. A bad port is refused. - A
hypertableortime_columnoutside[A-Za-z0-9_]+is refused with 400. - A template without exactly
$1to$4is refused with 400. - A
pool_sizeof 0 is refused. Abatch_sizeof 0 is refused. - The production path uses
DriverTimescaleDbTransportthrough the maintained driver. The hand-written framing path serves offline unit tests only. - An explicit
sslmode=require,verify-ca, orverify-fullopts into TLS through the system trust store. Other URLs stay plaintext. An empty trust store denies the connection. - The driver runs MD5 and SCRAM-SHA-256 logins. A wrong password fails closed with a connection error.
- Values travel bound (
$1time,$2device id,$3topic,$4metrics). One extended-protocol round trip runs per row. $1carries RFC 3339 millis text. Time targets bind as time. Date targets bind as days. Other targets bind as text. Bad time fails the row with 22008.$2comes from the JSONdevice_idfield or the topic.$4carries the payload as JSON or as a JSON string.- A payload that is not valid UTF-8 is refused. An empty topic is refused.
- Retryable states restore and back off: class
08,57P01to57P03,40001,40P01. Other states fail the batch. - A failed flush restores rows in order and backs off. Backoff waits 2s, then doubles to 30s. Flushes fail fast during backoff.
- The connect deadline is 5 seconds. The statement deadline is 10 seconds.
- The connector runs behind the rule engine bounded queue. A slow server sheds load; it never stalls publishers.
- The tier is community. The kind ships in the default build.
Next steps
- Feed the connector from a rule in Actions.
- See every kind in Connectors.