gifts2017

Оптимизация выгрузки из 1С 7.7 по правилам обмена большого объема данных

Опубликовал Oleg (Just4Fun) в раздел Обмен - Перенос данных из 1С7.7 в 1C8.X

При выгрузке из 1С 7.7. большого справочника программа завершалась примерно с таким сообщением: "msxml6.dll: Недостаточно памяти для завершения операции". Вариант выгрузки порциями меня не устраивал. Поэтому немного оптимизировал обработку выгрузки. Возможно кому-то сэкономит время.

Конвертация данных хороший инструмент, которая позволяет быстро реализовать несложную перегрузку данных между различными базами 1С. С помощью конвертации данных реализованы перенос остатков из типовых конфигураций 1С 7.7 в типовые на базе 1С8.

Когда в очередной раз столкнулся с необходимостью переноса данных из 1С7.7 в 1С8, то столкнулся с проблемой. При выгрузке большого справочника (около 400 тыс. элементов. Все относительно конечно)  семерка вылетает с ошибкой «недостаточно памяти…» Проблема в том, что обработка выгрузки использует объектную модель DOM для работы с XML. В результате все дерево XML до записи в файл хранится в памяти. Также столкнулся с тем, что при выгрузке большого числа элементов, чем дольше работает выгрузка тем медленнее она работает.

Решил немного переделать обработку выгрузки, чтобы исключить эти узкие места.

Первая доработка. Возможность формирования файла xml большого размера. 

Доработка заключается в потоковой записи данных XML сразу в файл. На IS видел публикацию в которой автор писал, что использовал другой объект для работы с XML, но подход вроде такой же – запись сразу в файл.

Для потоковой записи в файл использую объект FileSystemObject Windows Script Host. Доработки следующие.

1.Добавить описание переменных

Перем ФСО; 
Перем ХМЛФайл;

2.Изменить процедуру Выгрузить()

Процедура Выгрузить()

    // Добавлено. Начало
    ФСО = СоздатьОбъект("Scripting.FileSystemObject");
    ХМЛФайл = ФСО.CreateTextFile(ИмяФайлаДанных, 1, 1);
    // Добавлено. Конец
        
    ВыполнитьВыгрузку();

    // Добавлено. Начало
    ХМЛФайл.Close();
    // Добавлено. Конец
    
    Если Форма.МодальныйРежим() = 0 Тогда
        //Предупреждение("Выгрузка данных завершена.");
        Сообщить("Выгрузка данных завершена.");
    Иначе
        Сообщить("Выгрузка данных завершена.");
    КонецЕсли;
    
    Форма.Параметр = СписокОшибок;
    
КонецПроцедуры // Выгрузить()

3.Изменить процедуру ВыгрузитьПоПравилу()
Весь текст процедуры не пишу.  Изменения ближе к концу процедуры

Функция ВыгрузитьПоПравилу(Источник, Приемник, ВходящиеДанные, ИсходящиеДанные, ИмяПКО = "", УзелСсылки = "", ТолькоПолучитьУзелСсылки = 0,
    НомерПКО = 0)
………………………………………………………………
    // Изменено. Начало
    // Запись объекта
    //ДобавитьПодчиненный(rootNode, Приемник);
    ХМЛФайл.WriteLine(Приемник.xml);
    // Изменено. Конец
    
    // Обработчик ПослеВыгрузкиВФайлОбмена
    Если ПолучитьРеквизитПКО(НомерПКО, "ПослеВыгрузкиВФайл") = 1 Тогда
        
        КодПравила = СокрЛП(ПолучитьРеквизитПКО(НомерПКО, "Код"));
        
        Отказ = Шаблон("[ПКО_ПослеВыгрузкиВФайлОбмена_" + КодПравила + "(Источник, ВходящиеДанные, ИсходящиеДанные, ИмяПКО, Приемник, УзелСсылки)]");
        
        Если Число(Отказ) = 1 Тогда
            
            Возврат УзелСсылки;
            
        КонецЕсли;

    КонецЕсли;
    
    Возврат УзелСсылки;

