Skip to content
Explainer · Time-series databases

TDengine for Industrial IoT: Architecture and Use Cases

TDengine TSDB is an open source time-series database built for IoT and industrial data. Its distinctive idea is one table per data collection point, grouped under a supertable that carries the shared schema and the static tags, so that each device is written by one writer and stored contiguously. It adds SQL extensions for time windows, a latest-value cache, stream processing and Kafka-like subscriptions in the same server. This article explains that model and the cluster architecture, shows a plant-room example, separates what the open source edition includes from the Enterprise features, and lists cases where TDengine is not the right choice.

Facts checked 27 October 2026 against the vendors' official documentation. Versions covered: TDengine TSDB 3.4.x: the two official sources disagreed when checked. The latest release on GitHub was 3.4.3.3 (release date in its notes: 18 October 2026), while the documentation's release notes already listed 3.4.3.6 (dated 24 October 2026), which had no GitHub release at the time; we cannot tell from either source whether 3.4.3.6 was downloadable then. Documentation at docs.tdengine.com as published on 27 October 2026. Next review due April 2027. Products, licences and prices in this field change quickly; confirm on the vendor's site before you choose. No benchmarks are quoted unless the source and test setup are named.
Short answer
  • TDengine TSDB-OSS is licensed under the AGPL v3 and includes clustering; TDengine TSDB-Enterprise and TDengine Cloud add features such as GUI connectors for MQTT and OPC, encryption in transit and audit logs.
  • Each data collection point gets its own table; a supertable defines the columns and tags for a device type and lets you query all its subtables as one, filtering by tags.
  • A cluster is made of dnodes (taosd processes) holding vnodes, which shard tables; up to three mnodes hold metadata and use Raft; databases set retention (KEEP), file span (DURATION) and replicas.
  • SQL extensions include INTERVAL, SESSION, STATE_WINDOW, EVENT_WINDOW and COUNT_WINDOW windows with FILL, PARTITION BY tbname, and LAST and LAST_ROW served from a read cache.
  • It is a specialised time-series store: the vendor itself says heavy cross-table relational processing is better done in, or together with, a relational database.
How we know: Research-based from the TDengine documentation at docs.tdengine.com (introduction, basic concepts, data modelling, architecture, database DDL, time-series extensions, read cache, stream processing, data subscription, MQTT ingestion, transport security, functions, RSMA, Kubernetes deployment, the release notes index and release notes for 3.4.3.6) and the taosdata/TDengine GitHub repository (README, releases, docs/en sources and LICENSE), checked 27 October 2026. Pages were read as rendered in a browser; no bot checks were bypassed. The plain PostgreSQL queries on this page were run on PostgreSQL 18.6 (PGlite 0.5.11). TDengine SQL is adapted from the documentation to building equipment, was not run here and is labelled as illustrative; the per-chiller LAST_ROW query combines two documented features that the documentation does not show together and is marked as an untried combination, not an instruction. Where the GitHub release list and the documentation's release notes disagree on the latest version, both are reported. Licence terms are quoted from the LICENSE file and README in the repository; they are a summary, not legal advice. The campus counts, intervals and KEEP and DURATION values in the example are illustrative assumptions. Vendor performance and benchmark claims are deliberately not repeated, and prices were not confirmed so none are quoted.

What TDengine is, and who this article is for

TDengine TSDB (usually just TDengine) is a time-series database written mainly in C and C++. Taos Data first open-sourced it in 2019, and the cluster edition followed in 2020. Its documentation positions it for IoT, industrial internet, power, connected vehicles and IT operations, and it uses SQL as its query language, extended for time series. The same company also sells TDengine IDMP, a separate industrial data management platform that sits on top of the database; this article covers the database only.

This article is for architects and engineers choosing a store for plant, building and energy telemetry who want SQL and a device-centred data model. For the general concepts, start with the time-series database overview; for a different industrial design, see Apache IoTDB.

Editions and licence: OSS, Enterprise and Cloud

  • TDengine TSDB-OSS: the open source edition, under the GNU Affero General Public License v3. The vendor states that it is full-featured for production, including clustering, and that it is the code base of the paid products.
  • TDengine TSDB-Enterprise: built on the OSS code, deployable at the edge, on premises or in public or private clouds. Features the documentation marks as Enterprise include no-code connectors (MQTT, OPC, PI System and others) configured in the taosExplorer GUI, TLS for the public WebSocket and REST endpoint, IP allow-lists, audit logs, encryption at rest and, for databases, two-replica mode.
  • TDengine Cloud: the Enterprise feature set as a managed service on AWS, Azure and Google Cloud.

