- 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.
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.
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
taosdserver 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_WINDOWand an external window.FILLfills empty intervals withNULL,PREV, a constant value or the other documented modes._wstartreturns 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
CACHEMODELset on the database, the latest row or latest non-null values of each subtable are kept in memory, andLASTandLAST_ROWare 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.
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 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.
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; 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 | 250Ingestion, 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
plantwith millisecond precision,KEEP 400andDURATION 10for raw readings, and a second database with longerKEEPfor 15-minute roll-ups written by a stream; - supertables
chiller,ahuandmeter, 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_WINDOWquery 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 3for high availability in OSS; a single-replica database depends on backups (for example withtaosdump). - Retention and storage. Choose
KEEPandDURATIONper database at design time, size disks from a sample of your own data, and note that multi-tier storage (severalKEEPvalues) 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
- TDengine documentation: Introduction
- TDengine documentation: Basic concepts
- TDengine documentation: Data modeling
- TDengine documentation: Architecture
- TDengine documentation: Databases (DDL)
- TDengine documentation: Time-series extensions
- TDengine documentation: Read cache
- TDengine documentation: Stream processing
- TDengine documentation: Data subscription
- TDengine documentation: MQTT ingestion
- TDengine documentation: Transport security
- TDengine 3.4.3.6 release notes
- TDengine documentation: Functions (LAST_ROW)
- TDengine documentation: RSMA downsampled storage
- TDengine release notes index
- TDengine documentation: Kubernetes deployment
- taosdata/TDengine releases on GitHub
- taosdata/TDengine repository on GitHub (README)
- taosdata/TDengine repository: LICENSE
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.