Node.JS и выгрузка каталога из 1С на сайт

от автора

На днях закрыли очередной проект. Суть: создание новой версии интернет-каталога. Старая версия сайта, в силу ряда причин, клиента не устраивала. Особенностью проекта была его номенклатурная база. Объём номенклатуры каталога составлял ~26000 позиций раскиданных по дереву из 513 узлов + характеристики товара. Почти каждая номенклатурная позиция имела описание на 1-2К текста.

Файл выгрузки каталога в формате ComerceML 2 для старого сайта весил 104 MB. Формировался на стороне 1С 10 минут и после передачи на хостинг, парсился на стороне сайта полтора часа (!) со 100% загрузкой CPU.

Выход из ситуации

В качестве альтернативы 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;

Сервис обработчика выгрузки реализован по схеме:

  1. распаковывать архив;
  2. распарсить JSON;
  3. сообщить в HTTP ответе что всё впорядке или произошла ошибка;
  4. если всё хорошо — выставить флаг занятости и заливать данные в базу до победного конца;
  5. умереть очистив память.
  6. Возродиться новым процессом — за это отвечает 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); }); 

В заключение хочу традиционно пожелать, чтобы этот материал был ещё кому-нибудь полезен также как был полезен нам.

ссылка на оригинал статьи http://habrahabr.ru/post/189812/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *