Технология внешнего сохраненния расчетов

Довольно длительное время я сопровождал расчет бонусов в одной организации. Нюанс заключался в том, что алгоритмы и сами структуры данных для расчета постоянно менялись, порой кардинально.  Чтобы анализировать данные за период, нужно было сохранять данные расчетов, т.к. воспроизвести их было не реально. Однако возник вопрос: в каком формате сохранять, если в таблицах постоянно добавлялись и удалялись колонки, кроме ключевых?

Решено было сохранять таблицы расчетов во внешние файлы, а затем их восстанавливать при необходимости.

Технология оказалась удачной, хочу поделиться опытом.

Формальная постановка задачи

За каждый период формируется несколько таблиц, например ТЗРасчет, ТЗСтавки, ТЗТовары. В каждом из этих периодов у всех таблиц есть устойчивые ключевые колонки, но для расчетов могут добавляться и другие колонки, каждый раз разные.

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

На выходе такое решение выгодно даже для анализа текущего периода – отдельные расчеты сохранялись и затем по ним можно быстро получить сводную аналитику.

Первая попытка

Справедливости ради нужно заметить, что сначала я пошел по другому пути – создал таблицу в базе данных, рассчитанную на хранение универсальных данных.

Что-то вроде таблицы с полями: IDТаблицы, IDПоля, Значение.

Но не получилось, возникли проблемы:

  1. База слишком быстро выросла в объеме, что сказалось на размерах бэкапа.
  2. Сохранение в таблицу и получение информации работало крайне медленно.

Такая методика не годилась.

Использование внешних данных

Все таблицы расчетов решено было сохранить в каталог на сервере в виде отдельных файлов.

Т.к. расчеты делались за половинки месяцев, то для каждой половины месяца создавались подкаталоги с названиями вида YYYYMM01 и YYYYMM16. Можно было обойтись без деления на подкаталоги, просто указывать адрес для файла в его имени, но подкаталоги используются потому, что Windows плохо работает с каталогами с огромным количеством файлов.

В имя файла также добавлялся код подразделения, по которому производился расчет, т.к. каждый расчет шел по отдельному подразделению.

Вот пример файлов в каталоге:

В каждый файл сохраняется таблица через функцию ЗначениеВСтрокуВнутр, обратно разворачивается через ЗначениеИзСтрокиВнутр.

Статистика

Один расчет по 40 подразделениям за полмесяца занимает 150 Мб:

Все расчеты за полгода занимают 7 Гб:

Вот как выглядит каталог с расчетами Sequoia View с расчетами за полгода:

Бэкапить такие файлы удобно, не нужно хранить лишнюю информацию в базе.

Образцы кода

В разделе собран полезный для организации таких хранилищ код.

Библиотека для работа с Расчетами:

Функция ПолучитьКаталогСохраненияРасчетов(Дата) Экспорт

       БазовыйКаталог = СокрЛП(Справочники.САПКонстантыЛокальные.КаталогСохраненияРасчетов.Значение);

       Если БазовыйКаталог = "" Тогда

             Возврат БазовыйКаталог;

       КонецЕсли;

      

       ПодКаталог = Формат(Дата, "ДФ=yyyyMMdd");

      

       Возврат САП.СоздатьКаталогЛога(БазовыйКаталог, ПодКаталог, ложь);

КонецФункции

 

Функция ПолучитьИмяФайлаРасчетов(Магазин, Интервал, ИмяТаблицы) Экспорт

       ИмяФайла = СокрЛП(Магазин.Код) + "_" + Формат(Интервал.ДатаНачала, "ДФ=dd_MM_yyyy") + "_" + Формат(Интервал.НомерПериода, "ЧН=0; ЧГ=") + "_" + ИмяТаблицы+".txt";

       Возврат ИмяФайла;

КонецФункции

 

