Skip to main content

Historical Data Storage

Historical Data Storage is a data-forwarding target. It writes values from the forwarding-group range to SQL Server, MySQL, SQLite, PostgreSQL, QuestDB, TDengine, or Apache IoTDB for trends, reports, and historical queries.

Sampling, variable range, and batching are configured on the forwarding group. This page covers target-specific database, table, cache, mirror, and custom-template settings; see Data Forwarding for the common workflow.

Store the first value locally​

SQLite uses a local file, so this exercise needs no separate database server.

  1. Add forwarding group Demo_History_Group under Development Configuration → Data Forwarding. Choose manual scope, interval triggering, and 1000 ms; save it.
  2. Reopen the group, select Demo_Modbus_Device.Demo_Modbus_Value or another working point, click the adjacent +, confirm the row appears, and save.
  3. Add target Demo_History_Target with Historical Data Storage. In plugin properties, select Sqlite and connection string Data Source=manual-demo.sqlite;journal mode=WAL.
  4. Disable custom SQL, use no table splitting, leave scripts and mirroring empty, and keep table names historyNumberValue, historyStringValue, and variableInfo. Keep offset +08:00 for this exercise.
  5. Save and enable the group and target. Confirm one group variable and an online target. The SQLite file belongs to the gateway host's runtime directory, which must be writable.
  6. Wait several intervals, then open Data and Alarms → Data Query → History and select Demo_History_Target.
  7. Choose numeric data and the example variable. Set a time range covering collection, confirm the time panel, then refresh the query.
  8. Check new rows and switch to the chart. A stable value produces a horizontal line. See Data Query for paging and export.

SQLite history target using a writable local file

Stored example variable in the historical chart

Prerequisites​

  1. For a server database, confirm the service is reachable and the gateway account can create, query, insert, and delete history data. For SQLite, confirm local file and directory permissions.
  2. Use an isolated database or table namespace for each target so records remain distinguishable in the query page.
  3. For Apache IoTDB, choose Tree or Table before writing the connection string.
  4. For TDengine, choose WebSocket or Native and use its matching port.

Configure the target​

Open Development Configuration → Data Forwarding and:

  1. Configure and save the forwarding group range, trigger, interval, online filter, and batching.
  2. Add a target, choose Historical Data Storage, and fill in the basic target information.
  3. Open Target properties, choose the database and paste a matching connection string.
  4. Configure table names, retention, time zone, cache, and optional templates.
  5. Save and enable the group and target, then verify the target debug panels and the history query page.

Basic target information​

SettingDefaultConfiguration
Group-Select a saved forwarding group. The target receives only variables in this group.
Target name-Required and unique within the group. Include the database or environment name.
EnableEnabledDisable to stop the target from starting or writing.
Plugin-Select Historical Data Storage.
Log levelInfoTemporarily use Debug when diagnosing connection or write errors.
Start timeout60 secondsValid range is 1 to 3600 seconds.
DescriptionEmptyOptional environment or ownership note.

Project storage settings​

Database storage​

SettingDefaultConfiguration
Database typeSqlServerSelect SqlServer, MySql, Sqlite, PostgreSql, QuestDb, TDengine, or IoTDB. Use that database's connection-string syntax.
IoTDB modelTreeVisible only for IoTDB. Tree uses device paths and measurements; Table uses database, table, TAG, ATTRIBUTE, and FIELD.
Custom SQL modeDisabledAvailable for relational databases, QuestDB, TDengine, and IoTDB Table. It must be disabled for IoTDB Tree.
Split-table strategyNo splitRelational targets can split by day, week, month, quarter, year, or a custom split service. Native time-series targets keep their native storage layout.
Connection stringDevelopment exampleReplace the example with the actual endpoint, database, account, and password. See Connection strings.
Numeric history tablehistoryNumberValueStandard-mode numeric history table. Custom SQL mode uses the template TableName.
String history tablehistoryStringValueStandard-mode string history table. Use a valid database identifier.
Variable information tablevariableInfoUsed by relational standard mode for variable metadata needed by history queries.
Retention days3650Enter a positive integer. Values below 1 are normalized to 1. QuestDB uses TTL, TDengine uses KEEP, and IoTDB Table uses the cleanup task.
History table scriptEmptyRelational Dynamic SQL mode only. Select a saved Dynamic SQL expression; it owns table creation, writes, and cleanup. Do not combine it with Custom SQL mode.
Time-series history scriptEmptyQuestDB, TDengine, and IoTDB logic script owning its schema, writes, and cleanup; mutually exclusive with custom SQL.
Time-zone offset+08:00Enter a fixed offset such as +08:00 or -05:00; empty means UTC. Use the same convention when querying.
Custom template configurationEmptyUsed only by Custom SQL mode. The three SQL templates may all be empty and will then be generated.
Dual-database mirrorDisabledRelational standard tables only. It cannot be combined with Custom SQL, a history-table script, or native time-series targets.

