Files
mariadb-optimization/README.md
2025-12-12 17:55:35 +01:00

392 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# JTL-Shop MariaDB Optimization
Automatisierte Index-Optimierung, FULLTEXT-Suche und MariaDB-Konfiguration für JTL-Shop-Datenbanken auf Multi-Shop-Servern.
## 🎯 Problemstellung
Bei der Analyse eines Plesk-Servers mit **50 JTL-Shops** wurden massive Performance-Probleme identifiziert:
- **24,4% aller Queries** waren Slow Queries (>1 Sekunde)
- **~250 Slow Queries pro Sekunde**
- Hauptursache: Fehlende Indizes und deaktivierte FULLTEXT-Suche
## 📊 Analyse-Ergebnisse
### Systemübersicht (Beispiel-Server)
| Ressource | Wert |
|-----------|------|
| RAM | 98 GB |
| CPU | 14 Kerne |
| Disk | SSD (NVMe) |
| MariaDB | 10.11.13 |
| DB Größe | ~20 GB |
| Shops | 28 Datenbanken |
| Tabellen | ~14.350 |
### Top Slow Queries nach Ursache
| Rang | Problem | Anteil | Ursache |
|------|---------|--------|---------|
| #1 | Suchcache INSERT | **68,6%** | FULLTEXT deaktiviert, LIKE '%...%' |
| #2 | tbesucherarchiv | 9,3% | INSTR() verhindert Index-Nutzung |
| #3-6 | Filter-Queries | ~6% | JTL-interne Query-Struktur |
| #14 | tlieferscheinposinfo | 0,5% | Fehlender Index |
| #17 | tkampagnevorgang | 0,4% | Suboptimaler Index |
| #19 | tlieferscheinpos | 0,3% | Fehlender Index |
### Identifizierte fehlende Indizes
| Tabelle | Fehlender Index | Betroffene Shops | Rows Examined |
|---------|-----------------|------------------|---------------|
| `tartikel` | FULLTEXT | 25 von 28 | bis 468.000 |
| `tlieferscheinposinfo` | `kLieferscheinPos` | 11 von 28 | ~280.000 |
| `tlieferscheinpos` | `kLieferschein` | 11 von 28 | ~215.000 |
| `tkampagnevorgang` | `kKampagneDef, kKey` | 28 von 28 | ~50.000 |
| `txsellkauf` | `kXSellArtikel` | 28 von 28 | bis 2.000.000 |
### Hauptproblem: FULLTEXT-Suche
**Befund:** Nur 2 von 28 Shops hatten die FULLTEXT-Suche aktiviert.
```sql
-- JTL mit deaktiviertem FULLTEXT (langsam):
WHERE tartikel.cName LIKE '%suchbegriff%'
-- → Full Table Scan über 468.000 Zeilen!
-- JTL mit aktiviertem FULLTEXT (schnell):
WHERE MATCH(cName, cSuchbegriffe) AGAINST('suchbegriff')
-- → Index-Nutzung, wenige Millisekunden
```
Der FULLTEXT-Index existierte auf den meisten Shops nicht, und selbst wo er existierte, war die JTL-Einstellung `suche_fulltext` auf `N` gesetzt.
## 🔧 Lösung
Das Skript `mariadb-optimization.py` behebt diese Probleme automatisiert:
### Was das Skript macht
| Phase | Aktion | Details |
|-------|--------|---------|
| **1** | Backup | Sichert alle `suche_fulltext` Einstellungen |
| **2** | Standard-Indizes | Erstellt 4 fehlende Index-Typen |
| **3** | FULLTEXT-Index | Erstellt FULLTEXT-Index auf `tartikel` |
| **4** | Einstellung aktivieren | Setzt `suche_fulltext = 'Y'` |
| **5** | MariaDB-Config | Erstellt optimierte Konfiguration & Restart |
### Erstellte Indizes
```sql
-- 1. Lieferschein-Position Info
CREATE INDEX idx_kLieferscheinPos ON tlieferscheinposinfo (kLieferscheinPos);
-- 2. Lieferschein-Position
CREATE INDEX idx_kLieferschein ON tlieferscheinpos (kLieferschein);
-- 3. Kampagne-Vorgang (Composite)
CREATE INDEX idx_kKampagneDef_kKey ON tkampagnevorgang (kKampagneDef, kKey);
-- 4. Cross-Selling
CREATE INDEX idx_kXSellArtikel ON txsellkauf (kXSellArtikel);
-- 5. FULLTEXT für Artikelsuche
CREATE FULLTEXT INDEX idx_tartikel_fulltext ON tartikel
(cName, cSeo, cSuchbegriffe, cArtNr, cKurzBeschreibung, cBeschreibung, cBarcode, cISBN, cHAN);
```
### MariaDB-Konfiguration (Phase 5)
Das Skript erstellt automatisch eine optimierte Konfiguration unter:
```
/etc/mysql/conf.d/ZZ-ztl-final-override.cnf
```
**Dynamische Buffer Pool Berechnung:**
- Ermittelt Gesamtgröße aller Datenbanken
- Berechnet: `DB-Größe × 1.2`, aufgerundet auf ganze GB
- Beispiel: 18.74 GB × 1.2 = **23 GB** Buffer Pool
**Konfigurationsinhalt:**
```ini
[mysqld]
# InnoDB Buffer Pool (dynamisch berechnet)
innodb_buffer_pool_size = XXG
innodb_buffer_pool_instances = 16
innodb_buffer_pool_dump_at_shutdown = 1
innodb_buffer_pool_load_at_startup = 1
innodb_log_file_size = 512M
innodb_log_buffer_size = 64M
innodb_flush_log_at_trx_commit = 2
# InnoDB Performance
innodb_flush_method = O_DIRECT
innodb_file_per_table = 1
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
innodb_read_io_threads = 16
innodb_write_io_threads = 16
# Query Cache deaktiviert (bei MariaDB 10.1.7+ empfohlen)
query_cache_type = 0
query_cache_size = 0
# Table Cache für viele Shops
table_definition_cache = 20000
table_open_cache = 15000
open_files_limit = 30000
# Performance
skip_name_resolve = 1
optimizer_search_depth = 0
key_buffer_size = 64M
# Slow Query Log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 0
# Connections
max_connections = 400
thread_cache_size = 100
wait_timeout = 120
# Buffer für JTL-Queries
join_buffer_size = 4M
sort_buffer_size = 4M
read_buffer_size = 2M
read_rnd_buffer_size = 2M
```
**Wichtige Einstellungen erklärt:**
| Parameter | Wert | Funktion |
|-----------|------|----------|
| `innodb_buffer_pool_dump/load` | 1 | Buffer Pool bleibt nach Restart "warm" |
| `skip_name_resolve` | 1 | Schnellere Verbindungen (kein DNS-Lookup) |
| `optimizer_search_depth` | 0 | Auto-Optimierung für JOINs |
| `key_buffer_size` | 64M | Reduziert (nur für MyISAM, JTL nutzt InnoDB) |
| `query_cache` | OFF | Bei write-heavy Workloads kontraproduktiv |
## 🚀 Installation & Verwendung
### Voraussetzungen
- Python 3.6+
- Root-Zugriff auf den Server
- MariaDB/MySQL Client
### Ausführung
```bash
# Direkt vom Git-Repository ausführen
wget https://git.jtl-hosting.de/thomasciesla/mariadb-optimization/raw/branch/main/mariadb-optimization.py
python3 mariadb-optimization.py
```
Oder:
```bash
# Repository klonen
git clone https://git.jtl-hosting.de/thomasciesla/mariadb-optimization.git
cd mariadb-optimization
python3 mariadb-optimization.py
```
### Empfohlene Ausführungszeit
⚠️ **Zu Zeiten geringer Last ausführen** (nachts/früh morgens)
Der FULLTEXT-Index auf großen Shops (z.B. 468.000 Artikel) kann mehrere Minuten dauern und erhöht kurzzeitig die Server-Last. Am Ende wird MariaDB automatisch neugestartet.
## 📁 Ausgabe-Dateien
Nach der Ausführung werden folgende Dateien erstellt:
| Datei | Zweck |
|-------|-------|
| `/root/jtl_search_backup_TIMESTAMP.json` | Backup aller Sucheinstellungen |
| `/root/jtl_search_rollback_TIMESTAMP.py` | Rollback-Skript für Sucheinstellungen |
| `/root/mariadb_config_backup_TIMESTAMP.cnf` | Backup der vorherigen MariaDB-Config |
| `/root/jtl_index_log_TIMESTAMP.txt` | Vollständiges Ausführungs-Log |
## 🔄 Rollback bei Problemen
Falls nach der Optimierung Probleme auftreten:
```bash
# Rollback der Sucheinstellungen (setzt suche_fulltext auf vorherigen Wert)
python3 /root/jtl_search_rollback_TIMESTAMP.py
# Rollback der MariaDB-Konfiguration
cp /root/mariadb_config_backup_TIMESTAMP.cnf /etc/mysql/conf.d/ZZ-ztl-final-override.cnf
systemctl restart mariadb
```
**Hinweis:** Die erstellten Indizes müssen nicht zurückgerollt werden sie haben keine negative Auswirkung und können bei Bedarf manuell entfernt werden.
## ❓ FAQ
### Ist das Erstellen von Indizes gefährlich?
**Nein.** Ein Index ist eine separate Datenstruktur neben der Tabelle wie ein Stichwortverzeichnis am Ende eines Buches:
- ✅ Ändert keine Daten
- ✅ Ändert keine Spalten
- ✅ Ändert kein Verhalten der Anwendung
- ✅ Kann jederzeit wieder gelöscht werden
- ✅ MariaDB entscheidet selbst, ob es den Index nutzt
### Was ändert sich durch `suche_fulltext = 'Y'`?
Diese JTL-Einstellung ändert, **wie** JTL nach Artikeln sucht:
| Einstellung | Suchmethode | Performance |
|-------------|-------------|-------------|
| `N` | `LIKE '%text%'` | Langsam (Full Table Scan) |
| `Y` | `MATCH() AGAINST()` | Schnell (FULLTEXT Index) |
**Mögliche Unterschiede im Suchverhalten:**
- FULLTEXT sucht nach ganzen Wörtern
- LIKE sucht nach Teilstrings
- Beispiel: Suche nach "gel" findet mit LIKE auch "Nagel", mit FULLTEXT evtl. nicht
### Warum fehlen die Indizes überhaupt?
Typische Gründe:
1. JTL hat sie bei bestimmten Versionen nicht erstellt
2. Updates erstellen keine Indizes nachträglich
3. Die Tabellen waren ursprünglich klein
### Wie wird der Buffer Pool berechnet?
```
Buffer Pool = Gesamtgröße aller Datenbanken × 1.2 (aufgerundet)
```
Beispiel: 18.74 GB × 1.2 = 22.49 GB → **23 GB**
### Warum ist Query Cache deaktiviert?
Bei Multi-Shop-Servern mit vielen Schreiboperationen (Bestellungen, Lagerbestände, etc.) ist der Query Cache kontraproduktiv:
- Jedes INSERT/UPDATE/DELETE invalidiert den Cache
- Lock Contention bei vielen gleichzeitigen Queries
- MySQL 8.0 hat den Query Cache komplett entfernt
### Wie kann ich den Effekt messen?
**Vorher/Nachher Vergleich:**
```bash
# Slow Query Rate prüfen
mysql -e "SHOW GLOBAL STATUS WHERE Variable_name IN ('Questions','Slow_queries');"
# Berechnung: Slow_queries / Questions * 100 = Slow Query Rate (%)
```
## 📈 Erwartete Verbesserungen
| Metrik | Vorher | Nachher (erwartet) |
|--------|--------|-------------------|
| Slow Query Rate | ~24% | <5% |
| Suchcache-Queries | 68,6% der Zeit | <5% |
| Artikelsuche | Sekunden | Millisekunden |
## 🔍 Analyse-Befehle
Die folgenden Befehle wurden für die Analyse verwendet und können zur Überprüfung genutzt werden:
### Slow Query Analyse
```bash
# pt-query-digest installieren (falls nicht vorhanden)
wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb
dpkg -i percona-release_latest.generic_all.deb
apt update && apt install percona-toolkit -y
# Slow Query Report erstellen
pt-query-digest --limit=50 /var/log/mysql/slow.log > /tmp/slow_report.txt
```
### Index-Status prüfen
```bash
# Shops ohne FULLTEXT-Index
mysql -N -B -e "SELECT t.table_schema, t.table_rows
FROM information_schema.tables t
WHERE t.table_name = 'tartikel'
AND t.table_schema NOT IN (
SELECT DISTINCT table_schema
FROM information_schema.statistics
WHERE table_name = 'tartikel' AND index_type = 'FULLTEXT'
)
ORDER BY t.table_rows DESC;"
# FULLTEXT-Einstellung pro Shop
mysql -N -B -e "SELECT table_schema FROM information_schema.tables
WHERE table_name = 'teinstellungen'" | while read db; do
val=$(mysql -N -B -e "SELECT cWert FROM \`$db\`.teinstellungen
WHERE cName = 'suche_fulltext' LIMIT 1" 2>/dev/null)
echo "$db: ${val:-NICHT GESETZT}"
done
```
### Datenbankgrößen prüfen
```bash
# Gesamtgröße aller Datenbanken
mysql -N -B -e "SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 2)
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys');"
# Größe pro Datenbank
mysql -e "SELECT table_schema AS 'Datenbank',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Größe (MB)'
FROM information_schema.tables
WHERE table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
GROUP BY table_schema
ORDER BY SUM(data_length + index_length) DESC;"
```
### Buffer Pool & Key Metrics
```bash
mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool%';"
mysql -e "SHOW GLOBAL STATUS WHERE Variable_name IN
('Connections','Max_used_connections','Threads_connected',
'Threads_running','Questions','Slow_queries','Uptime');"
```
## 🐛 Bekannte Einschränkungen
### Nicht durch Indizes lösbar
| Problem | Ursache | Workaround |
|---------|---------|------------|
| tbesucherarchiv (9,3%) | `INSTR(cIP, '...')` in Query | JTL-interner Code, nicht änderbar |
| Komplexe Filter-Queries | JTL Query-Struktur | Bereits optimale Indizes vorhanden |
### FULLTEXT-Einschränkungen
- Minimum Wortlänge: 3-4 Zeichen (konfigurierbar in MariaDB)
- Stoppwörter werden ignoriert
- Keine Wildcard-Suche am Wortanfang
## 📜 Lizenz
MIT License - Siehe [LICENSE](LICENSE)
## 👤 Autor
Thomas Ciesla - JTL-Hosting
## 🔗 Links
- [Repository](https://git.jtl-hosting.de/thomasciesla/mariadb-optimization)
- [Skript (Direktlink)](https://git.jtl-hosting.de/thomasciesla/mariadb-optimization/raw/branch/main/mariadb-optimization.py)
- [JTL-Shop Dokumentation](https://guide.jtl-software.de/jtl-shop/)
- [MariaDB Index-Dokumentation](https://mariadb.com/kb/en/optimization-and-indexes/)
- [Percona Toolkit](https://www.percona.com/software/database-tools/percona-toolkit)