КонецФункции // ВыгрузитьПоПравилу()

4.Изменить процедуру ИнициализацияФайлаОбмена()

Процедура ИнициализацияФайлаОбмена()
    
    УстановитьАтрибут(rootNode, "ВерсияФормата", "2.0");
    УстановитьАтрибут(rootNode, "ДатаВыгрузки",  ПолучитьДатуV8(ТекущаяДата(), ТекущееВремя()));
    УстановитьАтрибут(rootNode, "НачалоПериодаВыгрузки", ПолучитьДатуV8(ДатаНачала));
    УстановитьАтрибут(rootNode, "ОкончаниеПериодаВыгрузки", ПолучитьДатуV8(ДатаОкончания));
    УстановитьАтрибут(rootNode, "ИмяКонфигурацииИсточника", мКонфигурацияИсточник);
    УстановитьАтрибут(rootNode, "ИмяКонфигурацииПриемника", мКонфигурацияПриемник);
    УстановитьАтрибут(rootNode, "ИдПравилКонвертации", мИд);
    УстановитьАтрибут(rootNode, "Комментарий", "");

    // Добавлено. Начало
    ХМЛФайл.WriteLine("ВерсияФормата=" + """2.0""");  //NewNode.xm
    ХМЛФайл.WriteLine("ДатаВыгрузки" + "=""" + Строка(ПолучитьДатуV8(ТекущаяДата(), ТекущееВремя())) + """");
    ХМЛФайл.WriteLine("НачалоПериодаВыгрузки" + "=""" + Строка(ПолучитьДатуV8(ДатаНачала)) + """");
    ХМЛФайл.WriteLine("ОкончаниеПериодаВыгрузки" + "=""" + Строка(ПолучитьДатуV8(ДатаОкончания)) + """");
    ХМЛФайл.WriteLine("ИмяКонфигурацииИсточника" + "=""" + Строка(мКонфигурацияИсточник) + """");
    ХМЛФайл.WriteLine("ИмяКонфигурацииПриемника" + "=""" + Строка(мКонфигурацияПриемник) + """");
    ХМЛФайл.WriteLine("ИдПравилКонвертации" + "=""" + Строка(мИд) + """");
    ХМЛФайл.WriteLine("Комментарий" + "=""""");
    ХМЛФайл.WriteLine(">");
    // Добавлено. Конец
………………………………………………………………
………………………………………………………………
    // Добавлено. Начало
    ХМЛФайл.WriteLine(УзелПравилаОбмена.xml);
    // Добавлено. Конец


КонецПроцедуры // ИнициализацияФайлаОбмена()

5.Изменить процедуру ВыполнитьВыгрузку()

Процедура ВыполнитьВыгрузку()
…………………………………………………
    rootNode = DOMDocument.createNode(1, "ФайлОбмена", "");
    
    // Добавлено. Начало.
    ХМЛФайл.WriteLine("<" + "ФайлОбмена");
    // Добавлено. Конец
…………………………………………………..
    // Изменено. Начало
    //DOMDocument.appendChild(rootNode);
    //DOMDocument.save(ИмяФайлаДанных);
    ХМЛФайл.WriteLine("</" + "ФайлОбмена>");
    // Изменено. Конец

    
    ВывестиСообщение("Выгружено объектов:   " + мСчетчикВыгруженныхОбъектов);
    
    ВывестиСообщение("Окончание выгрузки:   " + ТекущаяДата() + " " + ТекущееВремя());

КонецПроцедуры // ВыполнитьВыгрузку()

Вторая доработка. Оптимизация скорости выгрузки.
По мере выгрузки справочника делал простой замер скорости. Считал за сколько минут выгружается  допустим 1000 объектов. И по этим данным делал примерный расчет общего времени выгрузки. И чем больше объектов выгружалось, тем больше становилось расчетное время выгрузки.  25 тыс – 4,5 часа. 50тыс. – 6 часов. На 100 тыс – уже 10 часов.
Сделал замер. На три однотипные строчки кода тратилось 78% времени.

