Automatisiertes Datenbank-Cleaning via WP-CLI und Server-Cronjob

Inhalt
Architektur-Überblick: Datenbank-Fragmentierung in WordPress
Mit wachsender Lebensdauer einer WordPress-v7.0.2-Installation unterliegt die zugrundeliegende MySQL- oder MariaDB-Datenbank einer erheblichen Fragmentierung und Datenblähung. Jedes redaktionelle Speichern erzeugt einen neuen Eintrag in der Tabelle wp_posts als Beitragsrevision. Gleichzeitig schreiben Plugins und Themes regelmäßig temporäre Cache-Daten als sogenannte Transients in die Tabelle wp_options. Nach Ablauf der Gültigkeit löscht WordPress diese Transients standardmäßig träge während regulärer HTTP-Anfragen, wodurch oft Tausende veralteter Zeilen verbleiben.
Darüber hinaus hinterlässt das Löschen von Artikeln oder Custom Post Types ohne explizite Bereinigungsroutinen verwaiste Metadaten in der Tabelle wp_postmeta. Diese unkontrollierte Ansammlung vergrößert die Datenbank-Indizes, verlangsamt die Ausführungszeit komplexer SELECT-Abfragen und erhöht den Arbeitsspeicherbedarf des Datenbankservers. Eine automatisierte Bereinigung über WP-CLI und Server-Cronjobs umgeht den Webserver-Stack vollständig und sorgt für eine hochperformante, blockierungsfreie Wartung ohne PHP-Laufzeitbegrenzungen.

