schlanke OLTP-Tabellen fuer grosse Magento-Shops
Ohne konsequente Tabellen-Archivierung sammeln sich in quote und sales_order Millionen verwaister Warenkorb-Zeilen und Jahre alter Bestellhistorie an, die jede Abfrage verlangsamen und Backups aufblaehen. Eine durchdachte Archivierungsstrategie trennt aktive Transaktionsdaten von historischen Datenbestaenden, ohne referenzielle Integritaet oder Compliance-Anforderungen zu gefaehrden.
Inhaltsverzeichnis
- 1. Warum quote und sales_order ungebremst wachsen
- 2. Abandoned-Cart-Bereinigung: quote sicher loeschen
- 3. bin/magento cron:run und die eingebaute Quote-Cleanup-Funktion
- 4. sales_order Archivierung: Kalt-Daten sicher auslagern
- 5. Partitionierung als Alternative zur Tabellen-Archivierung
- 6. Fremdschluessel und referenzielle Integritaet beim Loeschen
- 7. Batch-Deletes ohne Replikationslag und Lock-Eskalation
- 8. Backup- und Compliance-Anforderungen bei Bestelldaten
- 9. Monitoring: Tabellengroesse, Fragmentierung, OPTIMIZE TABLE
- 10. Zusammenfassung
- 11. FAQ
1. Warum quote und sales_order ungebremst wachsen
Jeder Besucher, der einen Artikel in den Warenkorb legt, ohne die Bestellung abzuschliessen, hinterlaesst eine Zeile in quote und zugehoerige Zeilen in quote_item und quote_address. Ohne aktive Tabellen-Archivierung bleiben diese verwaisten Datensaetze fuer immer in der Datenbank, weil Magento selbst keinen aggressiven automatischen Loeschprozess mitbringt. Bei einem Shop mit hoher Abbruchrate im Checkout summiert sich das schnell auf mehrere Millionen ungenutzter Zeilen pro Jahr.
Parallel dazu waechst sales_order mit jeder abgeschlossenen Bestellung unaufhoerlich, und anders als bei quote ist hier ein Loeschen aus rechtlichen Aufbewahrungspflichten meist nicht ohne Weiteres moeglich. Genau das macht Tabellen-Archivierung im Sales-Kontext zu einer anderen Aufgabe als im Quote-Kontext: Wo Warenkoerbe geloescht werden duerfen, muessen Bestelldaten typischerweise nur aus der aktiven OLTP-Tabelle in ein Archiv verschoben werden, ohne dass die Information selbst verloren geht.
Der Effekt auf die Performance ist real messbar: Eine sales_order-Tabelle mit fuenfzig Millionen Zeilen, von denen nur zwei Prozent aus den letzten neunzig Tagen stammen, zwingt jeden Index-Scan, deutlich mehr Datenseiten zu lesen als noetig. Der InnoDB Buffer Pool kann historische, selten gelesene Daten nicht effizient cachen, wenn diese physisch zwischen aktiven Zeilen verteilt liegen. Eine gezielte Tabellen-Archivierung reduziert genau diesen Effekt, indem sie die aktive Arbeitsmenge klein und cache-freundlich haelt.
2. Abandoned-Cart-Bereinigung: quote sicher loeschen
Bevor eine Quote-Zeile geloescht wird, muss klar definiert sein, was als "abgebrochen" gilt. Ein gaengiges Kriterium: Quotes ohne zugehoerige Bestellung, deren updated_at aelter als neunzig Tage ist und die nicht zu einem eingeloggten Kunden mit aktivem Merklisten-Bezug gehoeren. Diese Kriterien sollten projektspezifisch abgestimmt werden, bevor die erste Tabellen-Archivierung produktiv laeuft, weil ein zu aggressives Loeschen aktive Warenkoerbe wiederkehrender Kunden vernichten kann.
-- Kandidaten fuer die Abandoned-Cart-Bereinigung identifizieren
SELECT q.entity_id, q.customer_email, q.updated_at, q.items_count
FROM quote q
LEFT JOIN sales_order so ON so.quote_id = q.entity_id
WHERE so.entity_id IS NULL
AND q.is_active = 1
AND q.updated_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
AND q.customer_is_guest = 1
LIMIT 5000;
-- Abhaengige quote_item und quote_address Zeilen vor dem Hauptloeschen entfernen
DELETE qi FROM quote_item qi
INNER JOIN quote q ON q.entity_id = qi.quote_id
LEFT JOIN sales_order so ON so.quote_id = q.entity_id
WHERE so.entity_id IS NULL
AND q.updated_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
AND q.customer_is_guest = 1
LIMIT 5000;
Ein haeufiger Fehler bei der ersten Tabellen-Archivierung fuer Quotes: Man vergisst, dass quote_item_option und quote_payment ebenfalls Fremdschluessel auf die quote-Tabelle halten. Wer nur die Haupttabelle bereinigt, hinterlaesst verwaiste Zeilen in den Nebentabellen, die spaeter selbst wieder zum Wachstumsproblem werden. Eine vollstaendige Bereinigung muss alle abhaengigen Tabellen in der richtigen Reihenfolge durchlaufen, von den Blatttabellen zur Haupttabelle.
3. bin/magento cron:run und die eingebaute Quote-Cleanup-Funktion
Magento bringt mit sales_clean_quotes einen eingebauten Cron-Job mit, der ueber die Konfiguration unter Sales > Sales > Quotes im Backend gesteuert wird und Quotes nach einer definierbaren Anzahl an Tagen automatisch loescht. In der Praxis reicht dieser Standardmechanismus fuer kleine bis mittlere Shops oft aus, stoesst aber bei sehr grossen Datenmengen an Grenzen, weil der Cron-Job ohne explizites Batching arbeitet und bei mehreren Millionen zu loeschenden Zeilen lange Transaktionen erzeugt.
Fuer eine robuste Tabellen-Archivierung in grossen Shops empfiehlt sich, den Standard-Cron-Job zu deaktivieren und stattdessen ein eigenes, batch-basiertes Cleanup-Skript zu implementieren, das ueber bin/magento cron:run --group="clean_quote" hinaus explizite Kontrolle ueber Chunk-Groesse und Pausen zwischen den Batches erlaubt. Das verhindert, dass ein einzelner Cron-Lauf die Datenbank fuer mehrere Minuten mit einer einzigen riesigen Transaktion blockiert.
# Konfigurierbare Aufbewahrungsdauer fuer den eingebauten Quote-Cleanup pruefen
bin/magento config:show sales/orders/delete_quote_after
# Standard-Cron-Gruppe fuer Quote-Cleanup isoliert testen
bin/magento cron:run --group="clean_quote"
# Eigenes batch-basiertes Cleanup-Skript anstelle des Standardjobs einplanen
# (Crontab-Eintrag, ruft ein PHP-Skript mit expliziter Chunk-Steuerung auf)
*/30 * * * * /usr/bin/php /var/www/magento/bin/quote-cleanup.php --chunk-size=1000 --sleep=2
4. sales_order Archivierung: Kalt-Daten sicher auslagern
Anders als bei Quotes ist bei sales_order das Ziel selten das endgueltige Loeschen, sondern das kontrollierte Verschieben in eine separate Archivtabelle oder Archivdatenbank. Der etablierte Ansatz: Eine Tabelle sales_order_archive mit identischer Struktur wird angelegt, Bestellungen aelter als beispielsweise drei Jahre werden batch-weise dorthin kopiert und erst nach erfolgreicher Verifikation aus der aktiven Tabelle entfernt.
-- Archivtabelle mit identischer Struktur anlegen
CREATE TABLE sales_order_archive LIKE sales_order;
CREATE TABLE sales_order_item_archive LIKE sales_order_item;
-- Batch-weises Kopieren alter Bestellungen ins Archiv (Beispiel-Batch)
INSERT INTO sales_order_archive
SELECT * FROM sales_order
WHERE created_at < DATE_SUB(NOW(), INTERVAL 3 YEAR)
AND status IN ('complete', 'closed', 'canceled')
ORDER BY entity_id
LIMIT 2000;
-- Verifikation: Zeilenzahl im Archiv gegen Quelle pruefen, bevor geloescht wird
SELECT
(SELECT COUNT(*) FROM sales_order WHERE created_at < DATE_SUB(NOW(), INTERVAL 3 YEAR)) AS source_count,
(SELECT COUNT(*) FROM sales_order_archive) AS archive_count;
Fuer den Zugriff auf archivierte Bestellungen im Kundenkonto oder im Backend braucht es entweder eine dedizierte Read-Only-Ansicht auf beide Tabellen per UNION, oder einen expliziten Wechsel des Reporting-Zeitraums, der Nutzer informiert, dass aeltere Bestellungen separat abgerufen werden. Diese Tabellen-Archivierung bewahrt die vollstaendige Historie, entlastet aber die produktive sales_order-Tabelle spuerbar, weil Indizes kleiner werden und mehr davon in den Buffer Pool passt.
5. Partitionierung als Alternative zur Tabellen-Archivierung
Statt Daten physisch in eine andere Tabelle zu verschieben, kann sales_order mit MySQL Range-Partitionierung nach created_at in mehrere logische Partitionen aufgeteilt werden, wobei die Tabelle fuer Abfragen weiterhin als Einheit erscheint. Der Vorteil gegenueber klassischer Tabellen-Archivierung: Kein zusaetzlicher ETL-Prozess noetig, Magento selbst muss nicht angepasst werden, weil die Partitionierung fuer die Anwendungsschicht transparent bleibt.
Der Nachteil: Partitionierung allein reduziert weder die Gesamtdatenmenge noch die Backup-Groesse, sie verbessert primaer die Performance von Abfragen, die sich auf eine bestimmte Partition eingrenzen lassen, etwa "alle Bestellungen der letzten dreissig Tage". Fuer Shops, die vor allem Query-Performance auf aktuelle Daten optimieren wollen, ohne alte Daten aus dem direkten Zugriff zu entfernen, ist Partitionierung oft die pragmatischere Ergaenzung zur eigentlichen Tabellen-Archivierung, nicht deren Ersatz.
6. Fremdschluessel und referenzielle Integritaet beim Loeschen
sales_order ist Ziel zahlreicher Fremdschluessel aus Tabellen wie sales_order_item, sales_order_payment, sales_order_address, sales_invoice, sales_shipment und sales_creditmemo. Jede Tabellen-Archivierung muss diese Abhaengigkeitskette vollstaendig abbilden, sonst entstehen entweder verwaiste Zeilen in den Kindtabellen oder Fremdschluesselverletzungen, die den gesamten Loeschprozess abbrechen lassen.
Ein bewaehrtes Muster: Vor jedem produktiven Loesch- oder Verschiebelauf wird zunaechst eine Trockenlauf-Abfrage ausgefuehrt, die alle betroffenen Kindtabellen auflistet und deren Zeilenzahl fuer die zu archivierenden Order-IDs zaehlt. Erst wenn diese Zahlen mit den Erwartungen uebereinstimmen, startet der eigentliche Archivierungslauf. Fuer die Reihenfolge gilt grundsaetzlich: von den am weitesten entfernten Blatttabellen zur Haupttabelle sales_order, niemals umgekehrt.
7. Batch-Deletes ohne Replikationslag und Lock-Eskalation
Ein einzelnes DELETE ohne LIMIT auf mehreren Millionen Zeilen sperrt in InnoDB potenziell sehr viele Zeilen gleichzeitig und erzeugt auf Replikas einen erheblichen Replikationslag, weil die Aenderung dort seriell nachvollzogen werden muss. Die etablierte Loesung fuer jede grossflaechige Tabellen-Archivierung ist ein Batch-Loop mit kleinen Chunks und kurzen Pausen zwischen den Batches, die dem Replikationsprozess Zeit zum Aufholen geben.
-- Batch-Delete-Loop-Muster (als Pseudocode-Kommentar, real in PHP oder Bash gesteuert)
-- Wiederholt ausfuehren, bis affected_rows = 0
DELETE FROM quote
WHERE is_active = 1
AND updated_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
AND entity_id IN (
SELECT entity_id FROM (
SELECT entity_id FROM quote
WHERE is_active = 1
AND updated_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
LIMIT 1000
) AS batch
);
-- Zwischen Batches: Replikationslag pruefen, bevor der naechste Batch startet
SHOW REPLICA STATUS\G
-- Bei Seconds_Behind_Source > 5: Batch-Loop kurz pausieren
Die Chunk-Groesse von tausend Zeilen ist ein bewaehrter Startwert fuer eine sichere Tabellen-Archivierung, sollte aber anhand der beobachteten Lock-Wartezeiten und des Replikationslags feinjustiert werden. Ein zusaetzlicher Sicherheitsmechanismus ist das Pruefen von Threads_running vor jedem Batch: Steigt dieser Wert ueber einen definierten Schwellenwert, sollte der Batch-Prozess automatisch pausieren, aehnlich der Drosselungslogik von Online-Schema-Change-Tools.
8. Backup- und Compliance-Anforderungen bei Bestelldaten
Bestelldaten unterliegen in vielen Laendern gesetzlichen Aufbewahrungspflichten von sechs bis zehn Jahren, was eine physische Loeschung im engeren Sinne meist ausschliesst. Jede Tabellen-Archivierung fuer sales_order muss deshalb sicherstellen, dass archivierte Daten weiterhin vollstaendig und wiederherstellbar bleiben, auch wenn sie aus der aktiven OLTP-Tabelle entfernt wurden.
Ein separates Backup-Regime fuer Archivtabellen ist sinnvoll: Waehrend die aktive sales_order-Tabelle taeglich vollstaendig gesichert wird, reicht fuer die selten geaenderte Archivtabelle ein monatlicher Vollbackup-Zyklus mit inkrementellen Sicherungen dazwischen. Das reduziert die Backup-Fenster und den Speicherbedarf spuerbar, ohne die Wiederherstellbarkeit der historischen Daten zu gefaehrden. Vor jeder produktiven Tabellen-Archivierung sollte zudem ein dokumentierter Restore-Test der Archivdaten erfolgen, um im Ernstfall die Wiederherstellungszeit realistisch einschaetzen zu koennen.
9. Monitoring: Tabellengroesse, Fragmentierung, OPTIMIZE TABLE
Nach jeder groesseren Tabellen-Archivierung bleibt physischer Speicherplatz in Form von Fragmentierung zurueck, weil InnoDB geloeschte Seiten nicht automatisch an das Betriebssystem zurueckgibt. Der freie Platz innerhalb der Tabelle wird zwar fuer neue Zeilen wiederverwendet, die Datei selbst schrumpft aber nicht von allein.
-- Fragmentierung nach einer grossen Tabellen-Archivierung pruefen
SELECT
table_name,
ROUND(data_length / 1024 / 1024, 1) AS data_mb,
ROUND(data_free / 1024 / 1024, 1) AS free_mb,
ROUND(data_free / NULLIF(data_length, 0) * 100, 1) AS fragmentation_pct
FROM information_schema.tables
WHERE table_schema = 'magento_prod'
AND table_name IN ('quote', 'sales_order', 'sales_order_item')
ORDER BY fragmentation_pct DESC;
-- Tabelle nach grosser Archivierung defragmentieren (Online-DDL, aber I/O-intensiv)
OPTIMIZE TABLE sales_order;
OPTIMIZE TABLE baut die Tabelle intern neu auf und gibt dabei freien Speicherplatz an das Betriebssystem zurueck, ist aber selbst eine I/O-intensive Operation, die auf grossen Tabellen wieder wie ein Online-Schema-Change-Tool behandelt werden sollte, am besten ausserhalb der Stosszeiten. Nach jeder groesseren Tabellen-Archivierung gehoert die Fragmentierungspruefung in den regulaeren Wartungsplan, nicht nur als einmalige Reaktion auf spuerbare Performance-Probleme.
| Datenkategorie | Loeschen erlaubt? | Empfohlene Strategie |
|---|---|---|
| Abandoned quote (Gast, >90 Tage) | Ja | Batch-Delete via Cron oder eigenes Skript |
| Aktive quote (eingeloggt) | Nein | Ausschliessen, laengere Aufbewahrung |
| sales_order (> 3 Jahre) | Nur mit Archivierung | In Archivtabelle verschieben |
| sales_order (< gesetzliche Frist) | Nein | Aktive Tabelle oder Partitionierung |
| sales_order_grid (Anzeige-Cache) | Ja, regenerierbar | Synchron zu sales_order archivieren |
10. Zusammenfassung
Eine wirksame Tabellen-Archivierung unterscheidet klar zwischen loeschbaren Daten wie verwaisten Quotes und aufbewahrungspflichtigen Daten wie abgeschlossenen Bestellungen. Abandoned-Cart-Bereinigung kann aggressiv per Batch-Delete erfolgen, sobald klare Kriterien definiert sind. Fuer sales_order ist Verschieben in eine Archivtabelle statt Loeschen die richtige Strategie, ergaenzt durch Partitionierung fuer bessere Query-Performance auf aktuellen Daten.
Technisch entscheidend sind Batch-Groessen, die Lock-Eskalation vermeiden, sorgfaeltiges Handling der Fremdschluessel-Kette in der richtigen Reihenfolge und regelmaessige OPTIMIZE TABLE-Laeufe nach grossen Archivierungsaktionen. Wer diese Bausteine kombiniert, haelt die produktiven OLTP-Tabellen schlank, ohne die referenzielle Integritaet oder gesetzliche Aufbewahrungspflichten zu verletzen.
Quote- und Sales-Tabellen-Archivierung, das Wichtigste auf einen Blick
Quote-Cleanup
Klare Kriterien fuer abandoned carts definieren, alle abhaengigen Tabellen in der richtigen Reihenfolge bereinigen.
sales_order archivieren statt loeschen
Kalte Bestelldaten in Archivtabellen verschieben, gesetzliche Aufbewahrungspflichten bleiben gewahrt.
Batch statt Big-Bang
Kleine Chunks mit Pausen vermeiden Replikationslag und Lock-Eskalation bei jeder Tabellen-Archivierung.
Nach der Archivierung optimieren
OPTIMIZE TABLE gibt Speicherplatz frei und reduziert Fragmentierung nach grossen Loesch- oder Verschiebelaeufen.