Dual-database mirroring supports standard relational history tables in SQL Server, MySQL, SQLite, and PostgreSQL. The primary and backup must be different physical databases. QuestDB, TDengine, and IoTDB use native write protocols and are outside this relational mirror path. Mirroring cannot be used with Custom SQL mode or History table script.

Connection strings​

The examples below use an isolated database named tgateway_history. Replace <password>, addresses, and accounts, then paste the complete string into Connection string.

DatabaseExampleNotes
SQL ServerServer=127.0.0.1,1433;Database=tgateway_history;User Id=history_user;Password=<password>;TrustServerCertificate=True;Default port 1433.
MySQLServer=127.0.0.1;Port=3306;Database=tgateway_history;Uid=history_user;Pwd=<password>;Allow User Variables=true;AllowLoadLocalInfile=true;Default port 3306.
SQLiteData Source=DB/history.db;journal mode=WALThe path is relative to the gateway directory; the directory must be writable.
PostgreSQLHost=127.0.0.1;Port=5432;Database=tgateway_history;Username=history_user;Password=<password>;Default port 5432.
QuestDBhost=127.0.0.1;port=9000REST SQL uses port 9000. QuestDB has no database namespace; isolate targets with table names.
TDengine WebSocketHost=127.0.0.1;Port=6041;Username=root;Password=<password>;Protocol=WebSocket;db=tgateway_historyWebSocket uses 6041; the database key must be db=, not Database=.
TDengine NativeHost=127.0.0.1;Port=6030;Username=root;Password=<password>;Protocol=Native;db=tgateway_historyNative uses 6030 and requires the TDengine Native client library.
IoTDB TreeDataSource=127.0.0.1;Port=6667;Username=root;Password=<password>;Model=tree;RootPath=root.tgateway;PoolSize=8;FetchSize=1024;ConnectionTimeoutInMs=5000;PoolWaitTimeoutInMs=10000;Compression=false;ZoneId=UTC;UseSSL=falseTree requires Model=tree and RootPath; do not set Database.
IoTDB TableDataSource=127.0.0.1;Port=6667;Username=root;Password=<password>;Model=table;Database=tgateway_history;PoolSize=8;FetchSize=1024;ConnectionTimeoutInMs=5000;PoolWaitTimeoutInMs=10000;Compression=false;ZoneId=UTC;UseSSL=falseTable requires Model=table and Database; do not set RootPath.

TDengine requires Host, Port, Username, Password, Protocol, and db. Optional keys include timezone, connTimeout, readTimeout, writeTimeout, useSSL, enableCompression, autoReconnect, reconnectRetryCount, reconnectIntervalMs, token, bearerToken, and (for WebSocket) adapterHA.

IoTDB requires Model=tree or Model=table. Tree and Table are mutually exclusive: Tree uses RootPath; Table uses Database. PoolSize, FetchSize, ConnectionTimeoutInMs, and PoolWaitTimeoutInMs must be positive. TLS requires UseSSL=true and CertificatePath.

CertificatePath configures the client certificate rather than trusting the server CA. Establish server trust in the gateway account's system store. Standard Table uses STRING value, BOOLEAN quality/type fields, and device/variable TAGs; Tree uses ID-based measurements without extra name/quality columns. FetchSize is a fetch batch, not the total row limit. See IoTDB Models and TLS.