Schritt-für-Schritt-Implementierungsanleitung
Schritt 1: Analyse der Datenbank-Größe und CLI-Audit
Vor der Ausführung von Löschroutinen sollte die aktuelle Größe und Struktur der Datenbank über eine SSH-Verbindung mittels WP-CLI analysiert werden. Der folgende Befehl listet alle Tabellen absteigend nach ihrer Speichergröße in Megabyte auf:
wp db size --tables --human-readable
Um die genaue Anzahl der aktuell in der Options-Tabelle verbliebenen, abgelaufenen Transients zu ermitteln, kann eine erste Diagnoseabfrage ausgeführt werden:
wp db query "SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '_transient_timeout_%' AND option_value < UNIX_TIMESTAMP();"
Schritt 2: Entwicklung des automatisierten Wartungs-Skripts
Um veraltete Daten sicher zu eliminieren, ohne auf Ressourcen verbrauchende Drittanbieter-Plugins angewiesen zu sein, eignet sich ein dediziertes Shell-Skript auf Basis von WP-CLI und direkten SQL-Befehlen. Das Skript adressiert vier Hauptursachen für Datenbank-Bloat:
- Abgelaufene Transients: Entfernt alle ungültigen Cache-Einträge aus
wp_options. - Veraltete Beitragsrevisionen: Bereinigt historische Revisions-Objekte aus
wp_posts. - Verwaiste Postmeta-Einträge: Führt eine relationale
DELETE-Abfrage aus, um Metadaten zu löschen, deren übergeordneter Post-ID-Eintrag nicht mehr existiert. - Verwaiste Taxonomie-Verknüpfungen: Entfernt Zuordnungen in
wp_term_relationships, deren zugehöriger Beitrag nicht mehr existiert. Mit Polylang oder mit Taxonomien für andere Objekttypen löscht die Abfrage auch Zuordnungen, die gar keinem Beitrag gelten, etwa die Sprachzuordnungen der Rubriken und Schlagwörter; Näheres unter „Fragen und Antworten“.
Das folgende produktionsreife Skript kann auf dem Server unter /opt/scripts/wp-db-cleanup.sh abgelegt werden:
#!/usr/bin/env bash
# ============================================================================
# Hochperformantes WordPress-Datenbank-Bereinigungsskript
# Zielsystem: WordPress v7.0.2 via WP-CLI
# ============================================================================
WP_PATH="/var/www/lukaswojcik.com/htdocs"
LOG_FILE="/var/log/wp_db_cleanup.log"
echo "[$(date +'%Y-%m-%d %H:%M:%S')] Wartungszyklus gestartet..." >> "$LOG_FILE"
# 1. Alle abgelaufenen Transients löschen
wp transient delete --expired --path="$WP_PATH" >> "$LOG_FILE" 2>&1
# 2. Alle Beitragsrevisionen löschen (optional: Limitierung auf die letzten 3)
REVISIONS=$(wp post list --post_type=revision --format=ids --path="$WP_PATH")
if [ -n "$REVISIONS" ]; then
wp post delete $REVISIONS --force --path="$WP_PATH" >> "$LOG_FILE" 2>&1
fi
# 3. Verwaiste Postmeta-Einträge via direkte relationale SQL-Abfrage löschen
wp db query "DELETE pm FROM wp_postmeta pm LEFT JOIN wp_posts wp ON wp.ID = pm.post_id WHERE wp.ID IS NULL;" --path="$WP_PATH" >> "$LOG_FILE" 2>&1
# 4. Verwaiste Taxonomie-Verknüpfungen entfernen
# Achtung: löscht mit Polylang auch Sprachzuordnungen von Termen (object_id = Term-ID)
wp db query "DELETE tr FROM wp_term_relationships tr LEFT JOIN wp_posts wp ON wp.ID = tr.object_id WHERE wp.ID IS NULL;" --path="$WP_PATH" >> "$LOG_FILE" 2>&1
# 5. MySQL/MariaDB-Speicher-Engines defragmentieren und optimieren
wp db optimize --path="$WP_PATH" >> "$LOG_FILE" 2>&1
echo "[$(date +'%Y-%m-%d %H:%M:%S')] Wartungszyklus erfolgreich beendet." >> "$LOG_FILE"
Schritt 3: Zuweisung strikter Datei-Berechtigungen
Um unbefugte Ausführungen zu verhindern, müssen dem Shell-Skript restriktive Unix-Dateirechte zugewiesen werden. Die Ausführung sollte ausschließlich dem Administrations-Benutzer oder dem Systemkonto des Webservers gestattet sein:
chmod 700 /opt/scripts/wp-db-cleanup.sh
chown www-data:www-data /opt/scripts/wp-db-cleanup.sh
Schritt 4: Automatisierung mittels Server-Cronjob
Im Gegensatz zum standardmäßigen WP-Cron, der auf eingehenden HTTP-Verkehr angewiesen ist und auf Seiten mit geringem Traffic verzögert auslösen kann, garantiert ein nativer Linux-Cronjob die Ausführung zu präzisen Zeitpunkten. Um die Bereinigung jeden Sonntag um 03:00 Uhr morgens ausführen zu lassen, wird die Crontab-Konfiguration bearbeitet:
crontab -e -u www-data
Folgende Zeitplan-Zeile wird am Ende der Crontab-Datei hinzugefügt:
0 3 * * 0 /bin/bash /opt/scripts/wp-db-cleanup.sh >/dev/null 2>&1
Schritt 5: Qualitätssicherung und Verifikation
Nach einer manuellen Testausführung des Skripts sollte die erfolgreiche Bereinigung über Terminal-Befehle verifiziert werden:
- Prüfung der Logfiles: Kontrolle der Datei `/var/log/wp_db_cleanup.log` auf Abwesenheit von Syntax-Fehlern oder Datenbank-Locks.
- Validierung der Tabellen-Optimierung: Ausführung von `wp db size –human-readable` zur Bestätigung des reduzierten Speicherbedarfs von `wp_postmeta` und `wp_posts`.
- Integritätsprüfung im Frontend: Sicherstellen, dass aktive Artikel, eigene Toolbox-Skripte und Polylang-Sprachbeziehungen fehlerfrei und ohne fehlende Metadaten geladen werden.
Zusammenfassung und resultierender Mehrwert
Was sich damit erreichen lässt: Der vollständige Verzicht auf manuelle Datenbankbereinigungen und ressourcenintensive Optimierungs-Plugins zugunsten einer autonomen, auf Serverebene operierenden Wartungs-Pipeline mittels WP-CLI und Linux-Cronjobs.
Resultierender Mehrwert:
- Dauerhaft schlanke Datenbankstruktur: Relationale Tabellen bleiben defragmentiert und frei von verwaisten Metadaten, wodurch die Gesamtgröße der Datenbank bei aktiven Publikationssystemen um 40–60 % sinkt.
- Beschleunigte SQL-Ausführungsgeschwindigkeit: Bereinigte Indizes reduzieren die Ausführungszeit für dynamische Dashboard-Aufrufe und komplexe JOIN-Abfragen signifikant.
- Null PHP-Ressourcenbelastung: Die Ausführung außerhalb des Webserver-Kontexts verhindert HTTP-Timeouts, Arbeitsspeicherspitzen und Ladezeitverzögerungen für Besucher im Frontend.
Fragen und Antworten
Warum läuft das Skript von Hand fehlerfrei, aus dem Cronjob aber nicht?
Meist wegen einer anderen Umgebung. Cron startet Befehle mit einer knappen Umgebung, und drei Unterschiede sind die häufigsten Ursachen:
- Der Suchpfad. Cron setzt PATH in der Regel nur auf /usr/bin:/bin. Liegt WP-CLI, wie oft, unter /usr/local/bin/wp, findet das Skript den Befehl wp nicht. Abhilfe schafft ein vollständiger Pfad im Skript oder eine PATH-Zeile am Anfang der Crontab.
- Die Schreibrechte der Logdatei. Das Skript läuft als www-data und schreibt nach /var/log/wp_db_cleanup.log, in ein Verzeichnis, das für www-data üblicherweise nicht beschreibbar ist. Scheitert die Umleitung in die Logdatei, führt die Shell den betreffenden Befehl gar nicht erst aus; die Datenbank bleibt also unberührt. Die Datei muss deshalb vorher angelegt und www-data übergeben werden, oder das Log liegt an einem Ort, an dem www-data schreiben darf.
- Die verschluckte Ausgabe. Die Crontab-Zeile leitet alles nach /dev/null. Die Fehlermeldungen der gescheiterten Umleitungen verschwinden dort, und der Lauf wirkt still erfolgreich.
Ein Probelauf unter denselben Bedingungen zeigt diese Fehler vorab, etwa mit sudo -u www-data env -i PATH=/usr/bin:/bin /bin/bash /opt/scripts/wp-db-cleanup.sh. Läuft das Skript so durch, sind die häufigsten Ursachen ausgeschlossen.
Ist die Abfrage für verwaiste Taxonomie-Verknüpfungen auf jeder Installation sicher?
Nein. Die Tabelle wp_term_relationships verknüpft Terme nicht nur mit Beiträgen. Eine Taxonomie kann für andere Objekttypen registriert sein, etwa für Benutzer oder für die alten Links aus wp_links; dann steht in object_id keine Beitrags-ID. Mehrsprachigkeits-Plugins wie Polylang ordnen außerdem Rubriken und Schlagwörter über diese Tabelle ihrer Sprache und ihren Übersetzungen zu, mit der Term-ID als object_id.
Die Abfrage aus dem Skript löscht jede Zeile, deren object_id in wp_posts fehlt, und trifft damit genau diese Zuordnungen. Tückisch ist, dass sie nicht alle trifft: Eine Term-ID, die zufällig auch als Beitrags-ID existiert, bleibt stehen. Das Ergebnis sind vereinzelte Rubriken ohne Sprache oder ohne Übersetzung, verstreut und schwer zu erklären.
Sicherer ist eine Einschränkung auf die Taxonomien, die tatsächlich an Beiträgen hängen, über einen JOIN auf wp_term_taxonomy und eine Liste wie category und post_tag. Vor jedem Löschlauf ist außerdem eine Sicherung nötig, etwa mit wp db export, und ein Probelauf mit SELECT COUNT(*) statt DELETE zeigt, wie viele Zeilen betroffen wären.
Was bewirkt wp db optimize auf InnoDB-Tabellen?
InnoDB kennt kein eigenes Optimieren. MySQL und MariaDB bauen die Tabelle stattdessen neu auf und aktualisieren ihre Statistiken; die Meldung dazu lautet „Table does not support optimize, doing recreate + analyze instead“. Für den Neuaufbau braucht der Server vorübergehend etwa so viel freien Plattenplatz, wie die Tabelle belegt, und große Tabellen brauchen entsprechend lange.
Kleiner wird die Datei nur, wenn jede Tabelle eine eigene Datei hat (innodb_file_per_table, in aktuellen Versionen Standard). Liegen die Tabellen im gemeinsamen Systemtablespace, bleibt dessen Datei gleich groß. Nach einer großen Löschaktion lohnt der Neuaufbau; als wöchentliche Routine bringt er wenig, weil InnoDB frei gewordenen Platz innerhalb der Tabelle ohnehin wiederverwendet.
Lässt sich die Zahl der Revisionen begrenzen, statt sie jede Woche vollständig zu löschen?
Ja, mit der Konstante WP_POST_REVISIONS in der wp-config.php, etwa define( 'WP_POST_REVISIONS', 3 );. WordPress entfernt dann beim nächsten Speichern eines Beitrags dessen älteste Revisionen über die Grenze hinaus. Die Versionsgeschichte bleibt so in begrenztem Umfang erhalten, statt jede Woche vollständig gelöscht zu werden.