Zammad-Fehlersuche: Wenn MySQL-Altlasten die PostgreSQL-Datenbank crashen (und wie man es repariert)

Veröffentlicht am 21.07.2026 22:18 in Linux & Debian, IT-Infrastruktur

Zammad-Fehlersuche: Wenn MySQL-Altlasten die PostgreSQL-Datenbank crashen (und wie man es repariert)

Wer ein Helpdesk-System wie Zammad über Jahre hinweg pflegt, kennt das: Das System wächst, bekommt zahllose Updates und zieht vielleicht sogar von einem Datenbank-System zum nächsten um. In unserem Fall haben wir eine Zammad-Instanz, die ursprünglich unter MySQL lief, vor einiger Zeit nach PostgreSQL migriert und immer fleißig aktualisiert – aktuell auf Version 7.1.1.

Eigentlich lief alles wunderbar, bis unsere Telefonanlagen-Kopplung (CTI) plötzlich streikte. Ein Blick ins Zammad-Log offenbarte diesen unschönen Fehler:

ERROR -- : Error ID Yy6FsVxF: PG::NotNullViolation: FEHLER: NULL-Wert in Spalte »start_at« von Relation »cti_logs« verletzt Not-Null-Constraint

Die Diagnose: Ein Geist aus der MySQL-Vergangenheit

siehe Zammad MySQL Postgres-Migration

Um den Fehler einzugrenzen, habe ich eine komplett frische Zammad 7.1.1 Instanz aufgesetzt. Dort funktionierte die CTI-Integration tadellos! Doch sobald ich den SQL-Dump unseres produktiven Systems in die frische Installation importierte, war auch dieses System kaputt.

Der Verdacht: Das Datenbank-Schema der alten Installation unterscheidet sich von dem einer frischen Installation. Bei der historischen Migration von MySQL zu Postgres und den unzähligen Zammad-Updates wurden offenbar alte, zu strikte Datenbank-Regeln (NOT NULL Constraints) mitgeschleppt, die Zammad heute gar nicht mehr verwendet.

Der Quick-Fix für die CTI-Integration

Um die CTI-Kopplung sofort wieder zum Laufen zu bekommen, reichte es, den falschen Constraint von den betroffenen Spalten zu entfernen. Über die Datenbank-Konsole (sudo -u postgres psql zammad_production) war das schnell erledigt:

ALTER TABLE cti_logs ALTER COLUMN start_at DROP NOT NULL;
ALTER TABLE cti_logs ALTER COLUMN end_at DROP NOT NULL;

Sofort nach dem Ausführen funktionierte die Telefon-Integration wieder. Aber das eigentliche Problem war damit nicht gelöst: Wie viele solcher Altlasten schlummerten noch in der Datenbank und warteten darauf, beim nächsten Zammad-Feature abzustürzen?

Der Profi-Weg: Schemata vergleichen mit "migra"

Ich wollte unsere alte Datenbank zu 100 % an das Schema einer frischen Installation angleichen – ohne dabei unsere wertvollen Ticket-Daten zu verlieren.

Das Tool der Wahl dafür heißt migra, ein Python-Programm, das zwei PostgreSQL-Datenbanken vergleicht und automatisch die passenden ALTER TABLE-Befehle ausspuckt, um die Unterschiede auszubügeln.

Da moderne Linux-Distributionen (z. B. Debian) die systemweite Installation von Python-Paketen blockieren, musste eine virtuelle Umgebung (venv) her.

Stolperstein: Python 3.13 und "pkg_resources"

Beim Einrichten der Umgebung stieß ich auf einen modernen Python-Fallstrick: migra stürzte beim Start mit ModuleNotFoundError: No module named 'pkg_resources' ab. Der Grund? Neuere Python-Versionen liefern in den setuptools dieses veraltete Modul nicht mehr mit.

Die Lösung war, eine ältere Version der Setuptools in der virtuellen Umgebung zu erzwingen:

