Skip to content

InfluxDB source

Polls an InfluxDB query and produces each returned row as a record. The query is yours, with placeholders the source fills in. The source keeps a timestamp cursor and advances it only after the broker acknowledges the batch.

InfluxDB 2.x and 3.x are both supported, selected with version. They differ in query language, endpoint and output shape, and the differences are called out throughout this page.

TypeSource
Librarylibpicomq_connector_influxdb_source
Ships inThe pico-connectors image
Modesv2 (Flux, /api/v2/query) and v3 (SQL, /api/v3/query_sql)
Output schemajson, or text / raw when extracting a single column
StateCursor timestamp, plus same-timestamp bookkeeping
On replayRows are re-read and re-produced with the same key
render query$cursor, $limitInfluxDBCSV or JSONL rowsproduceruntimeackadvance cursorcircuit breaker opens after repeated query failures

Quick start

InfluxDB 2.x with Flux.

toml
type = "source"
key = "cpu_v2"
enabled = true
version = 0
name = "CPU metrics"
path = "libpicomq_connector_influxdb_source"

[[topics]]
topic = { strategy = "field", path = "row.host", template = "cpu.{value}" }
schema = "json"
create_topics = true

[plugin_config]
version = "v2"
url = "http://influxdb:8086"
org = "myorg"
token = "..."
query = '''
from(bucket: "metrics")
  |> range(start: $cursor)
  |> filter(fn: (r) => r._measurement == "cpu")
  |> limit(n: $limit)
'''
poll_interval = "5s"

InfluxDB 3.x with SQL.

toml
[plugin_config]
version = "v3"
url = "http://influxdb:8181"
db = "metrics"
token = "..."
query = "SELECT * FROM cpu WHERE time > '$cursor' ORDER BY time LIMIT $limit OFFSET $offset"
poll_interval = "5s"
bash
PICOMQ_CONNECTORS_SOURCE_CPU_V2_PLUGIN_CONFIG_TOKEN=...

How it works

open() validates the configuration and connects.

  • Checks that query contains $cursor. Without it the cursor could never advance and the same rows would be produced forever, so the source refuses to start.
  • On v3, checks that query contains $offset when stuck_batch_cap_factor is above zero, and warns about ORDER BY ... DESC or >= $cursor, both of which break cursor semantics.
  • Validates cursor_field, initial_offset and payload_format.
  • Connects with up to max_open_retries attempts, backing off to open_retry_max_delay.

Each poll() then does the following.

  1. Sleeps poll_interval. If the circuit breaker is open, returns an empty batch instead of querying.
  2. Substitutes the cursor, batch_size and, on v3, the offset into query.
  3. Runs it, retrying transient failures up to max_retries times with backoff from retry_delay to retry_max_delay.
  4. Parses the rows, skipping any it already produced at the current cursor timestamp.
  5. Builds a record per row and stages the newest timestamp seen as the candidate cursor.

On Ack the candidate becomes the committed cursor. On Nack it is dropped and the next poll re-runs the same query. A failed query trips one count on the circuit breaker, and circuit_breaker_threshold failures open it for circuit_breaker_cool_down.

The cursor starts at initial_offset, or 1970-01-01T00:00:00Z when unset, so a new source reads from the beginning of whatever the query returns.

Rows sharing a timestamp

A timestamp cursor has a hole in it. If more rows share the newest timestamp than fit in one batch, advancing past that timestamp would skip the rest, and not advancing would return the same rows forever. The two versions handle this differently.

VersionStrategy
v2Remembers how many rows it has produced at the current cursor and skips that many on the next poll, inflating $limit to compensate. The inflation is capped at ten times batch_size
v3Doubles the batch size on each poll that comes back full with a single timestamp, up to stuck_batch_cap_factor times batch_size. $offset skips rows already produced. Past the cap it advances and logs the rows it could not fetch

Use a cursor field with enough resolution that this rarely triggers. Nanosecond time in InfluxDB usually is.

Configuration

All keys go under [plugin_config]. Unknown keys are rejected, so a typo prevents the connector from loading.

Both versions

