На днях закрыли очередной проект. Суть: создание новой версии интернет-каталога. Старая версия сайта, в силу ряда причин, клиента не устраивала. Особенностью проекта была его номенклатурная база. Объём номенклатуры каталога составлял ~26000 позиций раскиданных по дереву из 513 узлов + характеристики товара. Почти каждая номенклатурная позиция имела описание на 1-2К текста.
Файл выгрузки каталога в формате ComerceML 2 для старого сайта весил 104 MB. Формировался на стороне 1С 10 минут и после передачи на
Выход из ситуации
В качестве альтернативы XML формату решили выгрузить в JSON. Идея была в том, чтобы попробовать разобрать JSON чем-то, что имеет нативную реализацию парсера, а именно node.js с его JSON.parse().
Наш 1С-ник, разобравшись с новым для него форматом, в несколько итераций добился того, что выгрузка 1С формировала валидный JSON. Время формирования выгрузки сократилось с 10 до 3.5 минут. Те же данные которые в XML формате занимали 104 мегабайта, уместились в 58 мегабайт JSON. Но это было ожидаемо, неожиданностью стало другое…
Для тестирования времени разбора выгрузки я набросал тестовый код:
// Node.js
var fs = require('fs');
function parser(filename, callback){
fs.readFile(filename, { encoding:'utf8' }, function(err, dataFromFile){
var parsedData;
if(err){
callback(err);
} else {
try {
console.time('parse data'); // парсинг - синхронная операция...
parsedData = JSON.parse(dataFromFile.toString().trim()); // <- собственно сам парсинг.
console.timeEnd('parse data'); // ... поэтому измеряем задержку "в лоб".
callback(null, parsedData );
}
catch (e){
callback(e)
}
}
});
}
parser('../import/import.json', function(err, data){
if(err){
throw (err);
}
console.log('groups', data.groups.length);
console.log('items', data.items.length);
console.log('properties', data.properties.length);
});
Запустив его на своей машине (CPU 3.3GHz), я даже не успел привстать чтобы пойти за чаем. Результат и скорость с которой он был выведен в консоль, заставил меня предположить, что в коде баг и он не отработал корректно…
> node parse.js
parse data: 718ms
groups 513
items 26098
properties 149
Но это был не баг. Данные действительно были разобраны и помещены в память за ⅔ секунды. Количество элементов в коллекциях полностью совпадало с заявленным количеством в 1С. Оставалось только найти под столом отпавшую челюсть и написать сервис с полным циклом обработки данных.
Общая архитектура сервиса обработки выгрузки
В целом выгрузка на сайт отрабатывает по стандартной схеме:
- формирование выгрузки из 1С и его упаковка архиватором;
- загрузка сформированных файлов по FTP;
- вызов обработчика выгрузки по HTTP;
Сервис обработчика выгрузки реализован по схеме:
- распаковывать архив;
- распарсить JSON;
- сообщить в HTTP ответе что всё впорядке или произошла ошибка;
- если всё хорошо — выставить флаг занятости и заливать данные в базу до победного конца;
- умереть очистив память.
- …
- Возродиться новым процессом — за это отвечает Monit.
На продакшене (DigitalOcean, тариф за $10), с момента вызова и до пункта 3 сервис отрабатывает в целом через 3-4 секунды, после чего повторный вызов сервиса будет возвращать флаг занятости пока заливается база. Весь цикл обработки выгрузки с занесением данных в базу составляет 80 — 90 секунд. Загрузка процессора в момент парсинга выглядит как единичный пик до 70% с основанием в 10 — 30%.
В итоге:
- время формирования выгрузки сократилось с 10 до 3.5 минут;
- объём выгрузки сократился со 104 до 58 мегабайт (1.5 мегабайта после архивации);
- полное время обработки выгрузки на стороне сервера сократилось с полутора часов до полутора минут;
- ???????
- PROFIT
P.S. Средство от головной боли при отладке.
При всей своей скорости JSON.parse() очень неудобен для отладки. В случае наличия ошибки в структуре JSON, вы получаете практически ноль отладочной информации. На этапе пока ваш 1С специалист осваивает JSON очень выручает модуль JSON Lint. Он может быть использован как отдельная утилита или в качестве библиотеки. В отличии от штатного парсера он сообщает в объект исключения номер строки JSON-файла где произошло недоразумение, что при разборе косяков в файле в десятки мегабайт кардинально облегчает жизнь. Цена за такое удобство — скорость. Она упадёт в 5-7 раз по сравнению с нативным JSON.parse().
Тот же тестовый код с JSON Lint будет выглядеть так.
// Node.js
var fs = require('fs'),
jsonlint = require("jsonlint"); // Очень полезен на этапе отладки
function parser(filename, callback){
fs.readFile(filename, { encoding:'utf8' }, function(err, dataFromFile){
var parsedData;
if(err){
callback(err);
} else {
try {
console.time('parse data'); // парсинг - синхронная операция...
/* Используем Jsonlint если нам нужна более подробная информация о месте ошибки в структуре JSON.
* Работает медленнее в 5-7 раз чем нативный JSON.parse().
*/
parsedData = jsonlint.parse(dataFromFile); // тот же парсинг, но с дебагом и поэтессами.
console.timeEnd('parse data'); // ... поэтому измеряем задержку "в лоб".
callback(null, parsedData );
}
catch (e){
callback(e)
}
}
});
}
parser('../import/import.json', function(err, data){
if(err){
throw (err);
}
console.log('groups', data.groups.length);
console.log('items', data.items.length);
console.log('properties', data.properties.length);
});
В заключение хочу традиционно пожелать, чтобы этот материал был ещё кому-нибудь полезен также как был полезен нам.
Автор: Staltec