Some upgrades change the structure of the historian database. When the new version starts for the first time, it converts the existing historian before the engine is started. On a small historian this finishes in seconds and is barely visible. On a historian that has been logging for years it can take considerably longer, so plan the upgrade in a maintenance window.
While the conversion runs, every request to the server answers with a progress page instead of the normal application. The server is working, not hung.
Migration Progress Page
The progress page is titled Database migration in progress. It refreshes itself every ten seconds, so it can be left open on the screen for the duration of the migration.
The page shows which step is running, the position of that step in the sequence as Step 1 of 2, a progress bar with a percentage, the number of rows processed, and the elapsed time with an estimate of the time remaining. A step that cannot report a percentage shows a moving bar instead.
| Step | What it does | Progress shown |
|---|---|---|
| Historian: rebuilding the data table | Writes the historic data into a new table with the current structure, in chunks, then replaces the old table. | Percentage and row count |
| Historian: compacting the data table | Converts the historic data to a compact structure that stores the time as UTC milliseconds and each variable name once, in chunks, then replaces the HistoricData table with a read-only view of the same name. See Reports and Scripts That Read the Historian. | Percentage and row count |
| Historian: updating indexes | Removes indexes that are no longer used and rebuilds the remaining ones. | Moving bar |
No rows match the filter.
Availability During the Migration
The engine is held back until the migration completes. Plan for the following while it runs.
- Devices are not polled, so no values are read or written and nothing is logged to the historian.
- Alarms and events are not evaluated.
- Viewers and operator clients show the progress page instead of their pages.
- The API and the MCP endpoint answer with HTTP status 503 and a JSON body describing the migration, so an integration can detect the state and retry.
- A redundant standby node runs its own migration when it is upgraded. Upgrade and let one node finish before starting the other, so one node keeps running.
Expected Duration
The rebuild step processes roughly 1.5 GB, or about 8 million rows, in 35 seconds on typical server hardware. The index step depends strongly on the size of the database and the speed of the disk, and on a very large historian it can run for up to an hour.
The compacting step processes about 200,000 rows per second. A historian of 3.3 GB with 20.6 million rows was converted in 99 seconds on a workstation. A historian that also needs the rebuild step runs both steps, one after the other, and the durations add up.
| Historian size | Indication for the rebuild step | Indication for the compacting step |
|---|---|---|
| 1.5 GB | About 35 seconds | About 45 seconds |
| 15 GB | About 6 minutes | About 8 minutes |
| 100 GB | About 40 minutes | About 50 minutes |
| 500 GB | About 3 hours 15 minutes | About 4 hours |
No rows match the filter.
Use the figures below as a planning guide and measure on your own system. A historian on a mechanical disk takes longer than one on an SSD.
Free Disk Space
The migration writes a new table next to the existing one, so it needs free space on the drive that holds the historian database. The requirement is checked before the step starts. When there is not enough space the step stops and reports how much it needs, and the migration can be started again once space has been freed.
| Step | Free space required |
|---|---|
| Historian: rebuilding the data table | 10% of the historian file size, with a minimum of 256 MB |
| Historian: compacting the data table | 5% of the historian file size, with a minimum of 256 MB |
| Historian: updating indexes | 2% of the historian file size, with a minimum of 256 MB |
No rows match the filter.
The historian file keeps its size after the migration. The space that the old table occupied is reused by future logging rather than returned to the operating system. After the compacting step the existing history occupies about half of the file, so the file does not grow until new history has filled the freed space.
Before You Upgrade
Work through the following before starting the installer on a system with a large historian.
- Take a project export as a backup, as for any upgrade.
- Take a copy of the historian database files, Historian.db and on a redundant pair also StandbyHistorian.db, while the application is stopped. After the compacting step an earlier version cannot open the historian. Returning to the earlier version requires this copy, and history logged after the upgrade is not in it.
- Review reports, Python scripts and other tools that read Historian.db directly, as described in Reports and Scripts That Read the Historian.
- Check the size of the historian database and confirm the drive has the free space listed above.
- Plan a maintenance window that covers the expected duration.
- Inform operators that the screens will show a progress page during the upgrade.
- On a redundant pair, upgrade one node at a time.
Reports and Scripts That Read the Historian
The compacting step changes how the historian database stores the historic data. The samples are stored in the HistoricSamples table and the variable names in the HistorianVariables table. HistoricData is a read-only view with the same columns as before and the additional column TimeMs, so queries that read HistoricData keep working without a join.
The upgrade does not change scripts, Data Grid queries or other SQL. Review them against the tables below.
| Column | Content |
|---|---|
| Id | Record identifier. Unchanged. |
| TimeStamp | UTC time as text in the format yyyy-MM-dd HH:mm:ss.fff. |
| Variable | Full variable name. Unchanged. |
| Value | Unchanged. |
| Status | Unchanged. |
| Raw | Unchanged. |
| TimeMs | The same UTC time as a number: milliseconds since 1970-01-01. |
No rows match the filter.
Text comparisons on TimeStamp, such as TimeStamp >= '2026-10-09 06:00:00', return the same records as before, because the text keeps its format and its order. The time is stored with millisecond precision, so TimeStamp always shows three decimals: 12:00:00.06 is returned as 12:00:00.060.
Queries that select variables by name, with =, IN or a subquery, together with a TimeStamp range use an index, as before. A time range across all variables also uses an index, and TimeMs is the fastest form for long ranges.
| Query | After the upgrade | Change |
|---|---|---|
| Checks for the table with sqlite_master WHERE type = 'table' AND name = 'HistoricData' | Finds nothing, because HistoricData is a view. A script that treats this as missing data writes empty results without an error. | Use type IN ('table', 'view'). This form also works before the upgrade, so the script can be changed first. |
| Filters on a time range without a variable, or with a LIKE pattern on Variable | Returns the same records. A range that covers a large part of the historian reads most of the file. | For long ranges, filter on TimeMs: TimeMs >= strftime('%s', '2026-10-09 06:00:00') * 1000. |
| Compares the TimeStamp of two different variables for equality | Matches more records: samples that one device scan logs within the same millisecond now have the same TimeStamp. | Compare on the second when an exact pairing is required: TimeMs / 1000. |
| Reads more than three decimals of TimeStamp, or characters at fixed positions after the seconds | Finds three decimals. | Read a variable number of decimals. |
| Writes into HistoricData | Is refused, because a view is read-only. | Write to a table of your own. |
No rows match the filter.
SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = 'HistoricData';SELECT Variable, Value FROM HistoricData
WHERE TimeMs >= strftime('%s', '2026-10-09 06:00:00') * 1000
AND TimeMs < strftime('%s', '2026-10-09 07:00:00') * 1000;Notes
The historian is no longer checked for integrity at every startup. On a large database that check could take a long time and delay the server from coming up. Use the maintenance procedure for a database that is reported as damaged instead.
The configuration database is migrated separately at startup. That migration is a structural change only and completes immediately, so it has no progress page.
A migration only runs once per upgrade. Later restarts of the same version start normally.
When Historian Database Path on the Parameters page is set to a historian database of an earlier version, the file is converted when the setting is saved, before the system switches to it. The page waits until the conversion has completed, and logging continues in the current historian meanwhile. If the file cannot be converted, for example for lack of free disk space, the current historian stays in use and the page shows the reason.