feedsync — фіди постачальників у прайс-листи маркетплейсів
Огляд
Незалежний проєкт: сервіс на Go, що перетворює прайс постачальника (YML/XML, CSV або JSON) на прайс-листи для Rozetka та Prom.ua і синхронізує таблицю товарів OpenCart. Мапінг категорій, конвертація валют, правила націнок (фіксовані, відсоткові, за діапазоном цін чи категорією, з округленням) і фільтр залишків — це конфігурація, а не код; кожна відхилена позиція потрапляє у звіт із правилом, яке вона порушила.
Результат: 25 000 позицій перетворюються на обидва прайс-листи приблизно за 1,5 с і ≈36 МБ пам’яті, кожен опублікований файл проходить незалежну повторну перевірку з 0 помилок, а повторна синхронізація OpenCart нічого не змінює (≈25 мс).
Справжня складність
Конвертувати XML просто. Складно те, що маркетплейс відхиляє — або мовчки викидає — весь прайс через кілька некоректних позицій, а фіди надто великі, щоб їх переглядати очима. Тому конвеєр побудований навколо принципу не публікувати неперевірений файл: фід читається потоком, кожна позиція перевіряється окремо, готовий файл перечитується з диска і перевіряється тим самим набором правил, і лише тоді замінює діючий.
Архітектура

Один потоковий прохід читає фід, застосовує перетворення й пише кожну ціль у тимчасовий файл. Потім кожен файл повторно перевіряється з диска й атомарно перейменовується поверх опублікованого; невдалий запуск залишає попередній файл. Той самий прохід дає список оновлень для OpenCart — пакетні UPDATE … CASE в одній транзакції; змінюються лише рядки з новими значеннями.
Ключові інженерні рішення
| Рішення | Чому |
|---|---|
Потоковий xml.Decoder, один DecodeElement на позицію |
Пам’ять не росте з розміром фіда: у 4 рази більше позицій — ≈2,5× пікової пам’яті (36 → 91 МБ на 100 000) |
| Повторна перевірка перечитує файл і ділить набір правил із передперевіркою | Не довіряємо, що записувач записав те, що мав; перевіряється саме опублікований файл |
| Публікація через temp-файл і атомарне перейменування | Маркетплейс ніколи не бачить напівзаписаний чи невдалий файл |
Детерміновані ID позицій ([A-Za-z0-9]+ лишаються, інакше префікс + SHA-1) |
Той самий SKU постачальника щоразу дає той самий ID |
| Гроші як цілі копійки | Жодного дрейфу float у націнках і округленні |
| Товар без залишку обнуляється в CMS, ціна не чіпається | Мало прибрати його з прайсу — магазин продовжував би продавати те, чого в постачальника вже немає |
Результати
| Показник | Результат |
|---|---|
| 25 000 позицій (26 МБ) → Rozetka + Prom.ua | ≈1,5 с, ≈36 МБ пікової пам’яті |
| 100 000 позицій (105 МБ) | 6–7 с, ≈91 МБ |
| Повторна перевірка опублікованих файлів | 0 помилок в обох цілях |
| Відхилено для Rozetka | 259 позицій, кожна з правилом (немає картинки, картинка не HTTPS, немає виробника) у report.json |
| Синхронізація OpenCart, MySQL 8.4 | 19 119 рядків у 39 пакетах, ≈0,7 с; повторний запуск: 0 змін, ≈25 мс |
Юніт- та інтеграційні тести працюють під -race.
Підключення до вашого магазину
- Фід постачальника: вкажіть у
source.pathфайл або URL іsource.format; новий формат — ще один читач. - Маркетплейси: кожен запис у
targetsформує один прайс-лист — спрямуйтеoutу каталог, який віддає ваш вебсервер. - CMS: рядок підключення до MySQL в
opencart.dsn— і синхронізація цін і залишків починається з наступного запуску. - Розклад:
feedsync convert -c config.yaml -every 30m.
Код: github.com/valpere/feedsync
Стек
Go · потоковий encoding/xml · MySQL · SQLite · Docker