# Virtuelle Umgebung erstellen und aktivieren
python3 -m venv ~/migra_env
source ~/migra_env/bin/activate

# Kompatible Setuptools und Migra installieren
pip install "setuptools<70.0.0" migra psycopg2-binary

Den chirurgischen Eingriff vorbereiten

Ich hatte nun beide Datenbanken auf demselben Server:

  • zammad (Die frische, saubere Installation)
  • zammad_old (Unser Backup mit den Altlasten)

Der Vergleichsbefehl (ausgeführt als User postgres, um Berechtigungsprobleme zu vermeiden) lautete:

sudo -u postgres ~/migra_env/bin/migra --unsafe postgresql:///zammad_old postgresql:///zammad > korrektur.sql

(Das Flag --unsafe ist nötig, weil Migra sonst abbricht, wenn es alte Tabellen löschen will, die in der neuen Version nicht mehr existieren).

Die generierte Datei korrektur.sql war riesig. Migra wollte auch Spalten löschen (DROP COLUMN), die vielleicht noch historische Plugin-Daten enthielten. Da unser Hauptproblem aber die strikten NOT NULL-Bedingungen waren, nutzte ich einen einfachen Linux-Trick, um nur die sicheren Korrekturen herauszufiltern:

grep -i "drop not null" korrektur.sql > sichere_korrektur.sql

Diese gefilterte, nun sehr übersichtliche Datei konnte ich gefahrlos auf unsere kaputte Datenbank anwenden:

sudo -u postgres psql -d zammad_old -f sichere_korrektur.sql

Der Feinschliff: Berechtigungen und Sequenzen

Nach dem Import verglich ich die Schemata nochmals mit pg_dump --schema-only. Fast perfekt! Zwei kleine Unschönheiten fielen noch auf, die ich manuell glattzog:

1. Falscher Tabellen-Besitzer (Owner)
Weil ich als User postgres agiert hatte, gehörten die geänderten Tabellen plötzlich nicht mehr dem User zammad. Ein Update hätte das System später zerschossen. Die Lösung ist ein genialer Postgres-Einzeiler:

sudo -u postgres psql -d zammad_old -c "REASSIGN OWNED BY postgres TO zammad; ALTER SCHEMA public OWNER TO zammad;"

2. Fehlende Datentypen bei Zählern (Sequences)
Bei einigen Sequenzen fehlte der Zusatz AS integer, der bei ganz frischen Postgres-Installationen Standard ist. Um nicht alle manuell tippen zu müssen, ließ ich mir von Postgres die Fixes generieren:

SELECT 'ALTER SEQUENCE ' || sequence_schema || '.' || sequence_name || ' AS integer;'
FROM information_schema.sequences
WHERE sequence_schema = 'public' AND data_type = 'bigint';

Die ausgegebenen Befehle (z. B. ALTER SEQUENCE public.users_id_seq AS integer;) warf ich wieder in die Konsole – fertig!

Fazit

Nach einem abschließenden Leeren des Zammad-Caches (zammad run rails r "Cache.clear") und einem Service-Neustart lief das System perfekt. Ein Re-Index der Elassic-Search wurde ebenfalls gestartet

sudo -u zammad zammad run rake zammad:searchindex:rebuild[8]

Eine Datenbank über Jahre und Plattformen hinweg zu aktualisieren, hinterlässt fast immer Spuren. Aber mit Werkzeugen wie migra, ein wenig Bash-Magie (grep) und PostgreSQL-Bordmitteln lässt sich selbst das chaotischste gewachsene Schema wieder in einen Zustand versetzen, der von einer "Out-of-the-Box"-Neuinstallation nicht zu unterscheiden ist.

Tipp: Bevor Sie solche weitreichenden Schema-Änderungen durchführen, machen Sie immer einen vollständigen SQL-Dump oder VM-Snapshot!