УзелСсылки = ВыгруженныеОбъекты.Получить(КлючВыгружаемыхДанных);
ВыгруженныеОбъекты.Установить(КлючВыгружаемыхДанных, Нпп);
ВыгруженныеОбъекты.Установить(КлючВыгружаемыхДанных, УзелСсылки);

Изначально ВыгруженныеОбъекты – это список значений.
Заменил Список значений на индексированную таблицу из компоненты 1С++

Что сделать.
1.    Изменил процедуру  ПриОткрытии ()

Процедура ПриОткрытии()
    
    // Добавлено. Начало
    ЗагрузитьВнешнююКомпоненту("1cpp.dll");    
    // Добавлено. Конец
…………………………………………………………
КонецПроцедуры // ПриОткрытии()

2.    Изменить процедуру ЗагрузитьПКО()

Процедура ЗагрузитьПКО(Знач Порядок = "")
    
    мТаблицаПравилКонвертацииОбъектов.НоваяСтрока();
    мТаблицаПравилКонвертацииОбъектов.ТекущаяСтрока(мТаблицаПравилКонвертацииОбъектов.КоличествоСтрок());
    
    //мТаблицаПравилКонвертацииОбъектов.Выгруженные = СоздатьОбъект("СписокЗначений");
    ИндТаблица = СоздатьОбъект("ИндексированнаяТаблица");
       ИндТаблица.НоваяКолонка("КлючВыгружаемыхДанных");
       ИндТаблица.НоваяКолонка("Ссылка");
       ИндТаблица.ДобавитьИндекс("Ключ","КлючВыгружаемыхДанных");
       мТаблицаПравилКонвертацииОбъектов.Выгруженные = ИндТаблица;
…………………………………………………………………………………………………
КонецПроцедуры // ЗагрузитьПКО()

3.    Изменить процедуру ВыгрузитьПоПравилу()
Места изменния найти поиском по слову ВыгруженныеОбъекты

Функция ВыгрузитьПоПравилу(Источник, Приемник, ВходящиеДанные, ИсходящиеДанные, ИмяПКО = "", УзелСсылки = "", ТолькоПолучитьУзелСсылки = 0,
    НомерПКО = 0)
……………………………………………………………
        Если НеЗапоминатьВыгруженные = 0 Тогда
            
            // Изменено. Начало
            //УзелСсылки = ВыгруженныеОбъекты.Получить(КлючВыгружаемыхДанных);
            НомерСтроки = ВыгруженныеОбъекты.НайтиСтроку("Ключ", 1);
            Если НомерСтроки = 0 Тогда
                УзелСсылки = "";
            Иначе
                УзелСсылки = ВыгруженныеОбъекты.ПолучитьЗначение(НомерСтроки, "Ссылка");
            КонецЕсли;
            // Изменено. Конец
            
            Если ПустоеЗначение(УзелСсылки) = 0 Тогда
                
                Возврат УзелСсылки;
                
            КонецЕсли;
            
        КонецЕсли;
…………………………………………………………………….
        // Это позволит избежать циклических ссылок
        Если НеЗапоминатьВыгруженные = 0 Тогда
            
            // Изменено. Начало
            //ВыгруженныеОбъекты.Установить(КлючВыгружаемыхДанных, Нпп);
            ВыгруженныеОбъекты.НоваяСтрока();
            ВыгруженныеОбъекты.КлючВыгружаемыхДанных = КлючВыгружаемыхДанных;
            ВыгруженныеОбъекты.Ссылка = Нпп;
            // Изменено. Конец
            
        КонецЕсли;
