Automatyczne czyszczenie bazy danych za pomocą WP-CLI i zadań Crona

Spis treści
Przegląd architektury: Fragmentacja bazy danych w WordPressie
Wraz z rozwojem i czasem eksploatacji instalacji WordPress v7.0.2, jej baza danych MySQL lub MariaDB ulega znacznej fragmentacji i nagromadzeniu zbędnych informacji. Każdy zapis redakcyjny generuje nowy wiersz w tabeli wp_posts w postaci rewizji wpisu. Jednocześnie wtyczki oraz motywy regularnie zapisują tymczasowe dane pamięci podręcznej w tabeli wp_options jako transienty. Po wygaśnięciu ich ważności WordPress usuwa je w sposób pasywny podczas zapytań HTTP, co nierzadko pozostawia w bazie tysiące nieaktualnych rekordów.
Ponadto usuwanie wpisów lub niestandardowych typów treści bez dedykowanych procedur czyszczących skutkuje powstawaniem osieroconych metadanych w tabeli wp_postmeta. Z czasem to niekontrolowane nagromadzenie danych zwiększa rozmiary indeksów, wydłuża czas wykonywania złożonych zapytań SELECT oraz podnosi zużycie pamięci RAM serwera bazy danych. Automatyzacja regularnej konserwacji za pomocą WP-CLI i zadań cron na poziomie systemu operacyjnego całkowicie omija stos serwera WWW, zapewniając szybkie czyszczenie bez ryzyka przekroczenia limitów czasu wykonywania skryptów PHP.