Licence terms. The repository README states: "TDengine is licensed under the GNU Affero General Public License 3.0." The full text is in the repository as LICENSE. Its section 13, "Remote Network Interaction", says: "if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version". TDengine TSDB-Enterprise and TDengine Cloud are commercial products; the introduction describes TDengine TSDB-OSS as the "code base for our paid offerings". We did not find the commercial licence terms published as a document, and we did not confirm Enterprise or Cloud prices on a vendor pricing page, so none are quoted here.

Not legal advice. These are quotations from the licence and the vendor's pages, not a conclusion about whether your use triggers any obligation. Read the licence, and the commercial agreement if you buy one, and take advice for your situation.

The data model: one table per device, supertables and tags

TDengine asks you to create a separate table for every data collection point: one table per meter, per chiller or per AHU. The reasoning in the documentation is that each point has a single writer, so writes need no locks, arrive in time order and can be appended, and each point's data is stored contiguously in blocks, which keeps reads sequential and compression high. The first column of every table must be the timestamp.

  • Supertable: a template for one type of device. It defines the timestamp, the metric columns and the tag columns, but stores no data itself.
  • Subtable: the table for one device, created from the supertable with its own tag values. Queries on the supertable first filter subtables by tag, then read and merge them, so "all chillers on campus A" is one query.
  • Tags: static attributes such as site, plant room or model. They are stored once per subtable, not on every row, and can be changed later.
  • Virtual tables: read-only tables that combine columns from several source tables by timestamp, for devices whose sensors report at different rates. They store no data.

A database groups supertables that share storage settings, so put devices with different sampling rates or retention needs in different databases. Timestamps are stored in UTC.

Illustrative DDL, not run here: a chiller supertable, subtables and a write that creates its subtable, adapted from the TDengine documentation (KEEP 400 and DURATION 10 are illustrative values); check every setting against the documentation for your version before use
CREATE DATABASE plant PRECISION 'ms' KEEP 400 DURATION 10 CACHEMODEL 'last_row';
USE plant;

CREATE STABLE chiller (
    ts           TIMESTAMP,
    kw           FLOAT,
    chw_supply_c FLOAT
) TAGS (
    site       VARCHAR(32),
    plant_room VARCHAR(32)
);

CREATE TABLE ch_1 USING chiller (site, plant_room) TAGS ("campus-a", "pr-1");

-- creates subtable ch_2 on first write if it does not exist
INSERT INTO ch_2 USING chiller TAGS ("campus-a", "pr-1") VALUES (NOW, 380.0, 6.8);

Architecture: dnodes, vnodes and mnodes

  • dnode: a running taosd server process. A cluster has one or more, identified by host name and port, and a physical machine can run several.
  • vnode: the unit of sharding and load balancing. Each vnode has its own threads, memory and storage path and holds a subset of a database's tables, including their schemas and tag values. A vnode belongs to one database; a database spans many vnodes.
  • mnode: holds cluster metadata (users, databases, supertables) and monitors dnodes. Up to three mnodes form a group replicated with Raft.
  • taosc: the client driver that applications use; a REST and WebSocket gateway (taosAdapter) serves HTTP clients.

Database options control storage: KEEP is the retention in days (default 3,650; data older than this is deleted automatically), DURATION is the time span of each data file (default 10 days), and KEEP must be at least three times DURATION. REPLICA can be 1 or 3 in OSS, with 2 available in Enterprise from 3.3.0.0. The architecture documentation says each data file holds the period set by DURATION and that data files older than KEEP are deleted automatically, so retention works on whole time-ranged files; it also says KEEP can be adjusted later but DURATION cannot be changed once set. Several KEEP values for multi-tier storage are an Enterprise feature; the community version applies the longest one.

Querying: windows, PARTITION BY and the read cache

TDengine accepts standard SELECT with filters, joins and aggregates, and adds time-series clauses:

  • Windows: INTERVAL (fixed, optionally sliding), SESSION (gaps), STATE_WINDOW (runs of the same state, such as a compressor on or off), EVENT_WINDOW (start and end conditions), COUNT_WINDOW and an external window. FILL fills empty intervals with NULL, PREV, a constant value or the other documented modes. _wstart returns the window start.
  • PARTITION BY: splits a supertable query by tag or by tbname (the subtable name), so each device is aggregated separately.
  • Read cache: with CACHEMODEL set on the database, the latest row or latest non-null values of each subtable are kept in memory, and LAST and LAST_ROW are answered without reading historical data, which is what a "current status" dashboard needs. The documentation notes that maintaining the cache on the write path can affect write performance.

The first example below is adapted from a documented PARTITION BY tbname INTERVAL query. The second is different in kind: LAST_ROW is documented for tables and supertables and PARTITION BY tbname is documented for per-subtable results, but the pages we checked do not show the two together. It is an untried combination, not an instruction: we have not run it and the documentation does not confirm it. The documented form is LAST_ROW on a single subtable or on the supertable as a whole; confirm the per-subtable form in the documentation for your version, or with the vendor, before relying on it.