Функция ПолучитьИмяФайлаРасчетовПолное(Магазин, Интервал, ИмяТаблицы) Экспорт

       Каталог = ПолучитьКаталогСохраненияРасчетов(Интервал.ДатаНачала);

       Если НЕ ЗначениеЗаполнено(Каталог) Тогда

             Возврат Неопределено;

       КонецЕсли;

       ИмяФайлаРасчетов = ПолучитьИмяФайлаРасчетов(Магазин, Интервал, ИмяТаблицы);

       ИмяФайлаПолное = Каталог + "\" + ИмяФайлаРасчетов;

      

       Возврат ИмяФайлаПолное;

      

КонецФункции

 

 

Функция СохранитьРасчет(Магазин, Интервал, ИмяТаблицы, Таблица) Экспорт

       ИмяФайла = ПолучитьИмяФайлаРасчетовПолное(Магазин, Интервал, ИмяТаблицы);

       Если ИмяФайла = Неопределено Тогда

             Возврат Неопределено;

       КонецЕсли;

      

       Попытка

             Значение = ЗначениеВСтрокуВнутр(Таблица);

             Т = Новый ТекстовыйДокумент();

             Т.УстановитьТекст(Значение);

             Т.Записать(ИмяФайла, "UTF-8");

            

             Возврат ИмяФайла;

       Исключение

             ОписаниеОшибки = ОписаниеОшибки();

            

             //Если Найти(";ТЗСебестоимость;", ";" + ИмяТаблицы + ";") <> 0 Тогда

             //     Сообщить("Не смогли преобразовать таблицу для сохранения в файл: " + ИмяФайла +  "  " + ОписаниеОшибки, СтатусСообщения.Важное);

             //Иначе

             ВызватьИсключение "Не смогли преобразовать таблицу для сохранения в файл: " + ИмяФайла +  "  " + ОписаниеОшибки;

             //КонецЕсли;

       КонецПопытки;

      

КонецФункции

 

Функция ВосстановитьРасчет(Магазин, Интервал, ИмяТаблицы) Экспорт

       ИмяФайла = ПолучитьИмяФайлаРасчетовПолное(Магазин, Интервал, ИмяТаблицы);

       Если ИмяФайла = Неопределено Тогда

             Возврат Неопределено;

       КонецЕсли;

      

       Попытка

              Т = Новый ТекстовыйДокумент();

             Т.Прочитать(ИмяФайла, "UTF-8");

             Текст = Т.ПолучитьТекст();

             Значение = ЗначениеИзСтрокиВнутр(Текст);

       Исключение

             Возврат Неопределено;

       КонецПопытки;

       Возврат Значение;

      

КонецФункции

 

Пример использования в расчетах:

       Для Каждого Магазин из ВыбМагазины Цикл

             Для Каждого Интервал Из ВыбИнтервалы Цикл

                    #Если Клиент Тогда

                           Состояние("Бонусы. Магазин: " + Магазин + "Интервал: " + Интервал);

                    #КонецЕсли

                   

                    ТЗСтавки = ВосстановитьРасчет(Магазин, Интервал, "ТЗСтавки");

                    Если ТЗСтавки = Неопределено Тогда

                           Сообщить("Не найден расчет ТЗСтавки: " + " Магазин: " + Магазин + "Интервал: " + Интервал);

                           Продолжить;

                    КонецЕсли;

                   

                   

                    Для Каждого Стр ИЗ ТЗСтавки Цикл

                    КонецЦикла;

                   

                   

                    ТЗПозиции = ВосстановитьРасчет(Магазин, Интервал, "ТЗПозиции");

                    Если ТЗПозиции = Неопределено Тогда

                           Сообщить("Не найден расчет ТЗПозиции: " + " Магазин: " + Магазин + "Интервал: " + Интервал);

                           Продолжить;

                    КонецЕсли;

                   

                   

                   

                    Для Каждого Стр ИЗ ТЗПозиции Цикл

                          

                    КонецЦикла;

                   

 

                   

             КонецЦикла;

            

       КонецЦикла;