……………………………………………………………
                ВыгрузитьСвойства(Источник, Приемник, ВходящиеДанные, ИсходящиеДанные, НомерПКО, ПолучитьРеквизитПКО(НомерПКО, "СвойстваПоиска"),
                    УзелСсылки, , , ИмяПредопределенногоЭлемента, ВыгрузитьТолькоСсылку);
                
                // Изменено. Начало    
                //ВыгруженныеОбъекты.Установить(КлючВыгружаемыхДанных, УзелСсылки);
                ВыгруженныеОбъекты.НоваяСтрока();
                ВыгруженныеОбъекты.КлючВыгружаемыхДанных = КлючВыгружаемыхДанных;
                ВыгруженныеОбъекты.Ссылка = УзелСсылки;
                // Изменено. Конец
……………………………………………………………
КонецФункции // ВыгрузитьПоПравилу()


После этих изменений выгрузка моего справочника выполнилась за 2 с половиной часа.
С чем так и не смог справиться, так это с ошибкой о нехватке памяти, когда для ПКО НЕ стоит свойство «не запоминать выгруженные. Растет таблица закэшированных xml-фрагментов ссылок на выгруженные объекты. Пришлось для ПКО моего большого справочника поставить «не запоминать выгруженные объекты».

См. также

Подписаться Добавить вознаграждение

Комментарии

1. Андрей Первозванный (rfcor) 30.09.14 17:17
Сейчас попробуем) для начала неплохо было бы упомянуть, что сперва нужно зарегистрировать библиотеку scrrun.dll.
В командной строке под админом:
cd c:\windwos\system32
regsvr32 scrrun.dll
2. Андрей Казанцев (ander_) 01.10.14 10:39
Здорово! А никто не подскажет как бы подобным волшебным образом ускорить типовой обмен в 8-ке?
3. Oleg (Just4Fun) 01.10.14 11:02
(2) ander_, на практике с медленной работой тпового обмена не сталкивался. По крайней мере обмен не выполнялся настолько долго, что бы стать проблемой. Нужно смотреть конкретно на вашем случае, где узкое место при обмене. Точнее не отвечу.
4. Роман Роман (skt-roman) 21.12.14 16:29
Не понял где поставить в ПКО не запоминать выгруженные объекты
5. Oleg (Just4Fun) 22.12.14 12:35
(4) skt-roman, это галочка в свойствах ПКО. При наличии этой галочки, для объекта в файл XML будет формироваться полная выгрузка по правилам, каждый раз когда объект встречается при выгрузке. А если галочка НЕ стоит - то в файл объект записывается полностью только один раз, а дальше, если встречается повторно - записывается только описание "ссылки" с полями поиска.
6. Роман Роман (skt-roman) 28.12.14 07:14
Сделал первую доработку. Виснет на выгрузке счета 41.1, оставил выгрузку по правилу только этот счет - виснет выгрузив 2284 объекта
7. Oleg (Just4Fun) 29.12.14 10:27
(6) skt-roman, общий смысл первой доработки - исключить хранение в памяти объекта большого размера. это реализовано потоковой записью в файл. Общий алгоритм выгрузки не затронут.

Виснет на выгрузке счета 41.1, оставил выгрузку по правилу только этот счет - виснет выгрузив 2284 объекта
Если я правильно понял, выгружаете только этот один счет. А если выгружается 2284 объекта для одного счета - то это очень похоже на зацикливание.
Предположу, что для ПКО какого-то объекта, который попал в выгрузку, снята галочка "не запоминать выгруженные" (о чем говорили в (4) и (5)), а у выгружаемого объекта есть реквизит, который ссылается на этот же объект. По крайней мере сталкивался с таким. Точнее скажет только отладчик. Если не ошибаюсь, при записи сразу в файл, этот файл можно сразу открыть. Посмотрите по содержимому, что именно выгружается для вашего счета.

По поводу галочки "не запоминать выгруженные". в коде обработки выгрузки есть такой комментарий:
       