Bind a time-series script​

Compile a TimeSeriesSQL_VariableBasicData script based on TimeSeriesSQLBase<VariableBasicData>, disable custom SQL, then bind it. Implement DBInit and DBSavable returning Task and DBDeletable returning Task of int, using the context, logger, cancellation token, and supplied record batch. The target owns connection lifecycle; the context provides execution, queries, identifier quoting, and literal encoding. See Script Development.

The script owns its schema. Default numeric/string tables need not continue receiving data. Verify both data classes with a physical query; standard queries do not infer arbitrary schemas. Clearing the binding resumes standard writes without deleting script tables.

Standard tables and database modes​

ChoiceStorage structureNotes
SQL Server, MySQL, SQLite, PostgreSQL, Custom SQL disabledNumeric, string, and variable-information tablesSupports split tables, history-table scripts, and dual-database mirroring.
SQL Server, MySQL, SQLite, PostgreSQL, Custom SQL enabledOne custom relational wide tableUse Custom template configuration; do not fill a history-table script.
QuestDB, Custom SQL disabledNumeric and string tablesUses native REST/CSV writes and native partitioning; isolate targets with table names.
QuestDB, Custom SQL enabledCustom wide tableThe time column must be a QuestDB-supported timestamp.
TDengine, Custom SQL disabledNumeric and string supertables with child tablesSaveDays maps to KEEP; split-table selection does not change TDengine layout.
TDengine, Custom SQL enabledCustom supertable/child-table wide tableCustom create SQL must follow TDengine supertable syntax; empty means generated schema.
IoTDB TreeMeasurements under device pathsNo custom wide table or Custom SQL.
IoTDB Table, Custom SQL disabledStandard Table-model numeric and string tablesUses TIME, TAG, ATTRIBUTE, and FIELD.
IoTDB Table, Custom SQL enabledTable-model custom wide tableAvailable only for Table; the history page shows the returned columns.

When IoTDB is selected, choose Tree or Table explicitly. Tree cannot use Custom SQL mode; Table can. QuestDB and TDengine use their native protocols. History table script is only for SQL Server, MySQL, SQLite, and PostgreSQL relational paths; it cannot be combined with Custom SQL mode.

Automatically generated QuestDB and TDengine standard/custom tables use TTL and KEEP respectively; custom create or cleanup templates take precedence when supplied. IoTDB cleanup is executed by the target cleanup task. When a relational History table script is used, the script or database must own the cleanup policy; do not rely on the target's Retention days setting alone.

Custom template configuration​

When Custom SQL mode is enabled, define how several variables are placed into one wide-table row. All three SQL templates may be empty; the current database-specific statements are then generated automatically.

FieldDefaultConfiguration
TableNameCustomHistoryDataActual wide-table name. Use a valid database identifier only.
TimeColumnNameCollectTimeTime column used for ordering, filtering, and cleanup.
TimeColumnTypedatetimeType supported by the selected database, such as datetime2, timestamp, or TIMESTAMP.
ColumnsEmptyAdd business columns. Each column is configured below.
GroupModeCollectGroupGroup by collection group, device name, Remark1 to Remark5, or no grouping.
GroupTimeWindowMs1000Positive time window applied within the current upload batch, not across all historical batches.
CustomInitSQLEmptyCustom create-table SQL; empty means generated SQL.
CustomInsertSQLEmptyCustom insert SQL; empty means generated SQL.
CustomDeleteSQLEmptyCustom cleanup SQL; empty means the default cleanup strategy.
Columns​
FieldConfiguration
ColumnNameUnique database column name that follows the database identifier rules.
ColumnTypeSupported type such as float, double, decimal(18,3), int, bit, varchar(200), json, or jsonb. The default editor type is nvarchar(200).
VariableNameFirst matches a forwarded variable name; it can also be DeviceName, CollectGroup, or Remark1 to Remark5. Empty falls back to a same-name variable.
DefaultValueValue used when no mapping is found; empty writes NULL. It must convert to ColumnType.
IsRequiredGenerates NOT NULL for generated DDL. A row without a value or default can then be rejected by the database.