KeyTypeDefaultMeaning
versionstringv2v2 or v3
urlstringrequiredBase URL of the server
tokenstringrequiredAPI token. Redacted in the API
querystringrequiredFlux (v2) or SQL (v3) with $cursor and $limit, plus $offset on v3
poll_intervalduration5sSleep before each poll
batch_sizeint500Value substituted for $limit. Values below 1 become 1
cursor_fieldstring_time on v2, time on v3Column holding the RFC 3339 timestamp the cursor follows
initial_offsetstringnoneStarting cursor, RFC 3339. Validated on open()
payload_columnstringnoneEmit one column as the whole record
payload_formatstringjsonEncoding of payload_column. json, text or utf8, raw or base64
include_metadatabooltrueSee Output below
timeoutduration10sPer request
max_retriesint3Attempts per query. Values below 1 become 1
retry_delayduration1sInitial backoff between attempts
retry_max_delayduration5sBackoff ceiling
max_open_retriesint10Connection attempts in open()
open_retry_max_delayduration60sBackoff ceiling in open()
circuit_breaker_thresholdint5Consecutive failures that open the breaker
circuit_breaker_cool_downduration30sHow long the breaker stays open
verbose_loggingboolfalseLog every poll at info

v2 only

KeyTypeDefaultMeaning
orgstringrequiredOrganization, sent as the org query parameter

v3 only

KeyTypeDefaultMeaning
dbstringrequiredDatabase name
stuck_batch_cap_factorint10Ceiling for batch doubling as a multiple of batch_size. 0 disables the mechanism and drops the $offset requirement

Query placeholders

PlaceholderReplaced with
$cursorThe current cursor as an RFC 3339 string. Quote it in SQL
$limitbatch_size, or a larger value when catching up on a shared timestamp
$offsetv3 only. Rows to skip at the current cursor

Output

The two versions return differently shaped rows, and the record reflects that.

v2

Flux returns one row per field value. The record wraps it.

json
{
  "measurement": "cpu",
  "field": "usage_user",
  "timestamp": "2026-09-03T21:15:04.118Z",
  "value": 12.5,
  "row": {
    "_measurement": "cpu",
    "_field": "usage_user",
    "_time": "2026-09-03T21:15:04.118Z",
    "_value": 12.5,
    "host": "web-1"
  }
}

With include_metadata = false, row keeps only _time and _value. The four top-level fields are always present.

v3

SQL returns one row per point. The record is the row.

json
{
  "time": "2026-09-03T21:15:04.118Z",
  "host": "web-1",
  "usage_user": 12.5,
  "usage_system": 3.1
}

With include_metadata = false, the cursor field is removed from the record.

Common

AspectValue
KeyA decimal number derived from the row's timestamp in nanoseconds plus its position in the batch. Stable across replays of the same rows
TimestampTime the row was read, not the point's own time
HeadersNone

Routing rules address fields inside the record, so path = "row.host" on v2 and path = "host" on v3.

Single-column payloads

payload_column emits one column as the entire record, for tables that already hold serialized messages.

payload_formatExpectsTopic schema
jsonA string containing JSON on v2, any value on v3json
text, utf8A stringtext
raw, base64A base64 stringraw

State

Stored in the runtime's state storeStored in InfluxDB
Cursor timestamp, rows produced, same-timestamp bookkeepingNothing

Losing the state file restarts from initial_offset or the epoch and re-produces everything the query can return. Sinks that key on the record identity absorb the repeat.

Requirements

  • InfluxDB 2.x with a token that has read access to the bucket, or InfluxDB 3.x with a token that can query the database.
  • Network access from the runtime to the server.
  • A query that returns the cursor field on every row. Rows without it are produced but cannot advance the cursor, and a batch with none of them trips the circuit breaker.

Troubleshooting

SymptomCause
query must contain the '$cursor' placeholderAdd $cursor to the range or WHERE clause
V3 source query must contain the '$offset' placeholderAdd OFFSET $offset, or set stuck_batch_cap_factor = 0
cursor_field "time" is not valid for v2Flux exposes the timestamp as _time. The reverse applies on v3
unknown field at loadA key is misspelled or belongs to the other version
circuit breaker is OPEN. Skipping poll.circuit_breaker_threshold consecutive queries failed. Check the server, the breaker closes after circuit_breaker_cool_down
The same rows every pollThe query ignores $cursor, or uses >= instead of > on v3
Rows missing after a burstMore rows shared one timestamp than the catch-up mechanism covers. Raise batch_size or stuck_batch_cap_factor