// Это позволит избежать циклических ссылок
        Если НеЗапоминатьВыгруженные = 0 Тогда
            
            ВыгруженныеОбъекты.Установить(КлючВыгружаемыхДанных, Нпп);
            
        КонецЕсли;
...Показать Скрыть


По этому ставить этот флаг у ПКО объекта нужно, только в крайнем случае, если это действительно необходимо.
8. Роман Роман (skt-roman) 31.12.14 05:48
(7) Just4Fun, выгрузка без доработки это отрабатывает нормально, странно.
9. Юрий Патласов (NoRazum) 13.05.15 18:14
Когда смотрел стандартную выгрузку из 7.7 бухгалтерию в Бух 3.0
Особенно выгрузку остатков по счету. Кто придумывал такой алгоритм работы...
Создается таблица потом в первом прогоне уменьшается в два раза и потом еще раза три - четыре в цикле крутиться.
Пока это не исправить все остальное: мелочи.
10. Сергей Фирсаев (buy_sale) 16.06.16 01:32
Примерно до 20000 выгрузка шла быстро, потом стала выгружать примерно по 10 объектов в секунду. Никто не сталкивался ?
11. Oleg (Just4Fun) 16.06.16 12:46
(10) buy_sale, Сталкивался. Мой случай описан во "второй доработке" из этой статьи. Уже не помню свои исходные данные, но прогнозная оценка времени выгрузки составляла больше 10 часов. После доработки выгрузка выполнилась за 2,5 часа
Если применили эту доработку, то понять причину поможет только отладчик.
12. Сергей Фирсаев (buy_sale) 17.06.16 13:57
Уважаемый а вы уверены, что это тождественнно

//УзелСсылки = ВыгруженныеОбъекты.Получить(КлючВыгружаемыхДанных);
            НомерСтроки = ВыгруженныеОбъекты.НайтиСтроку("Ключ", 1);


мне кажется надо

//УзелСсылки = ВыгруженныеОбъекты.Получить(КлючВыгружаемыхДанных);
            НомерСтроки = ВыгруженныеОбъекты.НайтиСтроку("Ключ", КлючВыгружаемыхДанных);

иначе зацикливается на одном элементе

исправил так как указано - 480 000 за 90 мин !) До этого было 2 суток и падало с ошибкой. База 1,5 гиг свернутая

Аплодирую стоя !

В ВыгрузитьПоПравилу добавил для спр Номенклатура ( У меня 160000 элементов)
....
	НеЗапоминатьВыгруженные     = ПолучитьРеквизитПКО(НомерПКО, "НеЗапоминатьВыгруженные");
	НеВыгружатьОбъектыСвойствПоСсылкам = ПолучитьРеквизитПКО(НомерПКО, "НеВыгружатьОбъектыСвойствПоСсылкам");
	ВыгруженныеОбъекты          = ПолучитьРеквизитПКО(НомерПКО, "Выгруженные");
	ВсеОбъектыВыгружены         = ПолучитьРеквизитПКО(НомерПКО, "ВсеОбъектыВыгружены");
	НеЗамещатьОбъектПриЗагрузке = ПолучитьРеквизитПКО(НомерПКО, "НеЗамещать");
	НеСоздаватьЕслиНеНайден     = ПолучитьРеквизитПКО(НомерПКО, "НеСоздаватьЕслиНеНайден");
	
	РежимЗаписи     			= "";
	РежимПроведения 			= ""; 
	
	//фирс
	Если ТипЗначенияСтр(Источник)="Справочник" Тогда
		Если Источник.Вид()="Номенклатура" Тогда
			НеЗапоминатьВыгруженные = 1;
		КонецЕсли;
	КонецЕсли;	
	//фирс
...Показать Скрыть
Прикрепленные файлы:
ACC_ACC8.ert
13. Oleg (Just4Fun) 21.06.16 13:44
(12) buy_sale,
"Уважаемый а вы уверены, что это тождественнно " -
За давностью лет, подтвердить или опровергнуть не смогу. Поскольку вместо типового списка значений задействовал индексированную таблицу из 1СPP, то возможно сделал так по возможному синтаксису метода этого объекта. Уже не помню, скорее всего в методе допускалось указание имени или номера колонки, потому так и написал.

