
What stays unchanged
Telegraf separates data collection (inputs) from data delivery (outputs). Input plugins have no dependency on the output destination. An OPC-UA collector does not know or care whether its data goes to InfluxDB or CrateDB.
Here is a standard industrial input block. Nothing in this section changes:
[[inputs.opcua]] name = "opcua" endpoint = "opc.tcp://localhost:4840" connect_timeout = "10s" request_timeout = "5s" security_policy = "None" security_mode = "None" [[inputs.opcua.nodes]] name = "temperature" namespace = "3" identifier_type = "s" identifier = "Temperature" tags = [["line", "line1"], ["plant", "plant_a"]]
The same applies to MQTT, Modbus, Kafka, and every other input plugin. The collection layer is not part of this migration.
The output plugin swap
InfluxDB 2.x and 3.x (before)
[[outputs.influxdb_v2]] urls = ["http://localhost:8086"] token = "$INFLUX_TOKEN" organization = "my-org" bucket = "sensor-data"
InfluxDB 3.x accepts writes via the same influxdb_v2 line protocol endpoint, so this plugin name does not change between InfluxDB versions. Your current config works as the before-state regardless of which version you are running.
CrateDB (after)
[[outputs.cratedb]] url = "postgres://crate@localhost/doc?sslmode=disable" timeout = "5s" table = "metrics" table_create = true key_separator = "_"
Comment out the [[outputs.influxdb_v2]] block. Add the [[outputs.cratedb]] block. Restart Telegraf.
table_create = true tells CrateDB to create the metrics table on first write. No DDL, no schema setup required before you start.
Connecting to CrateDB Cloud
The local URL above uses the default crate user. For CrateDB Cloud, use your cluster credentials:
[[outputs.cratedb]] url = "postgres://your-user:your-password@your-cluster.cratedb.net:5432/doc?sslmode=require" timeout = "5s" table = "metrics" table_create = true key_separator = "_"
Your cluster hostname and credentials are provided in the CrateDB Cloud console.
How CrateDB stores what Telegraf sends
This is the part that will catch you if you skip it.
InfluxDB stores each field as a flat column. CrateDB's Telegraf plugin stores tags and fields in dedicated object columns. The auto-created table schema:
SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'metrics' ORDER BY ordinal_position;
| column_name | data_type |
|---|---|
| hash_id | TEXT |
| timestamp | TIMESTAMP WITH TIME ZONE |
| name | TEXT |
| tags | OBJECT |
| fields | OBJECT |
| day | TIMESTAMP WITH TIME ZONE |
name is the Telegraf measurement name (for example, "opcua" or "cpu"). tags holds all your tag key-value pairs. fields holds all numeric measurements. day is a generated partition column.
This structure handles high-cardinality tag sets and variable field names without schema migrations. When a new sensor type arrives with different fields, Telegraf writes it without a pipeline restart or a schema change. For an industrial stack where sensor configurations change across facilities, this matters.
Querying your data
The query model is standard SQL. The difference from flat-column databases is the bracket notation for accessing values inside the fields and tags objects.
Accessing a field value
SELECT timestamp, fields['temperature'] AS temperature FROM metrics WHERE name = 'opcua' ORDER BY timestamp DESC LIMIT 100
Filtering by tag
SELECT timestamp, fields['temperature'] AS temperature FROM metrics WHERE name = 'opcua' AND tags['plant'] = 'plant_a' AND tags['line'] = 'line1' ORDER BY timestamp DESC LIMIT 100
Time-bucketed aggregation
SELECT date_trunc('minute', timestamp) AS bucket, avg(fields['temperature']) AS avg_temp, max(fields['temperature']) AS max_temp FROM metrics WHERE name = 'opcua' AND tags['plant'] = 'plant_a' AND timestamp > NOW() - INTERVAL '1 hour' GROUP BY bucket ORDER BY bucket
This pattern maps directly to what Grafana time-series panels expect.
Cross-measurement joins
One query pattern that is not available in InfluxDB becomes possible in CrateDB: joining measurements across different sensor types or time ranges in a single statement.
SELECT t.timestamp, t.fields['temperature'] AS temperature, p.fields['pressure'] AS pressure FROM metrics t JOIN metrics p ON date_trunc('minute', t.timestamp) = date_trunc('minute', p.timestamp) AND t.tags['line'] = p.tags['line'] WHERE t.name = 'opcua_temperature' AND p.name = 'opcua_pressure' AND t.tags['line'] = 'line1' ORDER BY t.timestamp DESC LIMIT 50
Verifying the migration
After restarting Telegraf with the new config, confirm data is arriving:
SELECT * FROM doc.metrics LIMIT 5;
If the table does not exist yet, wait one flush interval (10 seconds by default) and run again. The first write creates the table.
To see which measurement names are present:
SELECT DISTINCT name, count(*) AS row_count FROM doc.metrics GROUP BY name ORDER BY row_count DESC;
Updating Grafana
Grafana connects to CrateDB via its built-in PostgreSQL data source. Add CrateDB as a PostgreSQL source, point it at your cluster, and update your panel queries to use object notation.
Before:
SELECT time, usage_user FROM cpu WHERE $__timeFilter(time)
After:
SELECT timestamp AS time, fields['usage_user'] AS usage_user FROM metrics WHERE name = 'cpu' AND $__timeFilter(timestamp)
The Grafana time macros ($__timeFilter, $__timeGroup) work with CrateDB's PostgreSQL-compatible query layer.
Time estimate
| Step | Time |
|---|---|
Edit telegraf.conf (swap output block) |
5 minutes |
| Restart Telegraf and verify data | 2 minutes |
| Update Grafana queries | 15 to 30 minutes per dashboard |
The pipeline is live in under 10 minutes. Dashboard query updates scale with how many panels reference field or tag columns directly.
What this migration does not change
Your Telegraf inputs. Every plugin (MQTT, OPC-UA, Modbus, Kafka, system metrics) runs identically against CrateDB. Your collection interval stays the same. The outputs.cratedb plugin uses Telegraf's standard metric model.
What changes: the destination URL, and how you reference fields in SQL. Both are visible, mechanical changes.
Next steps
Try this with your own data at cratedb.com/explore. The guided environment has a pre-loaded metrics table so you can test the query patterns before touching your production pipeline.
For the capability comparison that motivated this migration, see CrateDB vs. InfluxDB. For the technical explanation of why high-cardinality industrial workloads degrade in InfluxDB's storage model, see The InfluxDB Cardinality Problem.