Illustrative query, not run here: 15-minute average kW per chiller on one site, adapted from the documented PARTITION BY tbname INTERVAL example in the TDengine documentation; check it against the documentation for your version before use
SELECT _wstart, tbname, AVG(kw) AS avg_kw
FROM chiller
WHERE site = 'campus-a' AND ts >= '2026-10-27 09:00:00' AND ts < '2026-10-27 10:00:00'
PARTITION BY tbname
INTERVAL(15m) FILL(NULL);
Untried combination, not an instruction and not run here: the latest row per chiller, combining LAST_ROW and PARTITION BY tbname, which the TDengine documentation does not show together; a placeholder to confirm against the documentation for your version
-- UNTRIED COMBINATION: not shown in the TDengine documentation; confirm before use
SELECT tbname, LAST_ROW(ts, kw) FROM chiller PARTITION BY tbname;

The same questions in plain PostgreSQL

In a relational design the supertable becomes a readings table plus a device table holding the tags, and the tag filter becomes a join. These two queries ran on PostgreSQL 18.6. They show what TDengine's model replaces; they say nothing about relative speed.

Tags in a device table, readings in a narrow table (run on PostgreSQL 18.6)
CREATE TABLE chillers (
    chiller_id text PRIMARY KEY,
    site       text NOT NULL,
    plant_room text NOT NULL
);

CREATE TABLE chiller_power (
    chiller_id text        NOT NULL REFERENCES chillers,
    ts         timestamptz NOT NULL,
    kw         real        NOT NULL,
    PRIMARY KEY (chiller_id, ts)
);

INSERT INTO chillers VALUES
    ('ch-1', 'campus-a', 'pr-1'),
    ('ch-2', 'campus-a', 'pr-1'),
    ('ch-3', 'campus-b', 'pr-7');

INSERT INTO chiller_power VALUES
    ('ch-1', '2026-10-27 09:00+00', 412.0),
    ('ch-1', '2026-10-27 09:05+00', 418.5),
    ('ch-1', '2026-10-27 09:20+00', 430.0),
    ('ch-2', '2026-10-27 09:02+00', 380.0),
    ('ch-2', '2026-10-27 09:17+00', 390.5),
    ('ch-3', '2026-10-27 09:04+00', 250.0);

-- 15-minute average per chiller, filtered by a "tag" (site)
SELECT date_bin('15 minutes', p.ts, TIMESTAMPTZ '2026-10-27 00:00+00') AS window_start,
       p.chiller_id,
       round(avg(p.kw)::numeric, 1) AS avg_kw
FROM chiller_power AS p
JOIN chillers AS c USING (chiller_id)
WHERE c.site = 'campus-a'
  AND p.ts >= '2026-10-27 09:00+00' AND p.ts < '2026-10-27 10:00+00'
GROUP BY 1, p.chiller_id
ORDER BY p.chiller_id, window_start;

-- Latest reading per chiller (the "current value" tile)
SELECT DISTINCT ON (chiller_id) chiller_id, ts, kw
FROM chiller_power
ORDER BY chiller_id, ts DESC;
Results on PostgreSQL 18.6 (PGlite)
      window_start      | chiller_id | avg_kw
------------------------+------------+--------
 2026-10-27 09:00:00+00 | ch-1       |  415.3
 2026-10-27 09:15:00+00 | ch-1       |  430.0
 2026-10-27 09:00:00+00 | ch-2       |  380.0
 2026-10-27 09:15:00+00 | ch-2       |  390.5

 chiller_id |           ts           |  kw
------------+------------------------+-------
 ch-1       | 2026-10-27 09:20:00+00 |   430
 ch-2       | 2026-10-27 09:17:00+00 | 390.5
 ch-3       | 2026-10-27 09:04:00+00 |   250

Ingestion, stream processing and subscriptions

  • SQL and schemaless writes. Data can be written with SQL INSERT (including automatic subtable creation as above) or in schemaless mode using InfluxDB line protocol or the OpenTSDB telnet and JSON protocols, which lets collectors such as Telegraf write without a predefined schema.
  • No-code connectors. Subscribing to an MQTT broker, or reading from OPC UA, OPC DA, Kafka and historians, is configured in the taosExplorer GUI through taosX. The MQTT connector page is marked as an Enterprise-only feature, so with OSS you need your own bridge (a small service or Telegraf) from the broker.
  • Stream processing. Streams defined in SQL transform data as it is written, for example to maintain 15-minute energy totals, without a separate Flink job.
  • Data subscription. Topics are defined in SQL over a database, a supertable or a filtered query, and consumed with a Kafka-like consumer API; since 3.3.7.0 an MQTT client can also subscribe.