"Аплодирую стоя ! "
Пожалуйста.
14. Сергей Фирсаев (buy_sale) 23.06.16 22:15
Есть проблема : выгрузка зависает на 497144 объектах. После загрузки БП3 пишет Extra Content at the end of document. Соответственно документы остатков по самому большому справочнику номенклатуры (150 000 элементов) не появляются.

даже когда маленький файл гружу все равно конец обрезается (((
https://yadi.sk/i/8b7xdzFRsm4Hm
15. Oleg (Just4Fun) 28.06.16 00:56
(14) buy_sale,
"даже когда маленький файл гружу все равно конец обрезается"
Нужно проверить, корректно ли "закрывается" файл, т.е. все ли закрывающие теги на месте. На маленьком файле должно быть видно. Если файл действительно небольшой, то можно открыть в IE - должен корректно отобразиться в виде дерева.
В своей практике сталкивался с проблемой - "некорректный" символ в строке (в наименовании элемента справочника) - долго не мог понять, в чем причина.

"Соответственно документы остатков по самому большому справочнику номенклатуры (150 000 элементов) не появляются."
Может конечно у вас и не одним документом все грузится, но на всякий случай посоветовал бы ограничить количество строк на документ, например по 1-2 тыс. строк.. С документами с небольшим количеством строк потом и работать легче (отрывать, проверять, проводить и т.п.).

Еще как вариант оптимизации, выгрузить отдельно ваш большой справочник, только то что возвращает запрос для выгрузки остатков. А потом отдельно выгружать документы остатков с включенным флагом (в ПКО) "не выгружать объекты по ссылкам..."
16. Oleg (Just4Fun) 28.06.16 01:04
(14) buy_sale, посмотрел внимательно на скрин с ошибкой. Нет ли в обработчике "после загрузки данных" чтения каких-либо дополнительных данных из файла?
17. Валерий Покатило (its_valera) 10.08.16 07:20
Добрый день! А 1CPP.dll в Windows 10 работает? Что то не могу зарегистрировать dll-ку.
18. Валерий Покатило (its_valera) 10.08.16 07:56
Господа, подскажите что делаю не так. Не могу зарегистрировать 1СРР через regsvr32. Скачал с 1cpp.ru. Пробовал на Виндовс 10, 7 и ХР. Пушу в командной строке от Админа regsvr32 "c:\1CPP.dll" , ошибка Не найден путь.
19. Валерий Покатило (its_valera) 10.08.16 09:36
Вопрос снят! Просто закинул ее в BIN и в папку с базой!

Теперь другая ошибка при выгрузке:
мТаблицаПравилКонвертацииОбъектов.Выгруженные.УдалитьВсе();
{C:\CONVERT_25\V77EXP+.ERT(59116)}: Поле агрегатного объекта не обнаружено (УдалитьВсе)
20. Пафнутий Чебышев (primat) 08.11.16 22:23
(12)(12)(19) its_valera, добрый день.
Коллеги, всех приветствую. Использовали рекомендации из этой публикации на сложной задаче переноса данных из ТиС в УТ 11. Большое спасибо автору за идею и направление движения по оптимизации выгрузки! Пока у нас решение не заработало полноценно - зависает даже при отметке единственного правила "Номенклатура" на 27168м элементе. Будем пробовать рекомендации из комментариев.

А насчет ошибки, с которой Вы столкнулись - у нас такая тоже была. Вот что надо сделать. Вместо строки:
мТаблицаПравилКонвертацииОбъектов.Выгруженные.УдалитьВсе();

Нужен такой код:
мТаблицаПравилКонвертацииОбъектов.Выгруженные.УдалитьСтроки();


Так как тип теперь стал ТаблицаЗначений.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа