Товар в 1С с картинкой. Обмен прошёл, статус success, название и цена обновились. Картинки нет.
Или наоборот: картинки есть, все на месте, но папка /upload/ весит четыреста гигабайт при каталоге в двадцать тысяч товаров — и на сервере вот-вот кончится место.
Обе ситуации выглядят как разные проблемы, но причина у них общая: изображение в CommerceML — это не данные товара, а отдельная сущность со своим маршрутом. Файл едет одним путём, ссылка на него — другим, и они соединяются только в момент разбора XML. Понимание этого разделения решает обе задачи.
Как картинка попадает на сайт
В import.xml изображения нет. Есть только путь к нему:
<Товар>
<Ид>f4e2c1a9-...</Ид>
<Наименование>Смеситель для кухни</Наименование>
<Картинка>import_files/a1/smesitel_1024.jpg</Картинка>
<Картинка>import_files/a1/smesitel_1024_2.jpg</Картинка>
</Товар>Сам файл 1С отправляет отдельным HTTP-запросом, тем же способом, которым отправляет XML, — механика разобрана в статье про протокол обмена по CommerceML:
POST ?type=catalog&mode=file&filename=import_files/a1/smesitel_1024.jpgДальше файл проходит три стадии:
| Стадия | Где лежит | Что произошло |
|---|---|---|
| 1. Транзит | /upload/1c_catalog/import_files/ | 1С загрузила файл на сайт |
| 2. Импорт | /upload/iblock/xx/ | Битрикс скопировал его при разборе XML |
| 3. База | Строка в b_file | Файл получил ID, элемент ссылается на него |
Ключевой вывод: пути независимы. XML может приехать со ссылкой на файл, который не долез, — и обмен этого не заметит. Товар обновится, статус будет success, картинки не будет.
Каждая картинка физически существует минимум дважды. В транзитной папке 1c_catalog/import_files/ и в рабочей iblock/. Транзитную папку никто не чистит автоматически, и на больших каталогах она сама по себе становится заметной частью занятого места. Проверьте её размер прямо сейчас — на проекте, который живёт пару лет, там обычно есть на что посмотреть.
Почему картинки пропадают
Четыре причины, в порядке частоты. Все проверяются за минуту.
Файл не долез
Изображения передаются теми же порциями по file_limit, что и XML. Обрывается это на тех же местах:
# Есть ли файл в транзитной папке
ls -la /home/site/www/upload/1c_catalog/import_files/a1/ | head
# Сколько файлов приехало за последний обмен
find /home/site/www/upload/1c_catalog/import_files/ -type f -mmin -120 | wc -l
# Запросы на загрузку файлов в логе — ищем коды, отличные от 200
grep "mode=file" /var/log/nginx/access.log | awk '$9 != 200' | tail -20Типичный виновник — client_max_body_size в nginx, оставшийся дефолтным. Каталог рос, картинки стали тяжелее, и в какой-то момент часть из них перестала проходить. Обмен при этом продолжает завершаться успешно.
Регистр имени
Самая коварная из причин, потому что воспроизводится только на боевом сервере.
1С работает на Windows, где Foto.JPG и foto.jpg — один и тот же файл. Linux их различает. Если в XML написано одно, а на диск лёг файл с другим регистром, ссылка не найдёт ничего.
# Точное имя из XML
grep -o "<Картинка>[^<]*</Картинка>" /home/site/www/upload/1c_catalog/import.xml \
| head -20
# Есть ли файл ровно с таким именем
ls /home/site/www/upload/1c_catalog/import_files/a1/smesitel_1024.jpg
# Есть ли он же в другом регистре
find /home/site/www/upload/1c_catalog/import_files/a1/ -iname "smesitel_1024.jpg"Если вторая команда молчит, а третья находит файл — диагноз поставлен. Лечится на стороне 1С приведением имён к нижнему регистру; со стороны сайта это чинится только костылями, которые сломаются при следующем обновлении.
Кириллица и пробелы в именах
Смеситель для кухни.jpg проходит цепочку из HTTP-запроса, файловой системы и разбора XML, и на каждом стыке кодировка может поехать. Работает это ровно до первого несовпадения локали на сервере.
Правило простое и не обсуждается: имена файлов в выгрузке — латиница, цифры, дефис и подчёркивание. Это настройка на стороне 1С, и она стоит одного разговора с её администратором.
Права на папку
ls -ld /home/site/www/upload/1c_catalog/import_files/
# Владелец должен совпадать с пользователем, от которого работает PHP-FPMПочему растёт диск
Теперь вторая половина проблемы. Она не мешает работе, пока не кончается место, — и поэтому обнаруживается всегда внезапно.
При обновлении товара импорт создаёт новый файл и новую запись в b_file. Прежняя запись не удаляется: элемент просто начинает ссылаться на новый ID. Старое изображение остаётся на диске навсегда.
Посчитаем, во что это обходится. Каталог 20 000 товаров, у каждого три картинки по 300 КБ, обмен раз в сутки:
| Сценарий | Прирост за год |
|---|---|
| Обновляется 1% товаров в день | ~65 ГБ |
| Обновляется 10% товаров в день | ~650 ГБ |
| Полная перезапись всех товаров каждый обмен | место кончится за месяц |
Третья строка не гипотетическая. Именно так ведёт себя обмен, когда 1С выгружает каталог целиком, а Битрикс считает все файлы изменившимися.
Диагностика
# Сколько занимает upload и где именно
du -sh /home/site/www/upload/
du -sh /home/site/www/upload/iblock/ /home/site/www/upload/1c_catalog/
# Сколько физически файлов
find /home/site/www/upload/iblock/ -type f | wc -l-- Сколько записей о файлах в базе
SELECT COUNT(*) FROM b_file;
-- Дубли по имени и размеру — прямой признак повторной заливки
SELECT ORIGINAL_NAME, FILE_SIZE, COUNT(*) AS cnt
FROM b_file
GROUP BY ORIGINAL_NAME, FILE_SIZE
HAVING cnt > 1
ORDER BY cnt DESC
LIMIT 20;Последний запрос — самый показательный. Двадцать копий одного файла с одинаковым размером означают двадцать обменов, каждый из которых залил его заново.
Сначала выясните, почему файлы перезаливаются, потом чистите. Уборка без устранения причины — это разовая акция, после которой диск начнёт заполняться с той же скоростью. Проверьте на стороне 1С, действительно ли картинки меняются от выгрузки к выгрузке: часто оказывается, что 1С пересохраняет файлы при каждой выгрузке, меняя их размер на пару байт, и для сайта они выглядят новыми.
Как безопасно чистить
Уборка осиротевших файлов — операция с необратимыми последствиями, поэтому порядок здесь важнее скорости.
Шаг 1. Бэкап и файлов, и базы. Не «файлов», а обоих: связь между ними хранится в b_file, и восстановить одно без другого невозможно.
Шаг 2. Сначала отчёт, потом удаление. Скрипт должен уметь работать в режиме «показать, что удалю»:
<?php
// Осиротевшие записи: файл числится в b_file, но на него никто не ссылается.
// Список источников ссылок зависит от проекта — здесь только каталожная часть,
// и его обязательно нужно дополнить своими таблицами до боевого запуска.
$sql = "
SELECT f.ID, f.SUBDIR, f.FILE_NAME, f.FILE_SIZE
FROM b_file f
LEFT JOIN b_iblock_element e
ON e.DETAIL_PICTURE = f.ID OR e.PREVIEW_PICTURE = f.ID
LEFT JOIN b_iblock_element_property p
ON p.VALUE = f.ID
WHERE e.ID IS NULL
AND p.ID IS NULL
LIMIT 100
";Шаг 3. Сверка вручную. Возьмите десяток файлов из отчёта и убедитесь, что они действительно не используются: откройте товар, который мог бы на них ссылаться.
Шаг 4. Удаление порциями, а не одним запросом на сотни тысяч строк.
Список источников ссылок на файлы шире, чем кажется. Кроме DETAIL_PICTURE и PREVIEW_PICTURE элементов на файлы ссылаются картинки разделов, свойства типа «файл» — в том числе множественные, — пользовательские поля, баннеры и почти всегда что-нибудь своё, написанное под проект. Скрипт уборки, учитывающий только каталог, удалит нужное. Это тот случай, когда бэкап перед запуском не формальность: восстанавливать придётся не «когда-нибудь», а через час после запуска.
Что делать с превью
Отдельный источник роста диска — превью. Если типовое решение генерирует уменьшенные копии при импорте, каждая перезалитая картинка тянет за собой пересоздание всех своих превью.
Практичнее генерировать размеры на лету и кэшировать результат — подход разобран в статье про ресайз изображений в Битриксе. Тогда перезаливка оригинала не размножает файлы кратно числу используемых размеров, а конвертация в современные форматы делается тем же слоем: про это есть отдельный разбор про WebP на лету.
Проверка после обмена
Три команды, которые стоит выполнять после каждого изменения в настройках обмена:
# 1. Все ли файлы из XML реально лежат на диске
grep -o "<Картинка>[^<]*</Картинка>" /home/site/www/upload/1c_catalog/import.xml \
| sed 's|<[^>]*>||g' \
| while read -r f; do
[ -f "/home/site/www/upload/1c_catalog/$f" ] || echo "НЕТ ФАЙЛА: $f"
done | head -20
# 2. Динамика размера — запускать регулярно и сравнивать
du -sh /home/site/www/upload/iblock/
# 3. Транзитная папка не должна расти бесконечно
du -sh /home/site/www/upload/1c_catalog/import_files/Первая команда — самая полезная. Она отвечает на вопрос «картинки не приехали или не привязались» до всякого копания в админке. Общий порядок разбора описан в статье про отладку обмена: сначала смотрим, что реально приехало, и только потом настройки сайта.
Итоги
Файл и ссылка на него едут разными путями. Порвётся любой — картинки не будет, а обмен отчитается об успехе. Поэтому проверять нужно оба, и начинать с наличия файла на диске.
Регистр имён на Linux значим, на Windows — нет. Это расхождение не воспроизводится на тестовом стенде под Windows и всплывает только в проде.
Каждое обновление товара оставляет старый файл на диске. Не «иногда», а по устройству механизма. На больших каталогах это гарантированный рост, который нужно планировать заранее, а не обнаруживать по алерту о месте.
Уборка — только после выяснения причины перезаливки. Иначе вы будете чистить диск ежеквартально вместо того, чтобы починить один раз.
Скрипт уборки видит меньше ссылок, чем существует. Разделы, множественные свойства, пользовательские поля, своя логика проекта. Бэкап обязателен.
И наблюдение, которое экономит время на диагностике: если картинки пропали не у всех товаров, а у части, — почти всегда дело в именах файлов, а не в обмене. Обмен либо работает для всех, либо не работает ни для кого. А вот регистр, кириллица и пробелы бьют выборочно — ровно по тем позициям, где менеджер в 1С назвал файл по-своему.
Частые вопросы
Почему картинки из 1С не выгружаются на сайт?
Чаще всего файлы не долезли: они передаются теми же порциями по file_limit, что и XML, и обрываются на нехватке прав к папке /upload/1c_catalog/ или на ограничении client_max_body_size в nginx. Второй по частоте случай — расхождение регистра: 1С на Windows не различает Foto.JPG и foto.jpg, а Linux различает, поэтому ссылка из XML не находит реально лежащий файл.
Почему папка upload разрослась после обменов с 1С?
Потому что импорт при обновлении товара создаёт новый файл и новую запись в b_file, а прежнюю не удаляет. Старые изображения остаются на диске без единой ссылки из элементов. На каталоге в 20 000 товаров с ежедневным обменом это даёт рост в десятки гигабайт в год, который замечают, когда на сервере кончается место.
Куда Битрикс кладёт картинки из обмена с 1С?
Сначала в /upload/1c_catalog/import_files/ — это транзитная папка, куда 1С загружает файлы до разбора XML. При импорте Битрикс копирует их в /upload/iblock/ и заводит запись в таблице b_file. То есть каждая картинка физически существует минимум в двух местах, и транзитную папку тоже нужно чистить.
Как связаны картинка и товар в CommerceML?
Внутри Товара стоит тег Картинка с относительным путём вида import_files/a1/имя.jpg. Это ссылка на файл, который приезжает отдельным HTTP-запросом. Если тегов несколько, первый обычно становится детальной картинкой элемента, а остальные — значениями множественного свойства для галереи, чаще всего MORE_PHOTO.
Можно ли удалять старые файлы из upload/iblock вручную?
Только после сверки с таблицей b_file: файл на диске без записи в базе безопасен к удалению, а запись в базе без файла на диске даёт битые картинки на витрине. Удалять по дате изменения нельзя — старая картинка может быть актуальной для товара, который давно не менялся. Перед любой уборкой снимается бэкап и upload, и базы.