Database Migration After an Upgrade

What happens on the first start after an upgrade that changes the historian database structure, what the migration progress page shows, how long it takes, how much free disk space it needs, and what changes for reports and scripts that read the historian database directly.

Updated 7 min read
On this page (8 sections)
  1. Overview
  2. Migration Progress Page
  3. Availability During the Migration
  4. Expected Duration
  5. Free Disk Space
  6. Before You Upgrade
  7. Reports and Scripts That Read the Historian
  8. Notes

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.

Do not power off or restart the server to make the migration go faster. Let it finish. If the server is restarted anyway, the migration resumes where it stopped the next time the application starts.

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.

StepWhat it doesProgress shown
Historian: rebuilding the data tableWrites 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 tableConverts 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 indexesRemoves indexes that are no longer used and rebuilds the remaining ones.Moving bar

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 sizeIndication for the rebuild stepIndication for the compacting step
1.5 GBAbout 35 secondsAbout 45 seconds
15 GBAbout 6 minutesAbout 8 minutes
100 GBAbout 40 minutesAbout 50 minutes
500 GBAbout 3 hours 15 minutesAbout 4 hours

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.

StepFree space required
Historian: rebuilding the data table10% of the historian file size, with a minimum of 256 MB
Historian: compacting the data table5% of the historian file size, with a minimum of 256 MB
Historian: updating indexes2% of the historian file size, with a minimum of 256 MB

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.

ColumnContent
IdRecord identifier. Unchanged.
TimeStampUTC time as text in the format yyyy-MM-dd HH:mm:ss.fff.
VariableFull variable name. Unchanged.
ValueUnchanged.
StatusUnchanged.
RawUnchanged.
TimeMsThe same UTC time as a number: milliseconds since 1970-01-01.

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.

QueryAfter the upgradeChange
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 VariableReturns 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 equalityMatches 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 secondsFinds three decimals.Read a variable number of decimals.
Writes into HistoricDataIs refused, because a view is read-only.Write to a table of your own.
Table check that works before and after the upgrade
SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name = 'HistoricData';
Time range across all variables, fastest form
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.

Something missing or wrong in this article? Contact us