influx-reshape: Tags und Felder in InfluxDB umbenennen und umtypisieren

Ein Kommandozeilenwerkzeug, das Tags, Felder und Measurements in einem laufenden InfluxDB 1.x oder 2.x umbenennt, entfernt und umtypisiert und nach jedem Schritt die Werte zählt.

Das Problem

InfluxQL kennt kein ALTER. Tags, Felder und Measurements lassen sich nicht umbenennen, ein einzelnes Feld lässt sich nicht löschen, und ein Feld, dessen Typ zwischen Shards wechselt (der Fehler field type conflict), lässt sich nicht reparieren. Die Wünsche gehören zu den ältesten offenen Issues von InfluxDB: #4157 "RENAME TAG" hat 271 Reaktionen und ist seit 2015 offen, #6150 "Drop field" hat 204, #4155 "RENAME MEASUREMENT" hat 119.

Der Umweg in diesen Threads ist SELECT * INTO neu FROM alt GROUP BY * und danach das alte Measurement zu löschen. Nutzer berichten dort, dass ohne GROUP BY * aus Tags Felder werden. Beim Testen fiel mir ein zweites Problem auf: Hat ein Feld in einem Shard einen Float und in einem anderen einen String, liefert SELECT * nur einen der Typen und lässt die übrigen Werte ohne Fehlermeldung weg. Das einzige in den Threads verlinkte Werkzeug, infix, schreibt TSM-Dateien bei gestopptem Server um und kann nur 1.x.

Wie es arbeitet

influx-reshape ist ein Go-Programm ohne Abhängigkeiten außerhalb der Standardbibliothek. Mit InfluxDB 1.x spricht es direkt, mit 2.x über die v1-Kompatibilitäts-API.

plan liest Schema, Zeitbereich und die Zahl der Werte pro Feld, prüft die gewünschten Änderungen und zeigt die Schritte. Mit --scan läuft jeder Punkt durch die Änderungen, ohne dass etwas geschrieben wird. So fallen fehlschlagende Casts und Punkte auf, die nach dem Entfernen eines Tags zusammenfallen würden.

apply kopiert das Measurement mit den Änderungen in ein Zwischen-Measurement, löscht das Original, kopiert das Ergebnis zurück und löscht das Zwischen-Measurement. Gelesen wird in Zeitfenstern mit GROUP BY *, damit Tags Tags bleiben, und mit einer expliziten Liste "feld"::typ für jeden Typ jedes Feldes; erst damit sind Felder mit Typkonflikt vollständig lesbar. Nach jedem Fenster zählt das Werkzeug die Werte pro Feld im Ziel und vergleicht sie mit dem Geschriebenen. Der Fortschritt wird nach jedem Fenster gespeichert, derselbe Befehl setzt nach einem Fehler fort. --backup schreibt vorher die Originalpunkte als gzip-Line-Protocol. Neben Umbenennen, Entfernen und Casts kann es einen Tag in ein String-Feld und ein Feld in einen Tag umwandeln, zwei weitere Schemafehler, die InfluxQL nicht rückgängig machen kann.

Vor dem ersten Schreiben laufen Prüfungen: Umbenennungen dürfen nicht auf bleibende Schlüssel treffen, ein Feld mit Typkonflikt braucht einen Cast, Casts sind streng (1.5 wird kein Integer), und unter 1.x darf das Measurement in keiner anderen Retention Policy liegen, weil DROP MEASUREMENT es in allen löscht.

Beispiel

Ein Measurement, in das ein zweites Skript ab Februar temperature als Integer geschrieben hat, auf InfluxDB 1.11.8:

$ influx-reshape apply --db home -m sensors --rename-tag room=location \
    --drop-field battery --cast temperature=float --backup sensors.lp.gz --yes
step 1/5: back up "sensors" to sensors.lp.gz
  2544 points written to sensors.lp.gz
step 2/5: copy "sensors" -> "sensors__reshape_tmp" with the changes, verify counts
  2544 points read, 2544 written, 2544 values verified
step 3/5: drop measurement "sensors"
step 4/5: copy "sensors__reshape_tmp" -> "sensors", verify counts
  2544 points read, 2544 written, 2544 values verified
step 5/5: drop measurement "sensors__reshape_tmp"
done

Danach zeigt SHOW FIELD KEYS nur noch temperature float, und der Tag heißt location.

Tests

Unit-Tests decken Escaping im Line Protocol, Integer über 2^53, Casts und die Regelprüfungen ab. Die End-to-End-Tests laufen gegen echte Server: lokal gegen 1.11.8 und 2.7.12, im CI gegen 1.8.10, 1.11.8, 2.7.12 und 2.9.1. Sie prüfen Änderungen am Ort über mehrere Fenster mit escapten Schlüsseln, einen Float/Integer/String-Konflikt über Shards (samt Nachweis, dass SELECT * Werte auslässt), Umbenennen mit Backup und Restore, Kollisionen, Fortsetzen nach einem fehlgeschlagenen Fenster, die Ablehnungen und 24000 Punkte in 40 Serien.

Die echten Server fanden zwei Fehler, die die Unit-Tests nicht zeigten. Der Zeitbereich kam aus SELECT * ... LIMIT 1 und verfehlte den letzten Punkt, wenn dieser den anderen Typ eines Konfliktfeldes hatte. Und das Backup schrieb einen ganzzahligen Float eines Konfliktfeldes als Integer, weil InfluxQL zwischen Float und Integer umrechnet und nicht verrät, welcher Typ gespeichert war. Solche Felder schreibt das Backup jetzt als Float.

Grenzen

Schreibende Clients sollten während des Laufs gestoppt oder auf das neue Schema umgestellt sein; Punkte, die zwischen Kopie und Löschen im alten Measurement landen, gehen verloren. Änderungen am Ort kopieren die Daten zweimal über die HTTP-API, gemessen habe ich das an Produktionsgrößen nicht. Pro Lauf ein Measurement, keine Teilumbauten mit WHERE, kein InfluxDB 3.

Code und die vollständige Testliste: github.com/Jwrede/influx-reshape.