Нічого так не лякає маркетинг, як головна сторінка з купою зламаних зображень. Нічого так не лякає SRE, як «швидке очищення» в продакшні, яке тихо перетворюється на розподілену генерацію 404.
Ви цілком можете зменшити надмірну медіабібліотеку WordPress, зменшити витрати на зберігання та пришвидшити бекапи — без ламання URL. Але треба ставитися до цього як до продакшн-змiни: спочатку вимірюйте, видаляйте в останню чергу й збережіть запасний шлях для відкату.
Що саме ви очищуєте (і чому ламаються URL)
«Очищення медіа» звучить як видалення купи JPEG. У WordPress це складніше:
- Об’єкти файлової системи: зазвичай під
wp-content/uploads/YYYY/MM/, плюс згенеровані розміри (мініатюри) і іноді варіанти з суфіксом «-scaled». - Рядки в базі даних: вкладення зберігаються як записи в
wp_postsзpost_type='attachment'. - Метадані: метадані вкладень у
wp_postmeta(зокрема_wp_attached_fileта_wp_attachment_metadata) кажуть WordPress, які файли існують і які розміри були згенеровані. - Посилання: пости/сторінки зберігають посилання на зображення в HTML, JSON блоках, шорткодах, налаштуваннях теми, віджетах і іноді в таблицях плагінів.
- CDN/шари відвантаження: ваші «реальні» файли можуть бути на S3/Cloud Storage, а локальні копії — опціональні.
URL ламаються з трьох основних причин:
- Ви видалили файл, на який усе ще посилаються (вміст, конструктор сторінок, налаштування теми, соціальна картка або таблиця плагіна).
- Ви змінили відображення URL (зміна site URL, налаштувань плагіна offload, інший bucket/шлях або перепис шляхів завантажень).
- Ви змінили ім’я файлу (dedupe/перейменування/оптимізація, яка «допоміжно» перезберегла або перемістила зображення).
Очищення медіа, що не ламає URL, — це перш за все проблема цілісності посилань (reference integrity), а не лише видалення.
Операційна істина: WordPress не підтримує повний індекс «яке фото використовується в яких постах». Ви будуєте цю мапу самостійно, або погоджуєтесь на ризик. У продакшні приймайте якнайменше припущень.
Жарт №1: Видаляти медіа на живому WordPress-сайті без плану — як обрізати бонсай бензопилою: технічно інструмент, емоційно — помилка.
Цікаві факти й історичний контекст (коротко, корисно)
- Вкладення — це пости. WordPress зберігає медіа як рядки в
wp_postsз давніх версій, тому ви можете «редагувати» зображення як пост. - Мініатюри змінили гру. Автоматичні проміжні розміри існували давно, але сучасна поведінка для адаптивних зображень (наприклад,
srcset) зробила «одне завантаження → багато файлів» стандартом. - Суфікс «-scaled» — відносно сучасна річ. WordPress почав генерувати «scaled» зображення для дуже великих завантажень, щоб уникнути гігантських оригіналів, які псують макети та споживають пам’ять.
- Обробка EXIF — постійна підступність. Метадані орієнтації можуть змусити картинку виглядати «повернутою неправильно» залежно від того, як її обробили й перегенерували.
- Шлях uploads конфігурований. WordPress може зберігати файли поза папкою uploads за замовчуванням, але багато плагінів усе ще припускають стандартний шлях.
- CDN зробили постійність URL важливою. Як тільки URL з’явився в розсилці кампанії, ці медіа стають напівпублічними API-ендпойнтами.
- Будівники сторінок не завжди зберігають простий HTML. Конструктори часто серіалізують вміст у JSON-подібні бульби; простий grep пропускає такі використання.
- Деякі offload-плагіни вважають bucket джерелом істини. Локальне очищення може бути безпечним або катастрофічним залежно від того, чи плагін «копіює» чи «переміщує» файли.
- WP-CLI став наглядом дорослих. Операційно, WP-CLI — це різниця між відтворюваним обслуговуванням і «я клікав навмання й сподівався».
Непорушні принципи безпечного очищення медіа
1) URL — це контракт
Маркетинг бачить URL зображень як «активи». Інженери мають бачити їх як публічні інтерфейси. Як тільки URL опублікований — на сторінках, в емейлах, PDF чи в соціальних прев’ю — його зміна є злом сумісності.
2) Складіть план видалення, що припускає вашу помилку
Ви пропустите посилання в перший прохід. План має включати:
- Етап, який можна відмінити (карантин/переміщення замість видалення).
- Вікно моніторингу (спостереження за 404, origin-проханнями та рівнем помилок).
- План відкату (повернення файлів, відновлення рядків БД, скасування правил редиректу).
3) Розділяйте «сирота в БД» від «невикористаного в контенті»
Вкладення може бути відсутнє в інтерфейсі Медії (або неочевидним) і все одно використовуватися:
- В налаштуваннях теми (header logo, Open Graph image).
- У віджетах/меню.
- У фоні CSS.
- У таблицях плагінів-конструкторів.
4) Не плутайте «дублікати байтів» з «безпечним дедупом»
Два файли можуть бути ідентичні, але мати різні URL, які обидва використовуються. Дедуп без редиректів — це повільно й невідворотно аварія.
5) Ваша стратегія бекапів — частина стратегії очищення
Якщо ваші бекапи повільні, дорогі або ненадійні, ви будете схильні «просто видалити». Виправте пайплайн бекапів і прийматимете холодніші рішення. Також: тестуйте відновлення. Бекап, який не відновлювали — це просто дороге відчуття безпеки.
6) Підтвердіть, що реально віддає медіа
Це локальний диск? Підключений NFS/EFS? Об’єктне сховище через плагін? CDN, що тягне з origin? Якщо ви не знаєте шлях видачі, не можете спрогнозувати зону ураження.
Одна цитата, яку варто тримати на стікері:
«Надія — не стратегія.» — General Gordon R. Sullivan
Швидкий план діагностики
Коли хтось каже «медіабібліотека величезна і сайт повільний», не поспішайте до скриптів видалення. Спочатку визначте, з яким саме болем ви маєте справу: тиск на зберігання, проблеми з бекапами, повільність адмінки, повільність фронтенду чи навантаження на CDN/origin.
Спочатку: підтвердіть, що симптом реальний і актуальний
- Зберігання: використання файлової системи та виснаження inode.
- Бекапи: час виконання, поведінка інкрементальних, кількість об’єктів.
- Фронтенд: 404 на uploads, сплески пропускної здатності origin, частота промахів кешу.
- Адмінка: повільна сітка медіа/пошук через повільну БД або величезні метадані.
По-друге: знайдіть вузьке місце домену
- Обмежено диском: повільний I/O, занадто багато дрібних файлів, повільна мережева файлова система.
- Обмежено БД: повільні запити в
wp_posts/wp_postmeta, відсутні індекси під ваші запити, автозавантаження, яке гальмує адмінку. - Обмежено мережею: кеш CDN не влучає, origin недоступний, неправильні заголовки кешування, помилка в налаштуваннях offload.
По-третє: оберіть найменш ризиковий важіль
- Якщо проблема в тривалості бекапів, впровадьте інкрементальні бекапи або виключіть кеші перед видаленням медіа.
- Якщо проблема в пропускній здатності фронтенду, налагодьте кешування й розмір зображень перед «очищенням».
- Якщо проблема в тиску на зберігання, спочатку карантинні старі медіа; не видаляйте наосліп.
Практичні завдання (команди + вивід + рішення)
Це завдання рівня продакшн: кожне містить команду, приклад виводу, що означає вивід і яке рішення прийняти. Запускайте їх спочатку на staging-клоні. Потім — у продакшні з вікном змін і планом відкату.
Завдання 1: Перевірка використання диску та inode (тест «ми справді заповнені?»)
cr0x@server:~$ df -h /var/www/html/wp-content/uploads
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 200G 176G 24G 89% /
Значення: 89% використання — незручно, але не аварійно. Якщо це 95%+, ви в зоні «тут бувають інциденти».
Рішення: Якщо >90%, віддавайте пріоритет безпечному карантину + плану розширення; уникайте довготривалих скриптів, що генерують тимчасові файли.
cr0x@server:~$ df -i /var/www/html/wp-content/uploads
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 13107200 8123456 4983744 62% /
Значення: Використання inode в нормі. Якщо inode ~90% при наявності вільних ГБ, ви страждаєте від «занадто багато файлів», а не «занадто багато даних».
Рішення: Якщо проблема в inode — фокусуйтеся на розростанні мініатюр і каталогів кешу, а не лише на великих оригіналах.
Завдання 2: Виміряйте обсяг uploads по роках/місяцях (знайдіть підказку «чому 2019 такий величезний?»)
cr0x@server:~$ du -h --max-depth=2 /var/www/html/wp-content/uploads | sort -h | tail -n 10
4.2G /var/www/html/wp-content/uploads/2022
6.8G /var/www/html/wp-content/uploads/2023
7.1G /var/www/html/wp-content/uploads/2021
9.6G /var/www/html/wp-content/uploads/2020
31G /var/www/html/wp-content/uploads/2019
58G /var/www/html/wp-content/uploads
Значення: Один рік домінує. Це часто співпадає з кампанією, міграцією або плагіном, що згенерував купу розмірів.
Рішення: Сфокусуйте аналіз на аутлаєрі року першочергово. Там ви отримаєте найбільший безпечний ефект.
Завдання 3: Знайдіть найбільші файли (виграш в зберіганні без торкання URL)
cr0x@server:~$ find /var/www/html/wp-content/uploads -type f -printf '%s %p\n' | sort -nr | head -n 5
52428800 /var/www/html/wp-content/uploads/2019/07/booth-video-poster.png
41943040 /var/www/html/wp-content/uploads/2019/08/trade-show-wall.jpg
39845888 /var/www/html/wp-content/uploads/2020/01/hero-background.tif
36700160 /var/www/html/wp-content/uploads/2021/11/webinar-slide-01.png
33554432 /var/www/html/wp-content/uploads/2019/09/product-shot-raw.jpg
Значення: Ймовірно, у вас «веб-ворожі» формати (TIFF, raw JPG, величезні PNG), які ніколи не мали б бути завантажені.
Рішення: Віддавайте перевагу заміні + редиректам (збережіть оригінальний URL) або залишайте оригінали та виправляйте розміри для рендерингу. Не робіть масового видалення.
Завдання 4: Підтвердіть, що WordPress вважає uploads там, де ви думаєте
cr0x@server:~$ wp option get upload_path
Значення: Порожній вивід зазвичай означає «шлях uploads за замовчуванням». Якщо він щось повертає — ваші скрипти очищення мають це врахувати.
Рішення: Якщо встановлено кастомний шлях — аудитуйте плагіни й правила Nginx/Apache, що припускають стандартний uploads.
cr0x@server:~$ wp option get upload_url_path
Значення: Порожній вивід означає, що WordPress будує URL з site URL + uploads path.
Рішення: Якщо параметр встановлено (або плагін offload перезаписує), відображення URL може відрізнятися від файлового розташування.
Завдання 5: Інвентаризація типів вкладень і їх кількості (обсяг робіт)
cr0x@server:~$ wp db query "SELECT post_mime_type, COUNT(*) AS c FROM wp_posts WHERE post_type='attachment' GROUP BY post_mime_type ORDER BY c DESC LIMIT 10;"
+----------------+--------+
| post_mime_type | c |
+----------------+--------+
| image/jpeg | 48210 |
| image/png | 12340 |
| image/webp | 3210 |
| application/pdf| 2890 |
| image/gif | 740 |
+----------------+--------+
Значення: У вас велика бібліотека зображень і чимало PDF. PDF часто вбудовуються в завантаження й емейли; ставтеся до них як до постійних.
Рішення: Встановіть різні політики: зображення можна оптимізувати; PDF рідко варто видаляти, якщо ви не можете довести відсутність використання.
Завдання 6: Виявити вкладення, для яких відсутні файли (БД вказує на неіснуючі файлові об’єкти)
cr0x@server:~$ wp eval '
global $wpdb;
$ids=$wpdb->get_col("SELECT ID FROM {$wpdb->posts} WHERE post_type=\"attachment\" LIMIT 2000");
$missing=0;
foreach ($ids as $id){
$file=get_attached_file($id);
if ($file && !file_exists($file)) { $missing++; }
}
echo "checked=".count($ids)." missing=$missing\n";
'
checked=2000 missing=37
Значення: Деякі вкладення вказують на файли, яких немає на диску (можливо, вони відвантажені, видалені або шлях змінився).
Рішення: Перед видаленням будь-чого іншого усуньте проблеми з відсутніми файлами: вони псують аналіз і можуть вказувати на невідповідність offload.
Завдання 7: Перевірте, чи медіа відвантажуються (щоб не видалити origin випадково)
cr0x@server:~$ wp plugin list --status=active
+---------------------------+----------+--------+---------+
| name | status | update | version |
+---------------------------+----------+--------+---------+
| amazon-s3-and-cloudfront | active | none | 3.2.2 |
| wordpress-seo | active | none | 22.4 |
| woocommerce | active | none | 8.6.1 |
+---------------------------+----------+--------+
Значення: Активний плагін offload. Файлова система може й не бути авторитетним сховищем.
Рішення: Перевірте режим offload (copy vs move). Якщо він «переміщує» в об’єктне сховище, локальні файли можуть уже бути відсутні, і видалення локальних «сиріт» нічого не дасть.
Завдання 8: Знайти посилання на конкретний медіа-URL у контенті (метод spot-check)
cr0x@server:~$ wp db query "SELECT ID, post_title FROM wp_posts WHERE post_type IN ('post','page') AND post_status IN ('publish','draft') AND post_content LIKE '%/wp-content/uploads/2019/07/trade-show-wall.jpg%';"
+-----+--------------------------+
| ID | post_title |
+-----+--------------------------+
| 912 | Summer trade show recap |
+-----+--------------------------
Значення: Принаймні один пост прямо посилається на цей URL. Його видалення викличе помітний розрив.
Рішення: Якщо посилання існує — збережіть файл або замініть його, зберігаючи URL (див. стратегію редиректів нижче).
Завдання 9: Знайти вкладення без прикріпленого батьківського поста (не те ж, що невикористане)
cr0x@server:~$ wp db query "SELECT COUNT(*) AS unattached FROM wp_posts WHERE post_type='attachment' AND post_parent=0;"
+------------+
| unattached |
+------------+
| 39122 |
+------------+
Значення: Багато медіа «не прикріплені». Це нормально для сучасних редакторів і будівників; це не доводить, що вони не використовуються.
Рішення: Не використовуйте post_parent=0 як фільтр для видалення. Використовуйте сканування посилань + журнали доступу.
Завдання 10: Перевірка вибуху мініатюр (скільки файлів на одне вкладення)
cr0x@server:~$ wp eval '
$id=12345;
$meta=wp_get_attachment_metadata($id);
echo "file=".$meta["file"]."\n";
echo "sizes=".count($meta["sizes"])."\n";
'
file=2019/07/trade-show-wall.jpg
sizes=18
Значення: Одне завантаження згенерувало 18 похідних. Помножте на 50k зображень — і ваша кількість inode розповість історію.
Рішення: Якщо розмірів надто багато — зменшіть зареєстровані розміри зображень (тема/плагіни) перед регенерацією чого-небудь.
Завдання 11: Використайте журнали доступу, щоб знайти «гарячі» відсутні медіа (що реально бачать користувачі)
cr0x@server:~$ sudo awk '$9==404 && $7 ~ /\/wp-content\/uploads\// {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -n 10
52 /wp-content/uploads/2019/07/trade-show-wall.jpg
31 /wp-content/uploads/2020/01/hero-background.tif
18 /wp-content/uploads/2018/03/old-logo.png
11 /wp-content/uploads/2019/07/trade-show-wall-1024x768.jpg
9 /wp-content/uploads/2017/12/banner-winter.jpg
Значення: Це URL, які зараз падають у продакшні. Частина з них — оригінали; частина — згенеровані розміри. Це важливо.
Рішення: Виправляйте найгірших порушників першочергово. Якщо 404 стосуються проміжних розмірів, може знадобитися регенерація або редиректи.
Завдання 12: Підтвердіть, чи «відсутній» файл існує на диску (щоб не ганятися за примарами)
cr0x@server:~$ ls -la /var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg
-rw-r--r-- 1 www-data www-data 41943040 Jul 12 2019 /var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg
Значення: Файл існує. Якщо користувачі все ще отримують 404, ймовірно проблема в маршрутизації, правах доступу, невідповідності CDN/origin або правилах offload.
Рішення: Нічого не видаляйте. Діагностуйте шлях видачі (Nginx alias, поведінка CDN, права на файли).
Завдання 13: Перевірте права й власника в uploads (класичний тихий розрив)
cr0x@server:~$ namei -l /var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg
f: /var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-xr-x root root html
drwxr-xr-x www-data www-data wp-content
drwxr-x--- www-data www-data uploads
drwxr-x--- www-data www-data 2019
drwxr-x--- www-data www-data 07
-rw-r----- www-data www-data trade-show-wall.jpg
Значення: Каталоги мають 750, файл — 640. Якщо Nginx працює як www-data, нормально. Якщо як інший користувач — будуть 403/404 залежно від конфігурації.
Рішення: Виправте права перед очищенням. Інакше ви помилково класифікуєте «недоступне» як «невикористане».
Завдання 14: Карантин підозрілих сиріт безпечно (переміщуйте, не видаляйте)
cr0x@server:~$ mkdir -p /var/www/html/wp-content/uploads-quarantine
cr0x@server:~$ rsync -a --remove-source-files /var/www/html/wp-content/uploads/2017/ /var/www/html/wp-content/uploads-quarantine/2017/
cr0x@server:~$ find /var/www/html/wp-content/uploads/2017 -type f | head
Значення: Остання команда нічого не виводить, бо файли були переміщені. Це оборотно: ви можете швидко повернути їх назад.
Рішення: Карантиньте старі медіа по шматках (за роком/місяцем). Моніторьте 404. Якщо немає сплесків — видаляйте карантин пізніше.
Завдання 15: Додайте тимчасовий Nginx-фолбек для карантинованих файлів (мережевий страховий сіт)
cr0x@server:~$ sudo nginx -T | sed -n '1,120p'
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# ...snip...
Значення: Конфіг валідний. Тепер безпечно додати location-блок (у вікні змін), який спробує звичайний шлях, а потім — карантин.
Рішення: Під час міграції/карантину можна віддавати файли з карантину, щоб URL працювали, поки ви підтверджуєте невикористання.
Приклад логіки location (концептуально; реалізуйте у потрібному серверному блоці й протестуйте): спочатку try normal path, потім quarantine.
Завдання 16: Відстеження bloat БД, пов’язаного з медіа (метадані вкладень можуть бути величезні)
cr0x@server:~$ wp db query "SELECT ROUND(SUM(LENGTH(meta_value))/1024/1024,1) AS mb FROM wp_postmeta WHERE meta_key='_wp_attachment_metadata';"
+------+
| mb |
+------+
| 892.4|
+------+
Значення: Майже гігабайт метаданих вкладень. Це може впливати на бекапи й продуктивність БД.
Рішення: Не «оптимізуйте» шляхом видалення метаданих. Замість цього зменшіть кількість похідних розмірів і регенеруйте вибірково.
Завдання 17: Перевірте, що «регенерація» не змінить URL (вона може зробити це опосередковано)
cr0x@server:~$ wp eval '
$id=12345;
echo get_attached_file($id)."\n";
'
/var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg
Значення: Шлях до оригіналу стабільний. Регенерація мініатюр зазвичай не змінює URL оригіналу, але може додавати/видаляти проміжні файли, на які фронтенд посилається через srcset.
Рішення: Якщо регенеруєте, перевірте вивід srcset в рендереному HTML і впевніться, що CDN/origin має проміжні розміри.
Жарт №2: «Ми просто перегенеруємо мініатюри» — це WordPress-мовою «я хотів би запланувати сюрпризний тест навантаження».
Три корпоративні міні-історії з передової
Міні-історія 1: Інцидент через неправильне припущення (неприкріплене = невикористане)
Команда контенту любила лендинг-сторінки й не любила чекати інженерів. За кілька років вони перейшли від класичного редактора до page builder, потім до блоків, а потім назад до builder «для швидкості». Медіабібліотека роздулася. Бекапи стали повільніші. Хтось нарешті сказав очевидне: «Давайте видалимо неприкріплені медіа.»
Інженер запустив запит для вкладень з post_parent=0 і видалив їх. Здавалося науково. Насправді це було помилково — саме так WordPress любить помилятися: сучасні редактори часто завантажують зображення, які ніколи не «прикріплюються» до батьківського поста, навіть якщо їх всюди використовують.
Аварія не стала повним падінням сайту. Було гірше. Верхня навігація завантажувалась, але hero-зображення пропали на цінних сторінках. Сторінки кампаній виглядали, ніби їх зробили в 1998 році. Підтримка заповнилася скриншотами й повідомленнями, які починалися з «Чи тільки у мене…».
Postmortem виявив банальне: не було індексу посилань, не було карантину й перевірки по логах. Відновили з бекапу, але відновлення також скотило назад сторонні контент-зміни і створило ще один безлад. Справжнє виправлення було нудним: stage-скан посилань, карантин першочергово, потім спостереження 404 і швидкий відкат при необхідності.
Міні-історія 2: Оптимізація, що дала протилежний ефект (дедуп + перейменування для «чистоти»)
Інша організація хотіла зменшити зберігання й «уніфікувати» імена файлів. План: знайти ідентичні зображення, зберегти один канонічний примірник і перейменувати все до чистої схеми на кшталт brand-product-usecase-001.jpg. Був навіть spreadsheet. Саме тут SRE глибоко зітхнув.
Вони перезаписали URL в контенті пошук/заміна та видалили «дублікати». На швидкій перевірці все виглядало нормально. Проблема: не всі посилання жили в пост-контенті. Частина була в налаштуваннях теми, частина — у CSS, частина — в старих PDF, а деякі кещувалися в headless-потребителі, що скрапив контент щодня.
Два тижні деякі зображення випадково падали залежно від шляху запиту: закешований HTML все ще вказував на старі імена, а CDN агресивно кешував 404. Служба підтримки не могла стабільно відтворити проблему. Інженери звинувачували CDN, CDN — origin, origin — «вебсайт». Класика.
Вони врешті впровадили редиректи зі старих на нові URL, але через масове перейменування мапа редиректів була величезною і крихкою. Висновок: дедуп і перейменування — це нормально лише якщо ви ставитесь до старих URL як до постійних і забезпечуєте надійні редиректи (або взагалі не змінюєте URL).
Міні-історія 3: Нудна, але правильна практика, що врятувала день (карантин + верифікація логами)
Ще одна команда мала медіабібліотеку, що тихо росла, доки їх спільне сховище не почало скиглити. Вони не панікували. Клонували продакшн на staging, написали скрипт для списку кандидатів у «сироти» і зробили щось глибоко немодне: вручну переглянули вибірку з командою контенту.
Потім вони карантинували по роках: перемістили найстаріший рік uploads в окрему директорію і додали тимчасовий фолбек на вебсервері, щоб віддавати з карантину, якщо файл запитували. Залишили це на кілька тижнів. Під час цього періоду вони стежили журнали доступу й 404 і одержали реальні докази того, що ще запитується.
Вони знайшли несподіване довгострокове використання: зображення з старого блогу було вбудовано в вікі партнера, а PDF з закритої кампанії був у слайді відділу продажу. Оскільки карантин все ще віддавав файли, ніхто не помітив розрив. Команда просто повернула конкретні активи назад у основний каталог і позначила їх «не видаляти».
Після вікна моніторингу вони видалили решту карантину, скоротили час бекапу й уникнули інциденту з URL. Результат не був драматичним. Він був кращий: ніхто поза інженерами не помітив змін — це найвища похвала для продакшн-систем.
Поширені помилки: симптом → корінь → виправлення
1) Симптом: раптовий сплеск 404 під /wp-content/uploads/
Корінь: Файли видалені або переміщені без редиректів; або CDN втратив об’єкти після зміни налаштувань offload.
Виправлення: Спочатку відновіть/відкличте карантин. Потім впровадьте тимчасове фолбек-обслуговування і побудуйте мапу посилань. Видаляйте лише після вікна моніторингу.
2) Симптом: зображення завантажуються в попередньому перегляді адмінки, але не на публічному сайті
Корінь: Адмінка використовує авторизовані маршрути або інший домен; публічний сайт — CDN-домен або інший шлях відображення.
Виправлення: Порівняйте wp option get siteurl, home та налаштування offload/CDN. Перевірте точний URL у інструментах розробника браузера й простежте його до origin.
3) Симптом: відсутні випадкові розміри на кшталт -1024x768, хоча оригінали є
Корінь: Проміжні розміри видалені, але контент посилається на них через srcset або жорстко прописані URL.
Виправлення: Регенеруйте мініатюри вибірково або додайте правила перепису, що падають назад на оригінал для відсутніх проміжних розмірів (увага: може збільшити трафік).
4) Симптом: сітка Медія повільна, пошук болісний
Корінь: Проблеми з продуктивністю БД, величезні таблиці вкладень, повільне сховище або адмінка виконує дорогі запити; іноді відсутній object cache.
Виправлення: Профілюйте запити БД; переконайтеся в індексах для поширених патернів; додайте object cache (Redis/Memcached) де потрібно; уникайте масової регенерації в робочі години.
5) Симптом: «очищення» звільняє майже нічого місця
Корінь: Ви видалили записи в БД, але не файли, або використовуєте offload де локальний диск не основний слід, або домінують мініатюри.
Виправлення: Виміряйте файлову систему по роках і типах файлів. Підтвердіть, де зберігаються медіа. Порахуйте проміжні розміри на зображення. Очищайте те, що реально велике.
6) Симптом: після міграції старі URL зображень редиректять на головну або в 301-луп
Корінь: Занадто широкі правила перепису або канонічні редиректи від WordPress/SEO-плагінів.
Виправлення: Зробіть редиректи специфічними (тільки для uploads path), тестуйте з curl і уникайте перепису всього на /. Переконайтеся, що редиректи зберігають сегменти шляху.
7) Симптом: видалення «невикористаних» зображень ламає PDF і шаблони емейлів
Корінь: Посилання існують поза постами WordPress (PDF, HTML емейлів у зовнішніх CRM, зовнішні сайти, що хостять посилання).
Виправлення: Використайте журнали доступу + логи CDN, щоб виявити зовнішні звернення. Розглядайте медіа з великою кількістю попадань як «публічне API». Залишайте або редиректьте.
8) Симптом: задача очищення таймаутить або завантажує CPU/I/O
Корінь: Сканування мільйонів файлів на мережевому сховищі, масова регенерація мініатюр або виконання DB LIKE-запитів без обмежень.
Виправлення: Розбийте роботу по роках/місяцях, запускайте у позаробочий час, обмежуйте паралелізм і надавайте перевагу таргетуванню на основі логів, а не повним сканам.
Контрольні списки / покроковий план
Фаза 0: Визначте, що для вас означає «не ламати URL»
- Строго: Кожний історичний URL повертає ті ж байти активу вічно. (Звично для бренд/юридичних активів.)
- Практично: Кожний історичний URL повертає діючий актив (можливо оптимізований/замінений), без 404. (Переважна більшість маркетингових сайтів.)
- Вільно: Важливі URL зберігаються; long-tail може 404. (Прийнятно, тільки якщо ви погоджуєтесь на поламані вбудовування.)
Оберіть один варіант. Запишіть його. Нехай це буде обмеження для кожного рішення.
Фаза 1: Інвентаризація та базова оцінка
- Виміряйте диск і inode (
df -h,df -i). - Виміряйте uploads по роках/місяцях (
du --max-depth). - Складіть список найбільших порушників (
find ... -printf '%s'sort). - Порахуйте вкладення за mime-типами (запит БД).
- Перевірте плагіни offload і підтвердіть їхній режим.
- Зробіть базову вибірку 404 для uploads у логах (топ відсутніх URL).
Фаза 2: Побудуйте модель посилань (достатньо добра, а не ідеальна)
Ви намагаєтесь відповісти: «Якщо я видалю цей файл, хто кричатиме?» WordPress не відповість на це за вас.
- Почніть з прямих посилань у post_content: шукайте патерни
/wp-content/uploads/. Це грубо, але ловить багато випадків. - Включіть використання ID вкладень: блоки часто посилаються на ID, а не URL. Скануйте на наявність патернів
"id":123у вмісті блоків, де можливо. - Перегляньте налаштування теми: логотипи, favicon, Open Graph за замовчуванням, фон.
- Перевірте відомі таблиці плагінів: конструктори, слайдери, галереї.
- Накладіть журнали доступів: запити — це сироватка правди. Якщо URL запитується — він використовується чимось, навіть якщо це старий PDF на сайті партнера.
Фаза 3: Карантин, моніторинг, потім видалення
- Карантин по директоріях (найстаріший рік першим). Переміщуйте файли до карантинного шляху на тому ж файловому пристрої, якщо можливо (швидкі переміщення).
- Опційний страховий механізм: тимчасове правило сервера, щоб віддавати з карантину, якщо файл не знайдено в основному шляху.
- Моніторинг: 404 uploads, топ відсутніх URL і сигнали бюджету помилок протягом принаймні 1–4 тижнів (залежно від патернів трафіку).
- Відновлення винятків: поверніть назад файли, які ще запитуються або на які посилаються.
- Видаліть карантин коли крива запитів стабілізується.
Фаза 4: Запобігання повторному розростанню
- Зменшіть непотрібні проміжні розміри, зареєстровані темою/плагінами.
- Впровадьте максимальні розміри завантаження для редакторів (політика + інструменти).
- Увімкніть серверну оптимізацію зображень обережно (не перейменовуйте файли; не змінюйте URL).
- Перегляньте, хто може завантажувати медіа та які формати дозволені.
- Заплануйте періодичні аудити (щоквартально), а не «п’ятирічні вечірки очищення».
Стратегія редиректів: коли потрібно змінювати шляхи, не імпровізуйте
Якщо ви мігруєте uploads на новий шлях або домен (CDN чи bucket), безпечний метод: зберегти старі URL робочими через редиректи або перепис, що зберігає повний відносний шлях.
- Найкраще: зберегти той самий URL і змінити лише бекенд зберігання (origin тягне з об’єктного сховища, оновлено CDN).
- Друге найкраще: 301 зі старого шляху на новий з ідентичною відносною структурою.
- Уникайте: 302 «тимчасово назавжди», або редирект всього на головну, або перепис рядків запросу без тестування.
Питання й відповіді
1) Чи можна безпечно видаляти «неприкріплені» медіа?
Ні, не як правило. post_parent=0 часто означає «завантажено через сучасний редактор/будівник», а не «невикористовується». Використовуйте сканування посилань і журнали доступу.
2) Яке перше очищення найменш ризикове й дає реальний простір?
Почніть з аутлаєрних директорій (один великий рік/місяць) і очевидних надмірно великих файлів. Потім карантиньте цю частину й моніторьте. Ви навчитесь системі, не ризикуючи всім.
3) Якщо я регенерую мініатюри, чи це зламає URL?
Зазвичай регенерація мініатюр не змінює URL оригіналу. Вона може зламати сторінки, що посилаються на конкретні проміжні імена файлів, якщо ці розміри зміняться або зникнуть. Тестуйте srcset в HTML і переконайтеся, що згенеровані файли є там, де очікують вебсервер і CDN.
4) Як зберегти URL стабільними під час оптимізації зображень?
Оптимізуйте на місці без перейменування і без зміни структури директорій. Якщо оптимізатор перейменовує файли (або конвертує в WebP з новими іменами), потрібні редиректи або ви погоджуєтесь на зламані посилання.
5) Я використовую S3/offload. Чи варто чистити локальний uploads взагалі?
Можливо. Спочатку підтвердіть, чи режим offload зберігає локальні копії. Якщо локаль — лише кеш, видалення може збільшити звернення до origin або спричинити сплески затримок. Якщо об’єктне сховище — авторитетне, об’єкт видаляти треба в бакеті, а не на сервері.
6) Чому я бачу файли на диску, яких немає в Медійній бібліотеці?
Типові причини: невдалі імпорти, ручні завантаження через FTP, старі плагіни або видалені записи вкладень без видалення файлів. Дійсність на диску і в БД з часом розбігаються — плануйте це врахувати.
7) Чи потрібен плагін, щоб знайти невикористані медіа?
Не обов’язково. Можна побудувати надійний процес з WP-CLI, запитами БД і логами. Плагіни можуть допомогти, але додають припущення — особливо щодо будівників і кастомних полів. Перевіряйте результати перед довірою.
8) Який моніторинг використовувати під час карантину?
Відстежуйте частоту 404 для шляхів uploads, топ відсутніх URL і будь-які сплески запитів до origin у CDN. Також стежте за синтетичними перевірками ключових сторінок з великою кількістю зображень.
9) Скільки часу тримати карантин перед видаленням?
Достатньо, щоб охопити ваш цикл трафіку. Для B2B-сайтів 2–4 тижні зазвичай безпечніші, ніж 2–4 дні. Для високотрафікових споживчих сайтів сигнал може з’явитися за 48–72 години, але довгий хвіст зовнішніх вбудовувань все ще існує.
10) Що робити, якщо юридичні/комплаєнс-вимоги вимагають видалення активів?
Тоді ваше обмеження «не ламати URL» змінюється: можливо, потрібно, щоб URL повертали 410 Gone або замінник активу. Робіть це свідомо: логгуючи, документуючи і уникаючи мовчазних 404.
Висновок: що робити наступного тижня
Очищення медіа, що не ламає URL, — це вправа надійності в масці управління контентом. Ви не «чистите бібліотеку». Ви керуєте публічним API активів з багаторічною аудиторією, більшість якої ніколи не подасть тікет.
Практичні наступні кроки:
- Запустіть базові завдання: диск, inode, топ-роки, топ відсутніх URL, перевірка offload.
- Виберіть один старий рік/місяць як пілот. Карантиньте його, не видаляйте.
- Додайте тимчасовий страховий механізм (фолбек сервера), якщо можете, і щодня перевіряйте 404.
- Поверніть винятки, потім видаліть карантин тільки після стабільного вікна моніторингу.
- Запобігайте повторному розростанню: зменшіть розміри зображень, впровадьте політику завантажень і заплануйте щоквартальні аудити.
Якщо робити так, очищення майже розчаровує своєю спокоєм. Саме цього й потрібно. Продакшн-системи винагороджують дорослих.