Instrukcja wdrożenia krok po kroku
Krok 1: Analiza rozmiaru bazy danych i wstępny audyt w CLI
Przed uruchomieniem procedur usuwających należy sprawdzić aktualny rozmiar i strukturę bazy danych za pośrednictwem terminala SSH przy użyciu WP-CLI. Poniższe polecenie wyświetla zestawienie wszystkich tabel posortowanych według ich rozmiaru w megabajtach:
wp db size --tables --human-readable
W celu dokładnego zliczenia wygasłych transientów zalegających obecnie w tabeli opcji, możliwe jest wykonanie polecenia diagnostycznego:
wp db query "SELECT COUNT(*) FROM wp_options WHERE option_name LIKE '_transient_timeout_%' AND option_value < UNIX_TIMESTAMP();"
Krok 2: Projektowanie automatycznego skryptu konserwacyjnego
W celu bezpiecznego usunięcia martwych danych bez konieczności instalowania obciążających wtyczek firm trzecich, optymalnym rozwiązaniem jest dedykowany skrypt powłoki (shell script) wykorzystujący WP-CLI oraz bezpośrednie zapytania SQL. Skrypt eliminuje cztery główne źródła nadmiarowych danych:
- Wygasłe transienty: Usuwa wszystkie nieaktualne wpisy pamięci podręcznej z tabeli
wp_options. - Stare rewizje wpisów: Czyszczenie historycznych obiektów rewizji z tabeli
wp_posts. - Osierocone metadane: Wykonuje relacyjne zapytanie
DELETEw celu usunięcia metadanych, których nadrzędny wpis (post ID) już nie istnieje. - Osierocone powiązania taksonomii: Usuwa przypisania w tabeli
wp_term_relationships, których powiązany wpis już nie istnieje. Przy Polylang lub taksonomiach zarejestrowanych dla innych typów obiektów zapytanie usuwa także przypisania, które w ogóle nie dotyczą wpisów, na przykład przypisania językowe kategorii i tagów; szczegóły w części „Pytania i odpowiedzi”.
Poniższy gotowy do wdrożenia skrypt należy zapisać na serwerze pod ścieżką /opt/scripts/wp-db-cleanup.sh:
#!/usr/bin/env bash
# ============================================================================
# Wysokowydajny skrypt czyszczenia bazy danych WordPress
# Cel: 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')] Rozpoczęcie cyklu konserwacji..." >> "$LOG_FILE"
# 1. Usunięcie wszystkich wygasłych transientów
wp transient delete --expired --path="$WP_PATH" >> "$LOG_FILE" 2>&1
# 2. Usunięcie wszystkich rewizji wpisów (opcjonalnie: zachowanie 3 najnowszych)
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. Usunięcie osieroconych metadanych przez bezpośrednie zapytanie relacyjne SQL
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. Usunięcie osieroconych powiązań taksonomii
# Uwaga: przy Polylang usuwa także przypisania językowe terminów (object_id = ID terminu)
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. Defragmentacja i optymalizacja silników tabel MySQL/MariaDB
wp db optimize --path="$WP_PATH" >> "$LOG_FILE" 2>&1
echo "[$(date +'%Y-%m-%d %H:%M:%S')] Cykl konserwacji zakończony pomyślnie." >> "$LOG_FILE"
Krok 3: Nadanie rygorystycznych uprawnień do pliku
W celu zabezpieczenia przed nieautoryzowanym uruchomieniem, skryptowi powłoki należy przypisać restrykcyjne uprawnienia w systemie plików Unix. Prawo do egzekucji powinno przysługiwać wyłącznie użytkownikowi administracyjnemu lub kontu serwisowemu serwera WWW:
chmod 700 /opt/scripts/wp-db-cleanup.sh
chown www-data:www-data /opt/scripts/wp-db-cleanup.sh
Krok 4: Automatyzacja za pomocą harmonogramu Cron serwera
W przeciwieństwie do standardowego mechanizmu WP-Cron, który zależy od przychodzącego ruchu HTTP i może opóźniać się na stronach o mniejszej odwiedzalności, natywne zadanie cron w systemie Linux gwarantuje wykonanie operacji w dokładnie wyznaczonym czasie. Aby zdefiniować czyszczenie w każdą niedzielę o godzinie 03:00 w nocy, edytowana jest konfiguracja crontab:
crontab -e -u www-data
Na końcu pliku konfiguracyjnego dopisywana jest następująca linia harmonogramu:
0 3 * * 0 /bin/bash /opt/scripts/wp-db-cleanup.sh >/dev/null 2>&1
Krok 5: Kontrola jakości i weryfikacja
Po ręcznym przetestowaniu działania skryptu, weryfikację poprawności procedury należy przeprowadzić z poziomu terminala:
- Weryfikacja logów wykonania: Sprawdzenie pliku `/var/log/wp_db_cleanup.log` pod kątem braku błędów składniowych i blokad bazy danych.
- Walidacja optymalizacji tabel: Wykonanie polecenia `wp db size –human-readable` w celu potwierdzenia zmniejszonej objętości tabel `wp_postmeta` i `wp_posts`.
- Sprawdzenie integralności frontendu: Upewnienie się, że aktywne artykuły, narzędzia w Toolboxie oraz powiązania językowe w Polylang ładują się poprawnie bez utraty metadanych.
Podsumowanie i mierzalna wartość dodana
Co da się osiągnąć dzięki instrukcji: Całkowite wyeliminowanie konieczności ręcznego czyszczenia bazy danych oraz rezygnację z obciążających wtyczek optymalizacyjnych na rzecz autonomicznego potoku konserwacyjnego opartego na WP-CLI i systemowym harmonogramie cron.
Wynikająca z tego wartość dodana:
- Trwale zoptymalizowany rozmiar bazy: Tabele relacyjne pozostają zdefragmentowane i pozbawione osieroconych metadanych, co redukuje całkowitą objętość bazy danych o 40–60% w aktywnych serwisach publikujących treści.
- Przyspieszenie zapytań SQL: Czyste indeksy przekładają się na znacznie niższe opóźnienia w wykonywaniu zapytań dla zapytań w panelu administracyjnym oraz złożonych operacji JOIN.
- Brak obciążenia dla zasobów PHP: Wykonywanie czyszczenia bezpośrednio w wierszu poleceń eliminuje ryzyko przekroczenia limitów czasu HTTP, skoków zużycia pamięci RAM oraz spowolnień dla użytkowników odwiedzających witrynę.
Pytania i odpowiedzi
Dlaczego skrypt uruchomiony ręcznie działa bez błędów, a z zadania cron nie?
Zwykle z powodu innego środowiska. Cron uruchamia polecenia w okrojonym środowisku, a najczęstszymi przyczynami są trzy różnice:
- Ścieżka wyszukiwania. Cron ustawia PATH zwykle tylko na /usr/bin:/bin. Jeśli WP-CLI leży, jak często bywa, pod /usr/local/bin/wp, skrypt nie znajduje polecenia wp. Pomaga pełna ścieżka w skrypcie albo wiersz PATH na początku crontaba.
- Prawo zapisu do pliku logu. Skrypt działa jako www-data i pisze do /var/log/wp_db_cleanup.log, czyli do katalogu, w którym www-data zwykle nie ma prawa zapisu. Gdy przekierowanie do logu się nie powiedzie, powłoka w ogóle nie wykonuje danego polecenia, więc baza danych pozostaje nietknięta. Plik trzeba zatem wcześniej utworzyć i przekazać użytkownikowi www-data albo trzymać log w miejscu, w którym www-data ma prawo zapisu.
- Połknięte komunikaty. Wiersz w crontabie kieruje wszystko do /dev/null. Znikają tam komunikaty o nieudanych przekierowaniach, a przebieg wygląda na cichy sukces.
Próbny przebieg w tych samych warunkach ujawnia te błędy zawczasu, na przykład poleceniem sudo -u www-data env -i PATH=/usr/bin:/bin /bin/bash /opt/scripts/wp-db-cleanup.sh. Jeśli skrypt przejdzie w ten sposób, najczęstsze przyczyny są wykluczone.
Czy zapytanie usuwające osierocone powiązania taksonomii jest bezpieczne w każdej instalacji?
Nie. Tabela wp_term_relationships łączy terminy nie tylko z wpisami. Taksonomia może być zarejestrowana dla innych typów obiektów, na przykład dla użytkowników albo starych odnośników z wp_links, i wtedy w object_id nie ma identyfikatora wpisu. Wtyczki wielojęzyczne, takie jak Polylang, przypisują ponadto przez tę tabelę kategorie i tagi do ich języka i tłumaczeń, z identyfikatorem terminu jako object_id.
Zapytanie ze skryptu usuwa każdy wiersz, którego object_id brakuje w wp_posts, i trafia tym samym dokładnie w te przypisania. Zdradliwe jest to, że nie trafia we wszystkie: identyfikator terminu, który przypadkiem istnieje też jako identyfikator wpisu, pozostaje. Skutkiem są rozproszone kategorie bez języka albo bez tłumaczenia, trudne do wyjaśnienia.
Bezpieczniej jest ograniczyć zapytanie do taksonomii rzeczywiście przypiętych do wpisów, przez JOIN z wp_term_taxonomy i listę w rodzaju category i post_tag. Każdy przebieg usuwający powinna też poprzedzać kopia zapasowa, na przykład przez wp db export, a próbny przebieg z SELECT COUNT(*) zamiast DELETE pokazuje, ilu wierszy by to dotyczyło.
Co robi wp db optimize z tabelami InnoDB?
InnoDB nie ma własnej optymalizacji. MySQL i MariaDB przebudowują zamiast tego tabelę i aktualizują jej statystyki; komunikat brzmi „Table does not support optimize, doing recreate + analyze instead”. Na przebudowę serwer potrzebuje chwilowo mniej więcej tyle wolnego miejsca na dysku, ile zajmuje tabela, a duże tabele przebudowują się odpowiednio długo.
Plik zmniejsza się tylko wtedy, gdy każda tabela ma własny plik (innodb_file_per_table, w aktualnych wersjach ustawienie domyślne). Jeśli tabele leżą we wspólnej systemowej przestrzeni tabel, jej plik zachowuje rozmiar. Po usunięciu dużej ilości danych przebudowa się opłaca; jako cotygodniowa rutyna daje niewiele, bo InnoDB i tak ponownie wykorzystuje zwolnione miejsce wewnątrz tabeli.
Czy liczbę rewizji da się ograniczyć, zamiast co tydzień usuwać wszystkie?
Tak, stałą WP_POST_REVISIONS w pliku wp-config.php, na przykład define( 'WP_POST_REVISIONS', 3 );. WordPress usuwa wtedy przy następnym zapisie wpisu jego najstarsze rewizje ponad limit. Historia wersji zostaje w ten sposób zachowana, podczas gdy skrypt co tydzień usuwa ją w całości.