For automatic IoTDB wide tables, physical time stays TIMESTAMP and group_key is created. General time-name/type settings do not change it, and IsRequired does not generate a NOT NULL constraint there. Within a grouped current batch, row time comes from the first record and repeated variable values use the last record; no averaging or cross-batch history reconstruction is performed.

Example: add a temperature column of type float, map it to Temperature, choose device-name grouping, and set a 1000 ms window.

SQL placeholders​

Placeholders describe SQL structure. Identifiers are quoted for the selected database and values are encoded as parameters or native literals. Never place passwords, tokens, or complete connection strings in a template.

PlaceholderUsed inReplacement
{TableName}Create, insert, deleteQuoted wide-table name.
{TimeColumn}Create, insert, deleteQuoted time-column name.
{Columns}CreateQuoted custom-column list.
{ColumnNames}InsertCustom-column list, excluding the time column.
{TimeParam}InsertTime parameter, normally @CollectTime.
{ValueParams}InsertParameters for custom columns.
{UpdateSet}InsertUpdate expressions for dialects that support UPSERT.
{Days}DeleteCurrent retention-days value.

Validate custom SQL both in the target log and with a database client. SQL quoting, time types, UPSERT syntax, and TDengine supertable syntax are database-specific.

Dual-database mirror settings​

SettingDefaultConfiguration
Sync to a backup databaseDisabledWrites standard history tables to both primary and backup databases.
Backup database typeSQL ServerSelect SQL Server, MySQL, SQLite, or PostgreSQL. Native time-series targets are not supported.
Backup database connectionEmptyPaste the complete backup connection string. It must be a different physical database.
Local pending limit262144Maximum local mirror Journal rows waiting to be committed.
Peer pending limit1048576Maximum rows visible to the peer but not copied; it cannot be below the local limit.
Automatic check interval (minutes)00 disables scheduled consistency checks; maximum 1440.
Automatically fill differencesDisabledAutomatically backfill missing rows found by a check.

Mirror maintenance​

Select a CreateTime range in target debug and check numeric/string data and both directions separately. Inspect differences before repair, then recheck the same range. A new standby is not automatically backfilled with all old history. Interval 0 disables scheduled checks rather than dual writes. Cancellation does not undo completed chunks. Compare exact record identities and database rows, not only target online status.

The reliable mirror queue rejects new events at capacity instead of evicting the oldest rows as ordinary retry caching does. Before changing database connections, table settings, or synchronization capacity, pause periodic production and wait for both the forwarding input and pending mirror commits to drain. Then change settings and resume production; records retained for the previous configuration cannot simply be redirected.

Variable mapping​

This target has no target-variable-specific fields. Whether a variable enters the target and how it is triggered are controlled by the forwarding-group range and group-variable settings; after saving the target, do not repeat sampling settings in a target-variable editor.

Forwarding-group policy​

Historical targets do not have target-level default sampling, interval, or condition fields. Configure these on the forwarding group; the manual variable list can override the update mode for an individual group member.

Historical Data Storage has no target-variable sampling properties, so sampling does not need to be configured again in Variable properties.

Group settingDefaultConfiguration
Variable rangeManualChoose all variables, manual selection, collection device, or collection group.
Scope configurationEmptyFor device or collection-group scope, enter comma-separated names.
Trigger modeChangeChange triggers on value changes; Interval uses the scheduler; Interval or change enables both.
Interval1000Enter integer milliseconds such as 1000 or a valid Cron expression, rather than a duration such as 00:00:01. Both scheduled modes need a valid interval, and member Update overrides must allow scheduling.
Online filterDisabledFilters offline variables before they reach the target.
Batch modeNoneOptionally split a trigger by data group, device, or collection group.
Maximum batch size1000Valid range is 1 to 100000.

For manual scope, group variables also expose Enable, Data group, Update, Group trigger, and Alias. These fields affect membership and triggering only; they do not replace target database settings.

Cache and capacity​

