Кейс: 318 ГБ фото в OpenCart остановили загрузку товаров
Интернет-магазин одежды на OpenCart перестал принимать новые фото товаров: менеджер загружала папки по FTP, а Total Commander останавливался на 1% с ошибками 550 No such file or directory и «Не удаётся создать удалённый каталог». Причина была не в FTP: фото товаров занимали 318 ГБ на диске в 394 ГБ, и серверу не хватило места для записи. Мы освободили место, собственным скриптом удалили фото товаров, которых уже нет в каталоге, и перевели изображения в WebP. Всё — в рамках обслуживания сайта.
Что видел клиент
Каталог магазина наполняется так: для каждого товара менеджер создаёт на FTP папку с артикулом и загружает туда фото. Одна партия — несколько десятков товаров и сотни мегабайт: отдельный снимок весил 6,2 МБ. В какой-то момент загрузка начала обрываться:
451 Append/Restart not permitted— сервер не дал докачать файл после обрыва;550 No such file or directoryи «Не удаётся создать удалённый каталог» — сервер не создаёт папку для нового товара.
После пяти попыток загрузка не шла дальше 1%, а потом перестала стартовать вообще. На стороне клиента ничего не менялось, поэтому первым подозрением были FTP-клиент или сеть.
Диагностика: куда делось место
Вместо настроек Total Commander мы проверили сам сервер. Картину дали три команды:
df -i— inodes (лимит количества файлов) заняты всего на 2%, значит, дело не в количестве файлов;df -h— основной раздел 394 ГБ, из них занято 316 ГБ;du -ahx / | sort -rh | head -n 20— почти всё это место занимает папкаimageмагазина.
| Папка | Размер | Что в ней |
|---|---|---|
image/catalog | 282 ГБ | оригиналы фото товаров в том виде, в каком их загрузили |
image/cache | 36 ГБ | уменьшенные копии, которые OpenCart генерирует сам |
Всего image | 318 ГБ | 81% всего диска сервера |
Почему OpenCart так разрастается
Это не сбой, а три особенности OpenCart, которые проявляются на больших каталогах:
- Оригинал хранится как есть. OpenCart не уменьшает загруженное фото. Типичный снимок в этом магазине — 2235×3365 пикселей и 2,3 МБ, хотя на сайте он нигде не показывается в таком размере.
- На каждое фото — набор копий. Для карточки товара, списка, корзины, сравнения и других блоков OpenCart создаёт отдельные миниатюры в
image/cache. Здесь это около 8 размеров на каждое фото. - Удалённый товар не забирает с собой фото. Когда товар удаляют в админке, исчезает только запись в базе, а файлы остаются на диске. Ассортимент за годы сменился, а фото старых коллекций никуда не делись.
Каталог растёт с каждой поставкой, так что диск закончится рано или поздно. Вопрос только в том, когда.
Что мы сделали
1. Освободили место, чтобы восстановить загрузку
Сначала убрали то, что можно удалить без риска, — кеш миниатюр image/cache. OpenCart создаёт его заново при первом открытии страницы. После этого FTP снова начал создавать папки, и менеджер продолжила наполнение.
2. Написали скрипт поиска «осиротевших» фото
Готовые модули очистки для OpenCart мы отбросили: они не знают о нестандартном импорте товаров в этом магазине и могут принять живые фото за мусор. Поэтому написали свой скрипт с простой логикой:
- собрать из базы все пути к фото товаров: главные (
oc_product) и из галереи (oc_product_image); - пройти папку с оригиналами и сверить каждый файл со списком;
- файл, которого нет в базе, удалить; папку товара, ставшую пустой, — тоже.
Ядро проверки занимает несколько строк:
$used = [];
foreach (['product', 'product_image'] as $table) {
$res = $db->query("SELECT image FROM " . DB_PREFIX . $table . " WHERE image <> ''");
while ($row = $res->fetch_assoc()) {
$used[$row['image']] = true; // catalog/nobrand/038939/2.jpg
}
}
// path on disk -> path as stored in the database
$relative = str_replace(DIR_IMAGE, '', $fullPath);
if (!isset($used[$relative])) {
unlink($fullPath); // dry run: echo $relative;
} Главный риск такого скрипта — стереть живые фото. Поэтому работали так:
- Сначала — режим имитации. Скрипт ничего не удалял, а показывал первые 100 файлов-кандидатов и примеры путей из базы. Так мы убедились, что пути на диске и в базе записаны одинаково (
catalog/..., без слеша в начале). Если бы они отличались хоть на один символ, скрипт посчитал бы мусором вообще всё. - Только папка с фото товаров. Картинки категорий, баннеров и производителей лежат отдельно, и скрипт их не трогает.
- Запуск из консоли, а не из браузера. Обход сотен гигабайт длится дольше, чем позволяет таймаут веб-сервера. Запуск, оборванный посередине, оставил бы непонятным, что уже удалено. Через PHP CLI скрипт работает до конца и выводит прогресс каждые 5000 файлов.
- После работы скрипт удалили с сервера. Оставленный в корне сайта, он позволил бы любому стереть фото, просто открыв ссылку.
3. Оптимизировали фото и перевели в WebP
Сначала попробовали сжать оригиналы без потерь утилитой jpegoptim. На реальных фото магазина это почти ничего не дало: 2 274 925 → 2 273 571 байт, то есть 0,06%. Снимки из фотостудии уже сохранены с хорошим сжатием, и без потери качества из них ничего не выжать.
Поэтому мы написали собственный скрипт, который проходит каждое изображение, оптимизирует его и конвертирует в WebP. Здесь есть нюанс OpenCart: пути к фото хранятся в базе с расширением .jpg. Если просто заменить файлы на диске на WebP, картинки на сайте пропадут. Поэтому смена формата должна идти вместе с заменой путей в базе.
И ещё: массовое сжатие сотен тысяч файлов сильно нагружает диск. Его стоит запускать в часы наименьшего трафика и с пониженным приоритетом (nice, ionice), чтобы не мешать крон-задачам и импорту товаров.
Результат
- Менеджер снова загружает фото по FTP без ошибок.
- На диске больше нет фото товаров, которых уже нет в каталоге.
- Фото на сайте отдаются в WebP, поэтому страницы каталога стали легче. Особенно это заметно с мобильного интернета.
Как не довести свой магазин до такого
- Настройте уведомление о заполнении диска, например на 80%. Ошибка FTP — это уже поздний симптом.
- Уменьшайте фото до загрузки. Оригинал на 3000+ пикселей лежит на диске навсегда, хотя сайту нужен гораздо меньший.
- Раз в квартал проверяйте размер папки
imageи чистите фото удалённых товаров. - Перед массовым удалением — бэкап и режим имитации. Восстановить стёртые фото без копии невозможно.
Такие задачи мы решаем в рамках обслуживания сайтов: следим за состоянием сервера и сайта, чтобы проблема не дошла до покупателей. Если ваш магазин на OpenCart замедлился или сервер «съедает» место, напишите нам — посмотрим.
Частые вопросы
Да. Там только уменьшенные копии фото, и OpenCart создаст их заново при первом открытии страниц. Первые открытия страниц после очистки будут немного медленнее.
Стандартный OpenCart при удалении товара убирает только записи в базе данных, а файлы изображений остаются на диске. Их приходится чистить отдельно — вручную или скриптом, который сверяет файлы с базой.
Выполните на сервере df -h (свободное место) и df -i (лимит количества файлов), а также проверьте дисковую квоту FTP-пользователя в панели хостинга. Типичные симптомы — ошибка 550 при создании папки и отказ докачивать файлы.
Только вместе с заменой путей в базе данных: OpenCart хранит их с расширением .jpg, и без этого фото на сайте пропадут.