- Überblick
- Anwendungsfälle
- Was migriert wird
- Voraussetzungen
- Zugriff & Navigation
- Ablauf einer Migration
- Schnellstart für den Import
- Kommandos und Optionen
- Konfiguration
- Beispiele
- Artefakte des Snapshots
- Prüfungen und Schutzmechanismen
- Verifikation und Rückabwicklung
- Berechtigungen
- Fehlermeldungen & Troubleshooting
- Leistung, Limits & Betrieb
- Verwandte Seiten
Überblick
Die Kontomigration überträgt ausgewählte Konten — mit ihren Benutzern, Rollen, Digitalen Zwillingen, Virtuellen Geräten, Konnektoren, Integrationsflows, Dashboards, Protokollen und gespeicherten Dateien — von einer niotix-Instanz auf eine andere. Die IDs bleiben dabei erhalten: Konten, Zugriffsbereiche, Benutzer und Rollen behalten auf dem Zielsystem dieselben Kennungen wie auf dem Quellsystem.
Ausgeführt wird die Migration mit dem Kommandozeilenwerkzeug tenant-migrate. Es arbeitet in zwei getrennten Hälften:
exportverbindet sich ausschließlich mit der Quelldatenbank und dem Dateispeicher der Quellinstanz und liest ausschließlich lesend. Ergebnis ist ein Snapshot — ein Verzeichnis mit einer JSONL-Datei je Datenbanktabelle, den gespeicherten Dateien und Metadaten.importverbindet sich ausschließlich mit der Zieldatenbank und dem Dateispeicher der Zielinstanz und spielt einen solchen Snapshot ein.
Mit pack wird der geprüfte Snapshot vor der Übergabe in ein passwortgeschütztes, AES-256-verschlüsseltes ZIP-Archiv verpackt; der Import liest dieses Archiv direkt.
Zwischen den beiden Hälften besteht keine Netzwerkverbindung. Die Übergabe erfolgt als Datei-Übergabe des Archivs. Damit lässt sich die Migration auch über Organisationsgrenzen hinweg durchführen: DIGIMONDO exportiert aus der Quellinstanz, der Betreiber der Zielinstanz importiert das übergebene Archiv.
Migriert werden die Daten der relationalen Datenbank (PostgreSQL) einschließlich der dort abgelegten Zeitreihentabellen sowie die gespeicherten Dateien aus dem Dateispeicher (S3 bzw. MinIO). Die Messwerthistorie in der InfluxDB ist nicht Teil dieses Werkzeugs; für sie existiert ein eigenes Migrationswerkzeug. Ihre Übertragung ist mit dem DIGIMONDO-Ansprechpartner separat zu planen.
Anwendungsfälle
- Wechsel des Betriebsmodells: Ein Konto samt Unterkonten wird von der von DIGIMONDO betriebenen Instanz auf eine eigenbetriebene oder anderweitig gehostete niotix-Instanz überführt.
- Übergabe an einen anderen Betreiber: Ein Kundenkonto wechselt den Plattformbetreiber; Benutzer, Rollen und Objektstrukturen sollen unverändert weiterbestehen.
- Zusammenführung von Instanzen: Konten mehrerer Instanzen werden auf einer Zielinstanz gebündelt — bei Bedarf in mehreren aufeinanderfolgenden Migrationsschritten.
- Herauslösen eines Teilbaums: Aus einer Kontenhierarchie wird ein Teilbaum migriert, einzelne Unterkonten werden dabei ausgenommen.
Was migriert wird
Der Umfang ist über eine Tabellenliste im Werkzeug fest definiert. Sie umfasst 74 Tabellenspezifikationen, die sich auf zwei getrennte Snapshot-Arten verteilen:
| Snapshot-Art | Tabellen | Erzeugt mit |
|---|---|---|
| Stammdaten-Snapshot | 70 | export |
| Zeitreihen-Snapshot | 3 | export --timeseries (siehe Zeitreihendaten) |
| Zurückgestellt, nicht exportiert | 1 | — (Modulregistrierung, siehe Nicht migrierte Daten) |
Inhaltlich abgedeckt sind:
| Bereich | Inhalt |
|---|---|
| Kontenhierarchie | Konten und Zugriffsbereiche einschließlich ihrer Ebenen |
| Identität und Rechte | Benutzer, API-Schlüssel, Rollen, Berechtigungen, Kontozuordnungen |
| Geräte und Zwillinge | Virtuelle Geräte, Gerätetreiber, Gerätetemplates, Digitale Zwillinge, Zwillingstemplates |
| Zustände | Datenpunkte, Zwillingszustände und deren zugehörige Verknüpfungen |
| Integrationen | Konnektoren, Integrationsflows, Filter, Transformationen, zugehörige Jobs |
| Oberfläche | Dashboards mit allen 18 abhängigen Detailtabellen |
| Protokolle und Meldungen | Systemprotokolle, Alarmprotokolle, Einstellungen, Meldungen der Objektverwaltung, Tickets mit Tickettypen und -prioritäten |
| Gespeicherte Dateien | Bilder, Anhänge, Logos und Ergebnisdateien (siehe Gespeicherte Dateien) |
| Zeitreihen in PostgreSQL | Gespeicherte Zustandswerte, Regelausführungen, Geotrigger-Historie (eigener Snapshot) |
Die Hierarchie-Hilfstabellen für Konten, Zugriffsbereiche und Digitale Zwillinge werden nicht übertragen, sondern beim Import aus den migrierten Datensätzen neu aufgebaut.
Bei Konnektoren wird der Laufzeitzustand beim Export bewusst zurückgesetzt: Die Zuordnung zu einem Ausführungsslot wird entfernt und der Zustand auf Warten gesetzt. Die Konnektoren verbinden sich auf der Zielinstanz also neu.
Tickettypen und Ticketprioritäten werden anhand ihres Namens zugeordnet: Ist ein gleichnamiger Eintrag auf der Zielinstanz bereits vorhanden, wird dieser verwendet; andernfalls behält der Eintrag seine ID, sofern sie frei ist, sonst erhält er eine neue. Tickettypen, die auf der Quellinstanz einem nicht migrierten Konto gehören, werden für das migrierte Konto neu angelegt. Die IDs von Tickettypen können sich daher von denen der Quellinstanz unterscheiden.
Gespeicherte Dateien
Mit einem Stammdaten-Snapshot werden auch die Dateien übertragen, auf die migrierte Datensätze verweisen — unter anderem:
- Bilder, Kartensymbole, Grundrissbilder und Anhänge von Digitalen Zwillingen,
- Logos, Favicon und Hintergrundbilder aus dem Design eines Kontos,
- Anhänge von Gateways und Gerätetemplates,
- Bilder und Anhänge von Gerätetreibern,
- Dateien an Tickets,
- Ergebnisdateien von Hintergrundaufgaben.
Verweise auf hochgeladene Dateien an nicht ausdrücklich vorgesehenen Stellen werden zusätzlich erkannt, mitgenommen und im Protokoll gemeldet.
Beide Hälften lesen bzw. schreiben genau einen S3-Bucket (bei Selbstbetrieb typischerweise MinIO), konfiguriert über dieselben GLOBAL_S3_*-Variablen wie die niotix-Instanz selbst (siehe Umgebungsvariablen). Je Datei werden der Inhalt und eine Metadatendatei übertragen; die Metadaten werden dabei auf unkritische Felder reduziert (Name, Typ, Größe, Datum, Zugriffsangaben).
Fehlt eine referenzierte Datei im Quell-Bucket, wird dies im Export gemeldet; die übrigen Dateien werden dennoch übertragen. Auf der Zielinstanz werden die Dateien erst nach Bestätigung der Datenbanktransaktion abgelegt.
Damit migrierte Dateien auf der Zielinstanz angezeigt werden, muss der Digital-Twin-Dienst der Zielinstanz in einer Version laufen, die Dateien aus dem aktuellen Dateispeicher auflösen kann. Diese Voraussetzung kann das Werkzeug nicht prüfen; die passende Version ist vorab mit DIGIMONDO abzustimmen.
Zeitreihendaten
Drei Tabellen mit zeitlich wachsenden Daten werden nicht mit dem Stammdaten-Snapshot, sondern mit einem eigenen Zeitreihen-Snapshot über export --timeseries übertragen:
| Tabelle | Inhalt | Mit --skip auslassbar |
|---|---|---|
dtwin-state-persist-data-json |
Gespeicherte Werte der Zwillingszustände | nein |
dtwin-rule-executions |
Historie der Regelausführungen | ja |
dtwin-rules-geotrigger-history |
Ausführungshistorie der Geotrigger | ja |
Der Zeitreihen-Export verwendet dieselbe Kontenauswahl und denselben API-Schlüssel wie der Stammdaten-Export und wird zuletzt ausgeführt — erst wenn alle Stammdaten-Snapshots der betroffenen Konten auf der Zielinstanz importiert sind. Ein Snapshot enthält immer entweder Stammdaten oder Zeitreihen, nie beides. Ablauf und Besonderheiten: Zeitreihen-Export und -Import.
Nicht migrierte Daten
Bewusst nicht übertragen werden:
- Laufzeitzustand der Zielinstanz: Ausführungsslots und Registrierung der Konnektoren, Hintergrundjobs und die Tabellen der Zeitplanung. Diese Daten entstehen auf dem Zielsystem neu.
- Modulregistrierung: Die Module werden auf der Zielinstanz von den einzelnen Diensten selbst neu registriert.
- Aktivierte Integrationen eines Kontos: Die Integrationsschalter in der Modulverwaltung eines Kontos (z. B. IoT-Data-Hub-Influx-Datenbank, Dashboard-Instanz, States-InfluxDB-Datenquelle, IEC 60870-5-104) werden nicht übertragen. Jedes migrierte Konto kommt mit allen Integrationen deaktiviert an; sie sind auf der Zielinstanz erneut zu aktivieren. Die API-Schlüssel der States-InfluxDB-Datenquelle werden ebenfalls nicht übertragen.
- Kurzlebige Anmeldedaten: OAuth2- und OIDC-Tokens, Autorisierungscodes sowie die Protokollierung fehlgeschlagener Anmeldeversuche.
- Berechtigungs-Cache: Die aufgelösten Zugriffsbereiche der Benutzer werden bei der nächsten Anmeldung neu berechnet.
- Instanzweite Grundausstattung: Regeldefinitionen des Systems, Übersetzungen, Schemaverwaltung und Migrationsstände — diese bringt die Zielinstanz selbst mit.
- Messwerthistorie in der InfluxDB: wird mit einem eigenen Werkzeug migriert.
- Der Systembenutzer der Superadministration sowie Kontozuordnungen migrierter Benutzer, die auf Konten außerhalb der Auswahl verweisen.
- Mit
--skipausgelassene Tabellen (siehe Kommando export).
Ein Dashboard-Widget, das auf ein Dashboard außerhalb des migrierten Umfangs verweist, wird mitübertragen. Das Navigationsziel ist auf der Zielinstanz dann nicht vorhanden. Dieser Fall ist bekannt und wird bewusst nicht als Fehler gemeldet.
Voraussetzungen
Zielinstanz
- Eine bereits vollständig eingerichtete niotix-Instanz mit eigenem Wurzelkonto, eingespieltem Datenbankschema, abgeschlossenen Migrationen und der instanzweiten Regel-Grundausstattung.
- Genau ein Wurzelkonto. Findet der Import keines oder mehrere, bricht er ab.
- Dieselbe niotix-Version wie die Quellinstanz. Weicht der Migrationsstand des Schemas ab, meldet der Import dies und bricht bei unverträglichen Unterschieden ab.
- Ein erreichbarer S3-Bucket (z. B. MinIO) der Zielinstanz mit Zugangsdaten, sofern der Snapshot Dateien enthält.
- Ein Digital-Twin-Dienst in einer Version, die migrierte Dateien auflösen kann (siehe Gespeicherte Dateien).
Werkzeug und Laufzeitumgebung
- Node.js 22 für den direkten Aufruf aus dem Repository; Abhängigkeiten sind die PostgreSQL-Treiber, das AWS-SDK für den Dateispeicher und eine ZIP-Bibliothek.
- Für den Import beim Betreiber der Zielinstanz alternativ Docker Engine 20.10 oder neuer mit Compose-Plugin sowie Zugriff auf das Container-Image
registry.gitlab.com/digimondo-code/niotix/tenant-migrate— entweder aus der Registry oder als übergebenes Image-Archiv. - Der Image-Tag muss zur Werkzeugversion des Snapshots passen (aktuell
1.2.0). Die Compose-Konfiguration hat bewusst keinen Standard-Tag: Ohne gesetzten Tag verweigert Compose den Start. - Ein interaktives Terminal für
pack,unpackund den Import eines Archivs, da das Archivpasswort ausschließlich über eine verdeckte Eingabeaufforderung abgefragt wird (siehe Archivpasswort). Ein Aufruf aus Skripten, Cron-Jobs oder mitdocker compose run -Tist nicht möglich.
Zugänge
- Für den Export ein API-Schlüssel der Quellinstanz mit der Berechtigung
all.manageauf jedem ausgewählten Konto oder einem übergeordneten Zugriffsbereich — dieselbe Befugnis, die zum Löschen des Kontos nötig wäre. Details unter Berechtigungen. - Ein Datenbankbenutzer mit Leserechten auf der Quelldatenbank sowie ein Datenbankbenutzer mit Schreibrechten auf der Zieldatenbank.
- Zugangsdaten zum Dateispeicher jeweils der eigenen Instanz: lesend auf der Quellseite, schreibend auf der Zielseite.
- Netzwerkzugang jeweils nur zur eigenen Datenbank und zum eigenen Dateispeicher. Eine Verbindung zwischen Quell- und Zielumgebung ist nicht erforderlich.
Organisatorisch
- Abgestimmte Konten-IDs einschließlich des festgelegten Ankerkontos.
- Ausreichend Plattenplatz: auf der Quellseite für Snapshot und Archiv, auf der Zielseite für das Archiv, die beim Import temporär entpackte Kopie und das Wachstum von Zieldatenbank und Bucket.
- Ein Archivpasswort, erzeugt mit einem Passwortmanager, sowie zwei getrennte Übertragungswege — einer für das Archiv, ein anderer für das Passwort. Ein Zeitreihen-Snapshot erhält ein eigenes Passwort.
- Ein benannter Ansprechpartner auf Seiten der Zielinstanz, der während des Wartungsfensters erreichbar ist. Vor dem Import ist eine Datenbanksicherung der Zielinstanz zu erstellen.
Ankerkonto
Das Ankerkonto ist das oberste der zu migrierenden Konten. Es ist das einzige Konto, das beim Import unter das Wurzelkonto der Zielinstanz gehängt wird; alle übrigen migrierten Konten behalten ihre Eltern-Kind-Beziehungen innerhalb der Auswahl.
Für den Anker gilt:
- Er muss Teil der migrierten Konten dieses Schritts sein.
- Er muss das oberste Konto der Auswahl sein — ist sein Elternkonto ebenfalls in der Auswahl, bricht der Export ab.
- Er darf nicht mit dem Wurzelkonto der Zielinstanz identisch sein.
Ebenen und Elternbeziehungen werden beim Import anhand des Zielsystems neu berechnet, nicht aus dem Snapshot übernommen.
Einfrieren der Quellkonten
Änderungen während der Migration gehen verloren. Eine Migration ist eine Momentaufnahme: Alles, was ab dem Start des Exports in die Quellkonten geschrieben wird — neue Messwerte, angelegte oder bearbeitete Geräte, Alarme, hochgeladene Dateien, Benutzeraktionen —, gelangt nicht in die Migration und fehlt anschließend auf der Zielinstanz. Deshalb sind laufende Datenflüsse vor dem Export zu stoppen und die Quellkonten für die Dauer der Migration einzufrieren.
Der Grund dafür liegt in der Arbeitsweise des Exports: Er liest die Tabellen nacheinander und ohne gemeinsamen Transaktions-Snapshot — bei einem fortgesetzten Export sogar über mehrere Prozesse hinweg. Schreibvorgänge, die zwischen dem Lesen zweier Tabellen stattfinden, landen also nur teilweise oder gar nicht im Snapshot.
Das Einfrieren der Quellkonten wird von der Software nicht erzwungen, und ein ausgelassenes Einfrieren fällt auch nicht auf: Es entsteht dennoch ein Snapshot, der die Prüfungen besteht und sich sauber importieren lässt — er ist dann aber in sich inkonsistent, etwa weil ein Gerät migriert wurde, die zugehörige Messreihe aber nicht. Das Einfrieren ist während einer mehrschrittigen Migration über alle Schritte hinweg aufrechtzuerhalten — einschließlich des abschließenden Zeitreihen-Schritts.
Zum Einfrieren gehören:
- Die API-Schlüssel der betroffenen Konten sperren — mit Ausnahme des für die Migration verwendeten Schlüssels.
- Datenaufnahme stoppen, also die Konnektoren der betroffenen Konten anhalten. Daten, die während der Migration ohnehin nicht übernommen würden, bleiben so beim Quellsystem stehen und lassen sich nach der Umschaltung gezielt nachziehen.
- Zeitgesteuerte Aufgaben, Hintergrundverarbeitung und die Alarmauswertung deaktivieren.
- Benutzer bis zur Umschaltung auf die Zielinstanz von der Quellinstanz fernhalten.
Lässt sich ein Datenfluss nicht anhalten, ist vor der Umschaltung zu klären, wie die in dieser Zeit angefallenen Daten nachgetragen werden — ein zweiter Export ersetzt den ersten nicht, sondern ist ein eigener Migrationsschritt.
Zugriff & Navigation
Die Kontomigration ist kein Bestandteil der niotix-Weboberfläche — es gibt keinen Menüpunkt und keine Ansicht dafür. Sie wird ausschließlich über die Kommandozeile ausgeführt:
- Auf der Quellseite aus dem niotix-Repository heraus über das npm-Skript
migrate:tenantbzw. direkt überbin/tenant-migrate.js. - Auf der Zielseite in der Regel über das bereitgestellte Container-Image, gesteuert per Docker Compose (siehe Beispiel: Import über Docker Compose).
Alle Protokollausgaben — und auch die Passwortabfrage — schreibt das Werkzeug auf stderr; stdout bleibt frei. Eine Protokolldatei legt das Werkzeug nicht selbst an — die Ausgabe ist bei Bedarf mit tee mitzuschreiben, damit die Passwortabfrage sichtbar bleibt.
Ablauf einer Migration
Eine vollständige Migration besteht aus folgenden Schritten:
- Stammdaten exportieren (
export) — bei großen Kontenbäumen in mehreren Schritten. - Verpacken (
pack) und Archiv sowie Passwort auf getrennten Wegen übergeben. - Stammdaten importieren (
import) — Schritt für Schritt, Eltern vor Kindern. - Zeitreihen exportieren (
export --timeseries), verpacken und übergeben — erst, wenn alle Stammdaten-Snapshots importiert sind. - Zeitreihen importieren (
import). - Zielinstanz verifizieren, Integrationen erneut aktivieren und umschalten.
Phasen des Exports
- Start und Hinweise — gemeldet werden die zurückgestellte Tabelle (Modulregistrierung) und bei einem Stammdaten-Export die Zeitreihentabellen, die stattdessen mit
export --timeserieszu exportieren sind; bei--skipaußerdem die ausgelassenen Tabellen. - Prüfung des API-Schlüssels — Existenz, Typ, Aktivzustand und Gültigkeitsdatum.
- Auflösung der Auswahl — Kontenmenge, zugehörige Zugriffsbereiche einschließlich der Zwillings-Unterbereiche, Benutzer- und Zwillings-IDs.
- Rechteprüfung je Konto — für jedes ausgewählte Konto wird
all.manageverlangt. - Vorabbericht zu den Gerätetreibern (nur Stammdaten) — noch bevor die erste Zeile geschrieben wird zeigt eine Übersicht, welche Gerätetreiber mitgehen, welche geerbt und welche nicht migrierbar sind.
- Prüfung des Dateispeichers (nur Stammdaten) — der Quell-Bucket muss erreichbar sein, bevor das Snapshot-Verzeichnis angelegt wird.
- Vorbereitung des Snapshot-Verzeichnisses — Prüfung auf Fortsetzung oder Bereinigung, Schreiben der Metadaten
_meta.jsoninklusive Migrationsstand, Spaltenschema der Quelle und der höchsten IDs der Quelltabellen (Grundlage für den ID-Versatz). - Export der Tabellen — Tabelle für Tabelle, gekennzeichnet als
[n/70]. Jede Tabelle wird zunächst als.jsonl.partialgeschrieben und erst nach vollständigem Durchlauf umbenannt. Nach jeder Tabelle wird_summary.jsonaktualisiert. - Export der gespeicherten Dateien — referenzierte Dateien werden aus dem Bucket in das Unterverzeichnis
files/geladen und in_files.jsonmit Größe und Prüfsumme verzeichnet. - Abschlussprüfung — der fertige Snapshot einschließlich der Dateien wird von der Platte neu gelesen und geprüft; Ergebnis ist
_audit.json. - Abschlussmeldung mit den Zeilenzahlen je Tabelle und der Anzahl übertragener bzw. fehlender Dateien.
Schlägt die Abschlussprüfung fehl, wird das Verzeichnis in <Verzeichnis>.REJECTED umbenannt und das Werkzeug beendet sich mit Code 3. Ein solcher Snapshot darf weder verpackt, übergeben noch importiert werden.
Verpacken für die Übergabe
pack verpackt einen geprüften Snapshot in ein ZIP-Archiv, in dem jeder Eintrag AES-256-verschlüsselt ist. Vor dem Verpacken prüft das Werkzeug, dass _meta.json vorliegt, keine unvollständigen .partial-Dateien vorhanden sind und _audit.json das Urteil pass trägt und zum Snapshot passt. Nach dem Schreiben wird das Archiv vollständig zurückgelesen und geprüft; anschließend wird das unverschlüsselte Snapshot-Verzeichnis gelöscht.
Jeder Eintrag trägt eine Authentifizierungsprüfsumme. Eine gesonderte Prüfsumme muss daher nicht mitgeschickt werden — ein beschädigtes oder verändertes Archiv wird beim Import erkannt.
Das Archiv lässt sich bei Bedarf mit 7-Zip, WinZip, Keka oder bsdtar öffnen, nicht jedoch mit dem Windows-Explorer, dem macOS-Archivierungsprogramm oder Info-ZIP unzip. Dateinamen und -größen der Einträge sind im ZIP-Format ohne Passwort lesbar; verschlüsselt sind nur die Inhalte.
Übertragungsweg, Übermittlung des Passworts und die Vernichtung aller Kopien nach erfolgreicher Migration bleiben organisatorische Schritte.
Phasen des Imports
Alle Prüfungen, die ohne Schreibzugriff möglich sind, laufen vor dem Transaktionsbeginn:
- Archiv öffnen (nur bei ZIP-Archiv) — Passwort abfragen und das Archiv in ein temporäres Verzeichnis entpacken. Das temporäre Verzeichnis wird am Ende in jedem Fall entfernt.
_meta.jsonmuss vorhanden und lesbar sein.- Prüfbericht
_audit.jsonmuss vorliegen, das Urteilpasstragen und zu Kontenmenge, API-Schlüssel und Dateiliste des Snapshots passen. - Werkzeugversion des Snapshots muss der Version des ausführenden Werkzeugs entsprechen.
- Hinweis auf ausgelassene Tabellen, falls der Export mit
--skiperfolgte. - Prüfung der Dateien — jede Datei im Snapshot gegen
_files.json, Erreichbarkeit des Ziel-Buckets und Kollisionsprüfung im Bucket. Dies geschieht noch vor dem Aufbau der Datenbankverbindung. - Datenbankverbindung mit Zeitlimits: unbegrenzte Laufzeit für die eigene Arbeit, begrenzte Wartezeit auf fremde Sperren.
- Migrationsstand von Quelle und Ziel wird verglichen — Abweichungen werden als Warnung gemeldet.
- Schemavergleich Spalte für Spalte zwischen Snapshot und Zieldatenbank.
- Wurzelkonto der Zielinstanz ermitteln und gegen das Ankerkonto prüfen.
- Kollisionsvorprüfung — in Blöcken von 5.000 IDs wird geprüft, ob bereits fremde Datensätze auf den zu vergebenden IDs liegen oder ob eindeutige Werte (z. B. die E-Mail-Adresse eines Benutzers oder der Name eines Kontos) auf der Zielinstanz bereits unter einer anderen ID vergeben sind.
- ID-Versatz — die ID-Zähler der Zielinstanz werden einmalig um 1.000.000.000 verschoben (siehe ID-Versatz auf der Zielinstanz).
Anschließend beginnt eine einzige Transaktion:
- Einspielen der Tabellen in festgelegter Reihenfolge; berücksichtigt werden nur Tabellen, für die eine Datei vorliegt.
- Vollständigkeitsprüfung — gelesene Zeilen gegen die im Export protokollierten Zeilenzahlen.
- Neuaufbau der Hierarchie-Hilfstabellen, Nachziehen der Sequenzen und — bei
--skip— Bericht, ob ein späteres Nachladen der ausgelassenen Zeilen möglich ist. - Bestätigung der Transaktion mit einer Übersicht gelesener und eingefügter Zeilen.
Nach der Bestätigung:
- Ablegen der Dateien im Ziel-Bucket — zuerst der Inhalt, dann die Metadaten je Datei.
Der Datenbankteil des Imports ist nicht fortsetzbar und kennt keine Zwischenstände: Bricht er an irgendeiner Stelle ab, wird die gesamte Transaktion zurückgerollt und die Daten der Zielinstanz bleiben unverändert. Die Meldung lautet dann „import rolled back; the target is unchanged. Fix the cause and re-run the same import." Ein erneuter Aufruf desselben Imports ist gefahrlos möglich — bereits vorhandene Zeilen und Dateien werden übersprungen. Ausnahmen: Der ID-Versatz bleibt nach einem Abbruch bestehen (unschädlich), und können nach der Bestätigung nicht alle Dateien abgelegt werden, endet der Import mit Rückgabewert 5 — die Datenbank ist dann bereits übernommen, und derselbe Import ist erneut auszuführen.
Einen Probelauf-Modus gibt es nicht. Ein „Testexport" bedeutet, den Export gegen eine Kopie der Quelldatenbank laufen zu lassen — es existiert kein entsprechender Schalter am Werkzeug.
Zeitreihen-Export und -Import
Der Zeitreihen-Snapshot ist auf große Datenmengen ausgelegt und arbeitet deshalb anders als der Stammdaten-Snapshot:
Export (export --timeseries)
- Planung — die Zeilen werden je Digitalem Zwilling bzw. je Regel gezählt und in Pakete von etwa 1.000.000 Zeilen aufgeteilt (
MIGRATE_TIMESERIES_CHUNK_ROWS). Der Plan wird in_timeseries.jsonabgelegt; die Protokollzeile nennt die geplante Zeilenzahl und eignet sich zur Abschätzung von Platzbedarf und Laufzeit. - Export in nummerierte Teildateien
<tabelle>.00001.jsonl,<tabelle>.00002.jsonl… — gekennzeichnet als[n/3], der Fortschritt wird in Prozent der geplanten Zeilen angezeigt. - Fortsetzung mit
--resumeauf Ebene der einzelnen Pakete. - Abschlussprüfung wie beim Stammdaten-Export.
Ein Zeitreihen-Export benötigt keinen Zugriff auf den Dateispeicher und keinen Vorabbericht zu Gerätetreibern. Geotrigger-Einträge zu Zwillingen außerhalb der Auswahl werden mit einer Warnung ausgelassen.
Import
- Zuerst wird geprüft, ob die zugehörigen Digitalen Zwillinge und Regeln auf der Zielinstanz vorhanden sind. Ist keiner davon vorhanden, bricht der Import ab, ohne etwas zu schreiben: Der Stammdaten-Snapshot ist zuerst zu importieren. Fehlen nur einzelne, werden deren Zeilen mit einer Warnung übersprungen.
- Eingespielt wird in einer Transaktion je Teildatei. Bricht der Import ab, bleiben die bereits bestätigten Teildateien erhalten; ein erneuter Aufruf desselben Imports setzt nach der letzten bestätigten Teildatei fort.
- Rückgabewert 5 tritt hier nicht auf, da kein Dateispeicher beteiligt ist.
Schnellstart für den Import
Für den Betreiber der Zielinstanz, der ein übergebenes Archiv einspielt:
-
Wartungsfenster vereinbaren und eine Sicherung der Zieldatenbank erstellen.
-
Das Archiv als
snapshot/snapshot.zipneben der Compose-Datei ablegen. Entschlüsseln oder Entpacken ist nicht erforderlich. -
In der Umgebungsdatei
migrate.envdie Verbindungsdaten der Zieldatenbank (MIGRATE_DST_URL) und des Dateispeichers (GLOBAL_S3_*) hinterlegen undTENANT_MIGRATE_TAGauf die Werkzeugversion des Snapshots setzen. -
Import in einem interaktiven Terminal ausführen, das Archivpasswort eingeben und die Ausgabe mitschreiben:
docker compose run --rm tenant-migrate 2>&1 | tee import.log; echo "exit=${PIPESTATUS[0]}" -
Rückgabewert prüfen:
0bedeutet, dass die Transaktion bestätigt und alle Dateien abgelegt wurden.5bedeutet, dass die Datenbank übernommen ist, aber Dateien fehlen — Zugriff auf den Bucket prüfen und denselben Import erneut ausführen. Jeder andere Wert bedeutet, dass die Daten der Zielinstanz unverändert sind. -
Zielinstanz verifizieren (siehe Verifikation und Rückabwicklung).
-
Für den Zeitreihen-Snapshot die Schritte 2 bis 6 mit
series.zipwiederholen (siehe Beispiel: Zeitreihen nachziehen). -
Archive und Übertragungskopien sicher vernichten.
Kommandos und Optionen
Das Werkzeug kennt vier Kommandos: export, import, pack und unpack. Positionelle Argumente sind nicht zulässig, unbekannte Optionen führen zum Abbruch. Ein Aufruf ohne Argumente gibt die Kurzhilfe aus und endet mit Rückgabewert 1; --help bzw. -h gibt die Hilfe aus und endet mit 0.
Kommando export
| Option | Wert | Pflicht | Bedeutung |
|---|---|---|---|
--src-url=<url> |
postgres://benutzer:passwort@host:port/datenbank |
ja | Quelldatenbank; wird ausschließlich gelesen. Alternativ über MIGRATE_SRC_URL. |
--api-key=<uuid> |
UUID | ja | Autorisierender API-Schlüssel der Quellinstanz. Alternativ über MIGRATE_API_KEY. |
--out=<verzeichnis> |
Pfad | ja | Zielverzeichnis des Snapshots. |
--anchor=<id> |
positive Ganzzahl | eines von beiden | Ankerkonto dieses Schritts. |
--accounts=<id,id> |
Liste positiver Ganzzahlen | eines von beiden | Konten dieses Schritts. Ohne Angabe gilt das Ankerkonto. |
--mode=single|recursive |
Zeichenkette | nein, Standard single |
recursive nimmt die angegebenen Konten samt allen Unterkonten auf. |
--exclude=<id,id> |
Liste | nein | Nimmt die genannten Konten samt ihren Teilbäumen aus. |
--skip=<tabelle,tabelle> |
Liste | nein | Lässt als optional gekennzeichnete Tabellen aus: activityLogs (Stammdaten) bzw. dtwin-rule-executions und dtwin-rules-geotrigger-history (Zeitreihen). |
--timeseries |
Schalter | nein | Exportiert ausschließlich die drei Zeitreihentabellen als eigenen Snapshot. |
--resume |
Schalter | nein | Setzt einen abgebrochenen Export fort. |
--exclude ist ausschließlich mit --mode=recursive zulässig; in Verbindung mit --mode=single bricht der Export mit der Meldung „–exclude is only valid with –mode=recursive" ab. Werden --anchor und --accounts gemeinsam angegeben, bestimmt --accounts die Kontenmenge, und --anchor wird lediglich dagegen geprüft.
Auslassen von Tabellen mit --skip: Ausgelassene Tabellen werden in _meta.json mit ihrer höchsten Quell-ID vermerkt. Der Import weist darauf hin, dass die Konten ohne diese Zeilen ankommen, und meldet am Ende, ob ein späteres Nachladen möglich ist. Zum Nachladen wird dieselbe Auswahl erneut ohne --skip exportiert und über die bestehende Migration importiert — und zwar bevor die Quellkonten gelöscht werden.
Fortsetzung mit --resume: Die Fortsetzung arbeitet beim Stammdaten-Export auf Tabellenebene, beim Zeitreihen-Export auf Paketebene: Bereits vollständig geschriebene Tabellen bzw. Pakete und Dateien werden nicht erneut abgefragt. Weichen die Rahmenbedingungen der Fortsetzung von denen des ursprünglichen Laufs ab — andere Konten, anderer API-Schlüssel, andere Quelldatenbank, veränderte Tabellenauswahl, geändertes --skip oder --timeseries —, verweigert das Werkzeug die Fortsetzung und benennt das abweichende Merkmal.
Ohne --resume leert der Export ein bereits vorhandenes Snapshot-Verzeichnis in --out. Enthält das Verzeichnis ein Unterverzeichnis files/, aber keine Metadaten eines Snapshots, verweigert das Werkzeug den Export, um keine fremden Daten zu löschen.
Kommando import
| Option | Wert | Pflicht | Bedeutung |
|---|---|---|---|
--dst-url=<url> |
postgres://benutzer:passwort@host:port/datenbank |
ja | Zieldatenbank; die einzige Datenbank, in die geschrieben wird. Alternativ über MIGRATE_DST_URL. |
--in=<pfad> |
Verzeichnis oder .zip |
ja | Einzuspielender Snapshot — als von pack erzeugtes Archiv oder als Verzeichnis. Ein Archiv wird am Dateiinhalt erkannt; das Passwort wird abgefragt. |
Das Ankerkonto, die Kontenauswahl und die Art des Snapshots (Stammdaten oder Zeitreihen) werden dem Snapshot entnommen — beim Import sind sie nicht erneut anzugeben.
Da alle Kommandos dieselbe Optionsliste teilen, akzeptiert import auch reine Export-Optionen wie --resume, --skip oder --anchor, ohne sie auszuwerten und ohne Warnung. Für den Import sind ausschließlich --dst-url und --in wirksam.
Kommandos pack und unpack
| Kommando | Option | Pflicht | Bedeutung |
|---|---|---|---|
pack |
--in=<verzeichnis> |
ja | Geprüftes Snapshot-Verzeichnis. Wird nach erfolgreichem Verpacken gelöscht. |
pack |
--out=<datei.zip> |
nein, Standard <verzeichnis>.zip |
Zu erzeugendes Archiv. Es darf noch nicht existieren und nicht innerhalb von --in liegen. |
unpack |
--in=<datei.zip> |
ja | Zu entpackendes Archiv. |
unpack |
--out=<verzeichnis> |
ja | Zielverzeichnis; muss leer sein. |
unpack wird für den regulären Import nicht benötigt, da import das Archiv direkt liest. Es dient der Einsicht in den Inhalt eines Archivs.
Archivpasswort
Das Archivpasswort wird ausschließlich über eine verdeckte Eingabeaufforderung abgefragt — es gibt bewusst keine Option, keine Umgebungsvariable und keine Datei dafür, damit es weder in Prozesslisten noch in der Shell-Historie oder in Protokollen erscheint.
- Bei
packwird das Passwort zweimal abgefragt („Archive password:" und „Repeat archive password:"). - Es muss mindestens 12 Zeichen lang sein und einen Großbuchstaben, einen Kleinbuchstaben, eine Ziffer und ein Sonderzeichen enthalten. Empfohlen werden 20 oder mehr Zeichen aus einem Passwortmanager.
- Das Passwort ist auf einem anderen Weg als das Archiv zu übermitteln.
Konfiguration
Eine Konfigurationsdatei besitzt das Werkzeug nicht. Gesteuert wird es über Kommandozeilenoptionen und Umgebungsvariablen; alle übrigen Werte sind fest hinterlegt.
Umgebungsvariablen
| Variable | Standard | Bedeutung |
|---|---|---|
MIGRATE_SRC_URL |
— | Quelldatenbank; hat Vorrang vor --src-url. |
MIGRATE_DST_URL |
— | Zieldatenbank; hat Vorrang vor --dst-url. |
MIGRATE_API_KEY |
— | API-Schlüssel; hat Vorrang vor --api-key. |
GLOBAL_S3_KEY, GLOBAL_S3_SECRET, GLOBAL_S3_BUCKET |
— | Zugang zum Dateispeicher. Pflicht für jeden Stammdaten-Export und für jeden Import eines Snapshots mit Dateien. Die Werte entsprechen denen der jeweiligen niotix-Instanz. |
GLOBAL_S3_ENDPOINT, GLOBAL_S3_REGION, GLOBAL_S3_FORCEPATHSTYLE, GLOBAL_S3_SIGVERSION |
— | Weitere Angaben zum Dateispeicher, wie in der niotix-Instanz gesetzt. Für MinIO typischerweise Endpunkt, true und v4. |
MIGRATE_TIMESERIES_CHUNK_ROWS |
1000000 |
Zeilen je Teildatei eines Zeitreihen-Exports. |
MIGRATE_LOCK_TIMEOUT |
30s |
Wartezeit des Imports auf konkurrierende Datenbanksperren, als PostgreSQL-Intervallangabe. |
MIGRATE_DEBUG |
nicht gesetzt | Mit 1 werden zusätzliche Diagnosemeldungen ausgegeben. |
MIGRATE_ALLOW_VERSION_SKEW |
nicht gesetzt | Mit 1 wird die Versionsprüfung zur Warnung herabgesetzt. Nur nach Abstimmung mit DIGIMONDO verwenden. |
MIGRATE_COLOR |
automatisch | 1 erzwingt farbige Ausgabe, 0 schaltet sie ab. |
NO_COLOR |
nicht gesetzt | Schaltet farbige Ausgabe ab. |
TMPDIR |
Systemstandard | Verzeichnis, in das der Import ein Archiv temporär entpackt. Bei knappem Platz auf ein eingebundenes Volume zeigen lassen. |
TENANT_MIGRATE_IMAGE |
registry.gitlab.com/digimondo-code/niotix/tenant-migrate |
Container-Image für den Import über Compose. |
TENANT_MIGRATE_TAG |
kein Standard | Image-Tag, z. B. 1.2.0. Ohne Angabe startet Compose nicht. |
Verbindungsdaten und API-Schlüssel sind vorzugsweise über die Umgebungsvariablen zu übergeben, da Kommandozeilenoptionen in Prozesslisten und in der Shell-Historie sichtbar sind. Der Wert von MIGRATE_LOCK_TIMEOUT wird unverändert an PostgreSQL übergeben und nicht vorab geprüft; eine ungültige Angabe führt zum Abbruch, bevor etwas geschrieben wird.
Feste Werte im Werkzeug
| Wert | Größe | Bedeutung |
|---|---|---|
| Werkzeugversion | 1.2.0 |
Muss zwischen Snapshot und ausführendem Werkzeug übereinstimmen. |
| Leseblock beim Export | 1.000 Zeilen | Blockgröße des serverseitigen Cursors. |
| Schreibblock beim Import | 1.000 Zeilen | Zeilen je Sammel-INSERT. |
| Block der Kollisionsvorprüfung | 5.000 IDs | IDs je Prüfabfrage. |
| Teildatei im Zeitreihen-Export | 1.000.000 Zeilen | Über MIGRATE_TIMESERIES_CHUNK_ROWS anpassbar. |
| ID-Versatz | 1.000.000.000 | Einmalige Verschiebung der ID-Zähler der Zielinstanz. |
| Berücksichtigte Sequenzen | 40 | ID-Zähler, die versetzt und nach dem Import nachgezogen werden. |
| Fortschrittsmeldung | alle 5 Sekunden | Lebenszeichen je laufender Tabelle. |
| Verbindungen im Pool | maximal 5 | Für die Datenbankarbeit wird eine Verbindung je Lauf genutzt. |
| Verbindungsaufbau | 10 Sekunden | Zeitlimit für den Aufbau der Datenbankverbindung. |
| Leerlauf einer Verbindung | 30 Sekunden | Zeitlimit für ungenutzte Verbindungen. |
| Zeitlimit im Import | unbegrenzt | Für Anweisungen und Leerlauf innerhalb der Transaktion. |
| Sperrwartezeit im Import | 30 Sekunden | Über MIGRATE_LOCK_TIMEOUT anpassbar. |
| Parallele Dateiübertragungen | 8 | Gleichzeitiges Laden bzw. Ablegen von Dateien. |
| Wiederholversuche beim Laden von Dateien | 3, Abstand 500 ms | Nur für Downloads aus dem Dateispeicher. |
| Verschlüsselung des Archivs | AES-256 | Je Eintrag, mit Authentifizierungsprüfsumme. |
| Mindestlänge des Archivpassworts | 12 Zeichen | Zusätzlich Groß-, Kleinbuchstabe, Ziffer, Sonderzeichen. |
| Gemeldete Verstöße im Prüfbericht | bis zu 50 gespeichert, 10 protokolliert | Umfang der Fehlerausgabe der Abschlussprüfung. |
| Gemeldete Kollisionen bzw. Schemaabweichungen | bis zu 40 | Weitere werden als Anzahl zusammengefasst. |
Die Datenbankarbeit erfolgt nicht parallel: Export und Import arbeiten Tabelle für Tabelle über eine einzige Datenbankverbindung. Blockgrößen und Nebenläufigkeit sind — abgesehen von der Teildateigröße der Zeitreihen — nicht konfigurierbar. Wiederholversuche auf Datenbankebene kennt das Werkzeug nicht — die Wiederherstellung erfolgt über --resume beim Export bzw. über einen erneuten Aufruf desselben Imports.
Beispiele
Die folgenden Aufrufe verwenden die Umgebungsvariablen $SRC, $DST und $KEY für Verbindungsdaten und API-Schlüssel. Für den Dateispeicher sind jeweils die GLOBAL_S3_*-Variablen der eigenen Instanz gesetzt.
Beispiel: Konto mit allen Unterkonten
Auf der Quellseite exportieren und verpacken:
export MIGRATE_SRC_URL='postgres://benutzer:passwort@host:5432/niotix'
export MIGRATE_API_KEY='<UUID des all.manage-Schlüssels>'
tenant-migrate export --anchor=23 --mode=recursive --out=./snap/a
tenant-migrate pack --in=./snap/a # erzeugt ./snap/a.zip, fragt das Passwort ab
Anschließend auf der Zielseite:
export MIGRATE_DST_URL='postgres://benutzer:passwort@host:5432/niotix'
tenant-migrate import --in=./a.zip # fragt das Passwort ab
Beispiel: Teilbaum ohne einzelne Unterkonten
npm run migrate:tenant -- export --src-url=$SRC --api-key=$KEY \
--anchor=23 --mode=recursive --exclude=99 --out=./snap/a
Konto 99 wird samt seinem Teilbaum ausgenommen.
Beispiel: Migration in mehreren Schritten
Große Kontenbäume lassen sich in mehreren Schritten migrieren. Der erste Schritt trägt das Ankerkonto, die folgenden hängen weitere Konten an die bereits migrierte Struktur:
npm run migrate:tenant -- export --src-url=$SRC --api-key=$KEY --anchor=23 --out=./snap/step1
npm run migrate:tenant -- pack --in=./snap/step1
npm run migrate:tenant -- import --dst-url=$DST --in=./snap/step1.zip
npm run migrate:tenant -- export --src-url=$SRC --api-key=$KEY --accounts=24 --out=./snap/step2
npm run migrate:tenant -- pack --in=./snap/step2
npm run migrate:tenant -- import --dst-url=$DST --in=./snap/step2.zip
Jeder Schritt setzt voraus, dass das Elternkonto bereits auf dem Zielsystem vorhanden ist — andernfalls bricht der Import mit dem Hinweis auf die Reihenfolge Eltern-vor-Kind ab. Das Einfrieren der Quellkonten gilt über alle Schritte hinweg.
Beispiel: Export ohne Aktivitätsprotokolle
Umfangreiche Systemprotokolle lassen sich zunächst auslassen, um das Wartungsfenster kurz zu halten:
npm run migrate:tenant -- export --src-url=$SRC --api-key=$KEY \
--anchor=23 --mode=recursive --skip=activityLogs --out=./snap/a
Die Konten kommen ohne Systemprotokolle an. Zum Nachladen wird dieselbe Auswahl später ohne --skip exportiert und importiert — vor dem Löschen der Quellkonten.
Beispiel: Zeitreihen nachziehen
Nachdem alle Stammdaten-Snapshots importiert sind, werden die Zeitreihen mit derselben Auswahl exportiert:
npm run migrate:tenant -- export --timeseries --src-url=$SRC --api-key=$KEY \
--anchor=23 --mode=recursive --out=./snap/series
npm run migrate:tenant -- pack --in=./snap/series # eigenes Passwort vergeben
Auf der Zielseite über Compose, mit dem Archiv als snapshot/series.zip:
docker compose run --rm tenant-migrate import --in=/snapshot/series.zip
Beispiel: Import über Docker Compose
Die mitgelieferte Compose-Konfiguration führt standardmäßig das Kommando import --in=/snapshot/snapshot.zip aus und bindet das lokale Verzeichnis ./snapshot schreibgeschützt ein. Zu setzen sind der Image-Tag sowie in migrate.env die Verbindungsdaten der Zieldatenbank und des Dateispeichers:
# migrate.env
MIGRATE_DST_URL=postgres://benutzer:passwort@host:5432/niotix
GLOBAL_S3_KEY=…
GLOBAL_S3_SECRET=…
GLOBAL_S3_BUCKET=…
GLOBAL_S3_ENDPOINT=http://host:9000
GLOBAL_S3_FORCEPATHSTYLE=true
GLOBAL_S3_SIGVERSION=v4
export TENANT_MIGRATE_TAG=1.2.0
docker compose run --rm tenant-migrate 2>&1 | tee import.log; echo "exit=${PIPESTATUS[0]}"
Der Container läuft in einem eigenen Compose-Netzwerk. Dienstnamen der niotix-Installation wie postgres oder minio sind darin nicht auflösbar. Entweder in migrate.env erreichbare Adressen eintragen oder die vorbereiteten networks:-Abschnitte der Compose-Datei aktivieren, um dem niotix-Netzwerk beizutreten. Die Ausgabe nicht mit 2> import.log umleiten — sonst ist die Passwortabfrage nicht sichtbar.
Artefakte des Snapshots
Im Verzeichnis aus --out entstehen:
| Datei | Inhalt |
|---|---|
_meta.json |
Zeitpunkt, Werkzeugversion, Kennung des Laufs, Art des Snapshots (Stammdaten oder Zeitreihen), Auswahlmodus mit Anker-, Konten-, Zugriffsbereichs- und Ausschluss-IDs, Anzahl und Prüfsumme der Benutzer, Tabellenliste, ausgelassene Tabellen, höchste IDs der Quelltabellen, gekürzte Quell-Verbindungsangabe, Angaben zur Autorisierung, Migrationsstand und Spaltenschema der Quelle |
_summary.json |
Exportierte Zeilenzahl je Tabelle — Grundlage der Vollständigkeitsprüfung beim Import |
_audit.json |
Urteil der Abschlussprüfung, Prüfzeitpunkt, Fingerabdruck des API-Schlüssels, geprüfte Konten, Prüfsumme der Dateiliste, Gesamtzahlen zu Dateien, Zeilen und Verstößen sowie die Verstöße im Einzelnen |
_files.json |
Liste der übertragenen Dateien mit Größe, Prüfsummen, verweisenden Datensätzen und fehlenden Dateien |
files/ |
Gespeicherte Dateien — je Datei Inhalt und reduzierte Metadaten |
_timeseries.json |
Nur Zeitreihen-Snapshot: Plan der Pakete |
<tabelle>.jsonl |
Je Datenbanktabelle eine Datei mit einer Zeile je Datensatz — bis zu 70 Dateien bei einem Stammdaten-Snapshot |
<tabelle>.00001.jsonl … |
Nur Zeitreihen-Snapshot: nummerierte Teildateien |
<tabelle>.jsonl.partial |
Nur während des Laufs vorhanden; wird nach vollständigem Export umbenannt |
<verzeichnis>.REJECTED |
Umbenanntes Verzeichnis nach fehlgeschlagener Abschlussprüfung; existiert es bereits, wird .REJECTED.<n> verwendet. Eine frühere Zurückweisung wird nie überschrieben. |
<verzeichnis>.zip |
Von pack erzeugtes verschlüsseltes Archiv; das Verzeichnis selbst ist danach gelöscht. |
In _meta.json werden keine Benutzer-IDs hinterlegt, sondern nur deren Anzahl und eine Prüfsumme; die Verbindungsangabe zur Quelle wird gekürzt gespeichert.
Prüfungen und Schutzmechanismen
Im Export
- Rechteprüfung je Konto vor dem ersten Schreibvorgang: Fehlt
all.managefür ein ausgewähltes Konto, bricht der Export ab und nennt die Konten, für die der Schlüssel zulässig wäre. - Warnung bei Wurzelbereich: Trägt der API-Schlüssel
all.manageauf dem Wurzelbereich der Instanz, kann er jedes Konto exportieren — das Werkzeug weist ausdrücklich darauf hin. - Abschlussprüfung des geschriebenen Snapshots mit vier Verstoßarten: Datensätze außerhalb der Berechtigung, Datensätze außerhalb der Auswahl, Verweise ins Leere sowie Dateien und Datensätze, die keiner Regel der Tabellenliste zugeordnet werden können. Das Verzeichnis
files/wird dabei wie eine Tabelle geprüft. - Vorabbericht zu Gerätetreibern: gemeldet werden fehlende Treiber, Treiber fremder Konten, die nicht mitgehen können, sowie geerbte, aber nicht öffentlich sichtbare Treiber, die auf dem Zielsystem an das Wurzelkonto gehängt werden und dort nur mit entsprechender Berechtigung auflösbar sind.
- Prüfung vor dem Verpacken:
packverpackt nur vollständige Snapshots mit bestandener Abschlussprüfung und prüft das fertige Archiv durch erneutes Lesen.
Im Import
- Archivprüfung: Ein falsches Passwort, ein beschädigtes Archiv oder unverschlüsselte bzw. unzulässige Einträge führen zum Abbruch, bevor etwas geschrieben wird.
- Prüfberichtssperre: Ohne
_audit.jsonmit dem Urteilpasswird nicht importiert. Der Bericht muss zur Kontenmenge, zum API-Schlüssel und zur Dateiliste des Snapshots passen — ein von anderswo hineinkopierter Prüfbericht wird erkannt. - Versionssperre zwischen Snapshot und Werkzeug.
- Schemavergleich: Fehlt eine Spalte des Snapshots auf dem Ziel, fehlt eine ganze Tabelle oder besitzt das Ziel eine neue Pflichtspalte ohne Standardwert, bricht der Import ab. Zusätzliche Zielspalten mit Standardwert sowie abweichende Spaltentypen führen nur zu einer Warnung.
- Kollisionsvorprüfung: Liegt auf einer zu vergebenden ID bereits ein anderer Datensatz oder ist ein eindeutiger Wert — etwa die E-Mail-Adresse eines Benutzers oder ein Kontoname — auf der Zielinstanz bereits unter einer anderen ID vergeben, bricht der Import unter Angabe von Tabelle, ID bzw. Wert ab.
- Prüfung der Dateien: Jede Datei wird gegen Größe und Prüfsumme aus
_files.jsongeprüft. Liegt im Ziel-Bucket unter derselben Kennung bereits eine andere Datei, bricht der Import ab, bevor etwas geschrieben wird. Metadaten mit nicht zulässigen Feldern werden zurückgewiesen. - Vollständigkeitsprüfung gegen
_summary.json. Verglichen werden gelesene, nicht eingefügte Zeilen — damit ein zweiter Aufruf desselben Imports nicht als unvollständig gilt. - Genau eine Hauptkontozuordnung je migriertem Benutzer wird erzwungen.
ID-Versatz auf der Zielinstanz
Da die Zielinstanz während und zwischen den Migrationsschritten in Betrieb sein kann, könnten neu angelegte Objekte IDs belegen, die ein späterer Migrationsschritt benötigt. Um das auszuschließen, verschiebt der Import vor Transaktionsbeginn die ID-Zähler der Zielinstanz einmalig auf einen Wert oberhalb von 1.000.000.000 — über die höchste vorhandene und die höchste im Quellsystem vergebene ID hinaus.
- Der Versatz erfolgt einmal je Zielinstanz; ein bereits versetzter Zähler bleibt unverändert.
- Er betrifft alle 40 berücksichtigten Zähler, auch solche von Tabellen, die im Snapshot nicht vorkommen.
- Kann eine ID-Spalte den Wert technisch nicht aufnehmen, wird der Versatz für diese Tabelle mit einer Warnung ausgelassen.
- Der Versatz ist nicht Teil der Transaktion und bleibt nach einem abgebrochenen Import bestehen. Das ist unschädlich: Ein erneuter Aufruf erkennt ihn.
Nach der Migration neu angelegte Konten, Digitale Zwillinge, Konnektoren usw. erhalten daher zehnstellige IDs, die auch in URLs der Weboberfläche erscheinen. Dies ist erwartetes Verhalten.
Verifikation und Rückabwicklung
Nach dem Import zu prüfen
- Zeilenzahlen auf der Zielinstanz gegen
_summary.jsonabgleichen. - Einordnung des Ankerkontos und die Hierarchieebenen der migrierten Konten kontrollieren.
- Stichproben auf erhaltene IDs bei Konten, Benutzern, Rollen, Digitalen Zwillingen und Virtuellen Geräten.
- Anmeldung eines realen Benutzers auf der Zielinstanz durchführen.
- Auflösung der Gerätetreiber prüfen — insbesondere bei geerbten Treibern, die an das Wurzelkonto gehängt wurden.
- Dashboards öffnen und auf vollständige Darstellung prüfen.
- Gespeicherte Dateien prüfen — etwa Zwillingsbilder, Kontologos und Anhänge öffnen. Die Anzahl der Objekte im Ziel-Bucket muss um das Doppelte der übertragenen Dateien gewachsen sein (Inhalt und Metadaten).
- Neuanlage testen — etwa ein neues Konto, einen Digitalen Zwilling oder einen Konnektor. Die neue ID muss oberhalb von 1.000.000.000 liegen; eine ID knapp über den migrierten IDs bedeutet, dass der ID-Versatz nicht gegriffen hat.
- Verbindungsaufbau der Konnektoren auf der Zielinstanz kontrollieren.
- Integrationen in der Modulverwaltung der migrierten Konten erneut aktivieren — sie kommen deaktiviert an.
- Tickets öffnen und prüfen, dass Tickettyp und -priorität angezeigt werden.
- Die Verknüpfungen der Zwillingszustands-Aggregate gegenprüfen: Ein Teil dieser Datensätze entsteht auf dem Zielsystem über einen Datenbank-Trigger neu und wird in der Import-Übersicht ausdrücklich zur manuellen Prüfung ausgewiesen.
- Nach dem Zeitreihen-Import: Zustandsverlauf eines Digitalen Zwillings und Ausführungsprotokoll einer Regel öffnen; Zeilenzahlen gegen
_summary.jsonabgleichen, abzüglich übersprungener Zwillinge bzw. Regeln.
Rückabwicklung
Ein Kommando zum Zurückrollen existiert nicht — der Schutz liegt in der Atomarität des Datenbankimports.
| Zeitpunkt des Abbruchs | Vorgehen |
|---|---|
| Während des Exports | Mit --resume fortsetzen oder ohne --resume in dasselbe Verzeichnis neu exportieren. |
| Vor dem Transaktionsbeginn des Imports, Rückgabewert 1, 3 oder 4 | Ursache beheben und denselben Import erneut ausführen. |
| Innerhalb der Import-Transaktion | Der Rollback ist bereits erfolgt; die Daten der Zielinstanz sind unverändert. Ursache beheben und erneut importieren. |
| Rückgabewert 5 | Die Datenbank ist übernommen, Dateien fehlen. Zugriff auf den Bucket prüfen und denselben Import erneut ausführen. |
| Während eines Zeitreihen-Imports | Bereits bestätigte Teildateien bleiben erhalten. Ursache beheben; ein erneuter Aufruf setzt fort. |
| Nach bestätigter Transaktion, Verifikation schlägt fehl | Die Quellkonten nicht löschen. Die eingefrorene Quellinstanz ist die Rückfallebene; der Fall ist zu eskalieren. |
| Nach bestätigter Transaktion und bereits gelöschten Quellkonten | Eine Rückabwicklung ist nicht möglich. |
Der Punkt ohne Wiederkehr ist die Bestätigung der Import-Transaktion. Die Quellkonten sind erst zu löschen, nachdem die Verifikation auf der Zielinstanz vollständig abgeschlossen ist, die Zeitreihen übertragen und ggf. mit --skip ausgelassene Tabellen nachgeladen wurden.
Berechtigungen
Auf der Quellinstanz autorisiert ein API-Schlüssel den Export. Er muss:
- als API-Schlüssel-Benutzer angelegt und aktiv sein,
- innerhalb seines Gültigkeitsdatums liegen und
- die Berechtigung
all.manageauf dem Wurzelbereich jedes ausgewählten Kontos oder einem übergeordneten Zugriffsbereich tragen.
Damit entspricht die verlangte Befugnis genau der, die zum Löschen des Kontos über die API nötig wäre. Ein persönlicher Anmeldezugang kann einen Export nicht autorisieren. Ein etwaiges Abfragekontingent des Schlüssels wird bewusst nicht ausgewertet. Grundlagen zum Rechtemodell: Berechtigungen und Berechtigungskonzept.
Datenbankrechte
| Seite | Benötigte Rechte |
|---|---|
| Quelldatenbank | Leserechte auf den Tabellen der Tabellenliste, auf information_schema sowie auf die Tabelle mit dem Migrationsstand |
| Zieldatenbank | SELECT, INSERT und DELETE auf den Tabellen der Tabellenliste und den Hierarchie-Hilfstabellen, Lesen und Setzen der Sequenzwerte sowie Lesezugriff auf information_schema, die PostgreSQL-Systemkataloge und die Tabelle mit dem Migrationsstand |
Dateispeicher
| Seite | Benötigte Rechte |
|---|---|
| Quell-Bucket | Bucket abfragen, Objekte lesen |
| Ziel-Bucket | Bucket abfragen, Objekte abfragen, lesen und schreiben (einschließlich mehrteiliger Uploads) |
Fehlermeldungen & Troubleshooting
Die Meldungen des Werkzeugs sind englischsprachig. Der Rückgabewert ordnet die Fehlerklasse zu:
| Rückgabewert | Bedeutung |
|---|---|
0 |
Erfolgreich abgeschlossen |
1 |
Aufruf- oder Laufzeitfehler, einschließlich verweigerter Fortsetzung, Kollision auf der Zielinstanz, Zeitreihen-Snapshot ohne zugehörige Konten, fehlendes, zu schwaches oder falsches Archivpasswort sowie nicht konfigurierter oder nicht erreichbarer Dateispeicher |
2 |
Problem mit dem API-Schlüssel |
3 |
Problem mit dem Snapshot — fehlender, fehlgeschlagener oder unpassender Prüfbericht, abgeschnittener oder unvollständiger Snapshot, ungültige Dateiliste oder beschädigtes Archiv |
4 |
Unverträgliches Schema oder abweichende Werkzeugversion |
5 |
Datenbank übernommen, aber nicht alle Dateien abgelegt — denselben Import erneut ausführen |
„Refusing to export: the supplied x-api-key does not hold all.manage on account(s) …"
Ursache: Der API-Schlüssel besitzt nicht auf allen ausgewählten Konten die Berechtigung all.manage. Die Meldung nennt die Konten, für die der Schlüssel zulässig wäre. Lösung: Auswahl auf diese Konten einschränken oder einen Schlüssel mit ausreichender Berechtigung auf einem übergeordneten Zugriffsbereich verwenden.
„The –api-key value is not a UUID" / „No user exists on the source for the supplied –api-key" / „…belongs to a regular user, not an api-key user" Ursache: Es wurde keine gültige Schlüssel-Kennung übergeben oder es handelt sich um einen persönlichen Zugang. Lösung: Die Kennung des API-Schlüssel-Benutzers der Quellinstanz verwenden.
„Anchor tenant N is not among the migrated accounts" / „Anchor tenant N has parent M, which is also in this selection" Ursache: Das Ankerkonto gehört nicht zur Auswahl oder ist nicht deren oberstes Konto. Lösung: Anker gemäß Ankerkonto korrigieren.
„–exclude is only valid with –mode=recursive"
Ursache: --exclude wurde ohne rekursiven Modus verwendet. Lösung: --mode=recursive ergänzen oder den Ausschluss entfernen.
„–skip names “…”, which the manifest does not mark optional"
Ursache: Die genannte Tabelle darf nicht ausgelassen werden oder gehört zur jeweils anderen Snapshot-Art. Die Meldung nennt die zulässigen Tabellen. Lösung: Nur activityLogs (Stammdaten) bzw. dtwin-rule-executions und dtwin-rules-geotrigger-history (Zeitreihen) angeben.
„Refusing to –resume: this export does not match the snapshot in –out"
Ursache: Die Rahmenbedingungen weichen vom ursprünglichen Lauf ab; die Meldung nennt das abweichende Merkmal. Lösung: Ursprüngliche Parameter verwenden, oder --resume weglassen und neu exportieren.
„Refusing to export into …: it holds a files/ directory but no _meta.json or _files.json"
Ursache: Das Verzeichnis in --out enthält Daten, die nicht von diesem Werkzeug stammen. Lösung: Ein leeres oder neues Verzeichnis angeben.
„… needs access to the file store, but GLOBAL_S3_… is/are not set"
Ursache: Die Zugangsdaten zum Dateispeicher fehlen. Lösung: Die GLOBAL_S3_*-Werte der niotix-Instanz übernehmen, gegen die die jeweilige Hälfte läuft.
Dateispeicher nicht erreichbar
Ursache: Der Bucket ist unter den angegebenen Zugangsdaten nicht erreichbar — häufig, weil der Container Dienstnamen wie minio nicht auflösen kann. Lösung: Erreichbare Adresse eintragen oder den Container dem niotix-Netzwerk beitreten lassen (siehe Beispiel: Import über Docker Compose).
Im Export fehlende Dateien
Ursache: Eine Datei, auf die ein Datensatz verweist, liegt nicht im Bucket der Quellinstanz — typischerweise, weil sie nach dem letzten Neustart der Quellinstanz noch nicht in den aktuellen Dateispeicher übernommen wurde. Lösung: Mit DIGIMONDO klären; nach einem Neustart der Quellinstanz den Export mit --resume fortsetzen.
„Snapshot audit FAILED — … quarantined to _audit.json im zurückgewiesenen Verzeichnis auswerten und den Auslöser klären. Ein solcher Snapshot darf nicht verpackt, übergeben oder importiert werden.
pack meldet einen unvollständigen Snapshot
Ursache: Im Verzeichnis liegen noch .partial-Dateien. Lösung: Den Export mit --resume abschließen und erneut verpacken.
„The archive password is typed at a prompt, and this command has no terminal to ask on"
Ursache: Das Kommando wurde ohne interaktives Terminal ausgeführt, etwa aus einem Skript oder mit docker compose run -T. Lösung: Aus einer interaktiven Shell ausführen; in Docker mit docker compose run ohne -T bzw. docker run -it.
„The archive password must contain at least 12 characters, …" / „The two passwords do not match" Ursache: Das Passwort erfüllt die Mindestanforderungen nicht bzw. die Wiederholung weicht ab. Lösung: Ein ausreichend starkes Passwort aus einem Passwortmanager verwenden. Es wurde nichts geschrieben.
„Wrong password for …" Ursache: Das eingegebene Passwort passt nicht zum Archiv. Lösung: Passwort über den vereinbarten Kanal erneut abgleichen.
Beschädigtes oder zurückgewiesenes Archiv (Rückgabewert 3) Ursache: Das Archiv wurde bei der Übertragung beschädigt oder verändert. Lösung: Archiv erneut übertragen und den Import wiederholen.
„No _audit.json in
„Refusing to commit: the snapshot does not hold every row the export wrote" Ursache: Der Snapshot ist unvollständig. Lösung: Archiv erneut übertragen und den Import wiederholen.
„Refusing to import: snapshot was written by tenant-migrate X, this is 1.2.0"
Ursache: Snapshot und Werkzeug stammen aus unterschiedlichen Versionen und können unterschiedliche Spalten erwarten. Lösung: Das Image mit dem passenden Tag verwenden. Die Umgehung über MIGRATE_ALLOW_VERSION_SKEW=1 ist nur nach Abstimmung mit DIGIMONDO vorgesehen.
„Refusing to import: the target schema cannot accept this snapshot" Ursache: Zielschema und Snapshot passen nicht zusammen — fehlende Spalte, fehlende Tabelle oder neue Pflichtspalte ohne Standardwert. Es wurde nichts geschrieben. Lösung: Die Zielinstanz auf denselben Migrationsstand wie die Quelle bringen und denselben Import erneut ausführen.
„Could not find a target root tenant" / „Found N candidate root tenants on target" Ursache: Auf der Zielinstanz existiert kein oder mehr als ein Wurzelkonto. Lösung: Zielinstanz korrekt bereitstellen; der Import benötigt genau ein Wurzelkonto.
„Refusing to import: N collision(s) on
„
„… none of the N … row(s) this snapshot holds data for is on the target" Ursache: Ein Zeitreihen-Snapshot wurde vor dem zugehörigen Stammdaten-Snapshot importiert. Lösung: Zuerst den Stammdaten-Snapshot importieren, dann denselben Zeitreihen-Import erneut ausführen. Es wurde nichts geschrieben.
„The DATABASE IMPORT IS COMMITTED — the accounts are on the target. Only these files are not." (Rückgabewert 5) Ursache: Nach der Bestätigung der Transaktion konnten nicht alle Dateien im Ziel-Bucket abgelegt werden. Lösung: Schreibzugriff auf den Bucket prüfen und denselben Import erneut ausführen; bereits vorhandene Zeilen und Dateien werden übersprungen.
„permission denied" beim Lesen von /snapshot
Ursache: Der Container läuft unprivilegiert und darf das eingebundene Verzeichnis nicht lesen. Lösung: Leserechte für das Snapshot-Verzeichnis setzen, etwa mit chmod -R a+rX snapshot.
Compose startet nicht und verlangt einen Tag
Ursache: TENANT_MIGRATE_TAG ist nicht gesetzt; einen Standardwert gibt es bewusst nicht. Lösung: Den zur Werkzeugversion des Snapshots passenden Tag setzen.
Kein Platz beim Entpacken des Archivs
Ursache: Der Import entpackt das Archiv temporär in das Systemverzeichnis für temporäre Dateien des Containers, das auf dem Datenträger der Docker-Daten liegt. Lösung: TMPDIR auf ein eingebundenes Volume mit ausreichend Platz zeigen lassen.
Der Import läuft länger als das Wartungsfenster
Ursache: Ein Teilimport der Stammdaten existiert nicht — die Transaktion läuft bis zum Ende oder wird zurückgerollt. Lösung: Fall eskalieren und mit DIGIMONDO ein Vorgehen abstimmen; die Daten der Zielinstanz bleiben bei einem Abbruch unverändert. Umfangreiche Systemprotokolle lassen sich mit --skip=activityLogs zunächst auslassen.
Leistung, Limits & Betrieb
| Aspekt | Wert |
|---|---|
| Tabellenspezifikationen insgesamt | 74 |
| Tabellen im Stammdaten-Snapshot | 70 |
| Tabellen im Zeitreihen-Snapshot | 3 |
| Leseweise beim Export | Serverseitiger Cursor, 1.000 Zeilen je Block; keine Tabelle wird vollständig im Speicher gehalten |
| Schreibweise beim Import | Sammel-INSERT mit 1.000 Zeilen je Anweisung |
| Zeilenweise Verarbeitung | Konten, Zugriffsbereiche, Kontozuordnungen sowie Tickettypen und -prioritäten — hierarchie- bzw. regelbedingt |
| Nebenläufigkeit | Datenbank: keine, eine Verbindung je Lauf; Dateien: bis zu 8 parallel |
| Wiederholversuche | Datenbank: keine; Dateidownloads: bis zu 3 Versuche |
| Zeitlimit im Import | unbegrenzt für eigene Anweisungen, 30 Sekunden Wartezeit auf fremde Sperren |
| Fortsetzbarkeit | Stammdaten-Export auf Tabellenebene, Zeitreihen-Export auf Paketebene; Stammdaten-Import nicht fortsetzbar, Zeitreihen-Import je Teildatei |
| Plattenplatz je Zeile | Richtwert etwa 200 Byte JSONL, zuzüglich gespeicherter Dateien |
| Fortschrittsmeldung | alle 5 Sekunden je laufender Tabelle |
Betriebshinweise:
- Sperrverhalten: Der Import wartet begrenzt auf konkurrierende Sperren. Der Neuaufbau der Hierarchie-Hilfstabellen ist auf die migrierten Datensätze beschränkt und sperrt gleichzeitige Lesezugriffe nicht aus.
- Betrieb der Zielinstanz: Dank des ID-Versatzes darf die Zielinstanz zwischen den Migrationsschritten genutzt werden, ohne dass neu angelegte Objekte mit später migrierten IDs kollidieren.
- Kein Probelauf: Zum Erproben ist der Export gegen eine Kopie der Quelldatenbank auszuführen.
- Kein Teilimport der Stammdaten: Ein Stammdaten-Import ist entweder vollständig bestätigt oder vollständig zurückgerollt. Das Wartungsfenster ist entsprechend zu bemessen.
- Keine Vorabprüfung des Plattenplatzes: Ein volles Dateisystem während des Exports oder beim Entpacken äußert sich lediglich als allgemeiner Abbruch beim Schreiben.
- Abschätzung der Zeitreihen: Die Planungszeile des Zeitreihen-Exports nennt die geplante Zeilenzahl und eignet sich zur Abschätzung von Platzbedarf und Laufzeit.
Zur Abschätzung der Laufzeit liegen ausschließlich Messwerte aus einem lokalen Testdatensatz vor — bei rund 415.000 Zeilen und 81 MB etwa 6 Sekunden für Export samt Prüfung und etwa 15 Sekunden für den Import. Diese Werte sind nicht auf Produktionsdatenbanken übertragbar und nicht zur Planung eines Wartungsfensters geeignet. Die Laufzeit ist stattdessen an einer Kopie der Quelldatenbank zu messen.