Set these inherited target properties in the Cache and reliability or Capacity limits group.

SettingDefaultConfiguration
Enable failed-write retry cacheEnabledKeeps failed records in local CacheDB for replay after recovery. When disabled, records rejected during backoff may be discarded.
Cache file maximum rows262144Maximum rows in this history target's CacheDB, shared by numeric and string records. Exceeding it evicts the oldest pending rows; it does not cancel a batch already fetched for a database write.
Upload chunk size2000Maximum records read from this history target's own CacheDB per replay batch. It does not set the group interval or rows per SQL statement; numeric and string records still use their respective storage paths.
Memory queue limit100000At capacity, persist pending records to CacheDB first. If persistence fails, retain accepted records and reject new records for upstream retry.
Filter offline dataDisabledFilters offline values when the target dequeues them. The group online filter also applies.
Upload concurrency1The current history writer processes writes serially and does not use this common field to increase concurrency. Setting 4 does not start four parallel writers; keep 1.

Debug, query and verify​

Target debug​

Open Development Configuration → Data Forwarding, select the forwarding group and target, and open Debug.

FeaturePurpose
Forwarding pipelineShows the variable range, trigger mode, filtering result, and data entering historical storage.
Cache outboxShows the memory queue, CacheDB pending rows, retry retention, and the latest error.

Forwarding pipeline​

Historical Data Storage forwarding pipeline

Use the pipeline to confirm the variable range, trigger, and data summary entering the target.

Cache outbox​

Historical Data Storage cache outbox

The cache outbox shows the memory queue, CacheDB pending rows, retained retries, and latest error. When offline cache is enabled, pending records are written after the database recovers.

Save and verify​

  1. Save the group and target, confirm the target is online, and check that initialization produced no error.
  2. Change an included variable or wait for one interval trigger.
  3. In target Debug, review Forwarding pipeline and Cache outbox. Pending rows should fall after a successful write.
  4. Query the database and check the newest row's identifiers, device name, value, nulls, collection time, and record time. For Custom SQL mode, check every configured column.
  5. In Data Query → History, switch to this target and compare total count, pagination, newest sort, and time-zone display with the database result.

Time and queries​

  • Time-zone offset is the storage convention, not the browser time zone. With a browser in Asia/Shanghai, UTC storage may be returned with a Z suffix, while +08:00 storage may appear as a local wall-clock value. They can represent the same instant; keep storage and query conventions consistent.
  • Standard mode uses numeric and string history tables. Custom SQL mode returns the actual wide-table columns; do not assume standard names such as variableId or value.
  • QuestDB, TDengine, and IoTDB native modes may return a native timestamp or an integer timestamp. Verify the actual time-column type and every field in the newest row.

Troubleshooting​

SymptomCheck
Target does not startCheck the database service, connection-string keys and port, permissions, identifier names, IoTDB model, and mutually exclusive options.
TDengine database not foundUse db=database_name, not Database=database_name; use 6041 with WebSocket and 6030 with Native, and keep Protocol aligned with the port.
IoTDB connection failsTree requires Model=tree;RootPath=...; Table requires Model=table;Database=...; never set both RootPath and Database. TLS also requires CertificatePath.
No new historical rowCheck the group range, group enable state, trigger mode, interval, group online filter, and target online state. Historical targets have no separate target-level sampling fields.
No replay after database recoveryEnable the failed-write retry cache and inspect CacheDB pending rows and the target log. When disabled, failed backoff records may have been discarded.
Count or newest time is wrongConfirm the selected target and table, sort field, time range, split strategy, and time-zone offset. Custom SQL uses the actual wide-table columns.
Custom SQL create or insert failsCheck column names, types, variable mappings, required values, placeholders, quoting, time type, and database-specific UPSERT or supertable syntax.
Dual-database mirror cannot startThe primary and backup must be different relational databases, and Custom SQL and history-table scripts must be disabled.
  • Data Forwarding: Forwarding groups, ranges, triggers, cache, redundancy, and target runtime operations.
  • Plugin Manual Index: Other collection and data-forwarding plugins.