Built-in streams and subscriptions reduce the number of systems for a single application, but they are not a general-purpose event backbone. If several independent systems need the same telemetry, keep a broker such as Apache Kafka or an MQTT broker in front and let TDengine be one consumer: the broker moves messages, TDengine stores and queries them, and a warehouse or lakehouse handles long-range analytics with asset and CMMS data.

Example: chiller plant and energy meters on a campus

A campus has 12 chillers, 300 AHUs and 1,500 sub-meters, read every 10 to 60 seconds by a BMS gateway. These counts, the intervals and the KEEP and DURATION values below are illustrative assumptions for the example, not recommendations: choose your own from your point list, sampling rates, how long raw readings must stay for reporting or compliance, and your disk budget. A TDengine design could look like this:

  • database plant with millisecond precision, KEEP 400 and DURATION 10 for raw readings, and a second database with longer KEEP for 15-minute roll-ups written by a stream;
  • supertables chiller, ahu and meter, with site, building, plant room and asset ID as tags, and one subtable per physical asset named after its asset ID;
  • CACHEMODEL 'last_row' so the operations dashboard reads current values from memory;
  • a STATE_WINDOW query on compressor status to count starts per day, a typical input to a maintenance trigger;
  • work orders raised in the CMMS from those counts, with the CMMS keeping the order records in its own relational database.

About 1,800 subtables is small next to the "tens of millions of sources" the vendor says the design is optimised for. Portfolios with hundreds of thousands of points produce as many tables; plan naming, tag sets and vnode counts (VGROUPS) early, because the architecture documentation says each table is assigned to a vnode by hashing its name when the table is created, and a database's vnodes are allocated when the database is created.

Security and operations

  • Transport security. The documentation states that TLS for the public WebSocket and REST endpoint on taosAdapter is an Enterprise feature. With OSS, keep TDengine on a private network or VPN, or terminate TLS at a proxy you operate.
  • Accounts. Change the default administrator password at installation, create separate users per application, and grant database-level privileges only.
  • Replication. Run three dnodes with REPLICA 3 for high availability in OSS; a single-replica database depends on backups (for example with taosdump).
  • Retention and storage. Choose KEEP and DURATION per database at design time, size disks from a sample of your own data, and note that multi-tier storage (several KEEP values) and RSMA downsampled storage are documented as Enterprise features.
  • Upgrades. The documentation says TDengine TSDB publishes a monthly release, and maintenance releases such as 3.4.3.6 include security fixes; read the release notes before upgrading production.

When not to use TDengine

  • Relational workloads. The vendor says TDengine is not intended for general workloads such as e-commerce, ERP or CRM, and that heavy cross-table relational processing suits a relational database, or TDengine used with one. CAFM and CMMS records stay relational.
  • Licence policy. If your organisation's policy does not accept AGPL-licensed software and you will not buy Enterprise or Cloud, Apache-licensed options such as Apache IoTDB or QuestDB are worth a look; whether the AGPL affects your use is a question for the licence text and your advisers, not for this article.
  • One PostgreSQL engine. Our editorial view: if you need PostgreSQL compatibility, extensions and joins with business tables in the same database, TimescaleDB is the more direct fit.
  • Prometheus-centred monitoring. For PromQL dashboards and alert rules, a Prometheus-compatible store such as VictoriaMetrics needs less translation.
  • Small deployments. Our editorial view: an illustrative few thousand slowly sampled points can live in a partitioned PostgreSQL table, as in How to Store IoT Sensor Data.

See also the guide to choosing a time-series database.

Frequently asked questions

Is TDengine open source?

Yes. TDengine TSDB-OSS is published on GitHub under the GNU Affero General Public License v3 and includes clustering. TDengine TSDB-Enterprise and TDengine Cloud are commercial products built on it. This is a summary, not legal advice; see the licence section above.

What is a supertable in TDengine?

A supertable is a template for one type of device: it defines the timestamp, metric columns and tag columns. Each device gets a subtable created from it with its own tag values, and queries on the supertable cover all its subtables, filtered by tags.

Why does TDengine use one table per device?

Each device then has a single writer, so writes are lock-free and append-only, and each device's data is stored contiguously, which the vendor says improves read speed and compression.

Does TDengine support SQL?

Yes. It uses SQL with time-series extensions such as INTERVAL, STATE_WINDOW and EVENT_WINDOW windows, FILL, PARTITION BY, and LAST and LAST_ROW, and offers JDBC, ODBC and REST access.

Can TDengine read data from MQTT?

The no-code MQTT connector in taosExplorer is an Enterprise feature. With the open source edition, use a bridge such as Telegraf or a small service that subscribes to the broker and writes with SQL or InfluxDB line protocol.

Sources

Checked 27 October 2026.

How we research these articles: our editorial method. Research-based from official documentation; SQL examples are run on a local database where the page says so. CodeWithSQL earns nothing from the vendors mentioned.

Building a sensor or telemetry database?

Start with the overview, or compare time-series databases side by side.