Как оптимизировать запись 1 миллиона записей в регистр сведений за 3 минуты в 1С 8.3?

Программист 1С v8.3 (Управляемые формы)
← На главную

При работе с большими объемами данных в системах 1С:Предприятие часто возникает задача быстрой записи миллионов записей в регистры сведений. Изначально заявленная скорость в 3 минуты для записи 1 миллиона записей в регистр сведений может показаться как приемлемой, так и медленной, в зависимости от контекста — используемого оборудования, частоты выполнения операции и, что немаловажно, архитектуры самого регистра. Если такая операция выполняется редко, возможно, текущая скорость достаточна. Однако, если она выполняется регулярно или требуется более высокая производительность, необходимо рассмотреть различные методы оптимизации.

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

Разбираем текущую реализацию и общие рекомендации

Автор темы описывает, что запись осуществляется через объект НаборЗаписей. Данные сначала формируются в Табличной Части (ТЧ), затем в цикле из ТЧ добавляются в набор записей регистра, а затем весь этот набор в 1 миллион записей записывается однократным вызовом метода Записать(). В цикле заполнения набора также присутствуют "несколько проверок и условий", которые меняются от настроек. В регистре при этом используется 8 измерений, 10 ресурсов и 3 реквизита. Эти детали крайне важны для анализа и дальнейшей оптимизации.

Прежде чем углубляться в конкретные технические решения, давайте отметим несколько общих, но важных моментов:

  1. Замеры производительности: Всегда выполняйте замеры производительности до и после каждой оптимизации. Это позволит объективно оценить эффективность внесенных изменений. Используйте для этого встроенные в 1С инструменты замера или простой хронометраж.
  2. Оценка контекста: Важно понимать, насколько часто будет выполняться данная операция. Если это единоразовый перенос данных или редкая процедура, возможно, 3 минуты — это приемлемо. Если же операция выполняется много раз в день, тогда стоит приложить значительные усилия для оптимизации.
  3. Ограничение ТЧ: В контексте Табличных Частей (ТЧ) документов 1С существует визуальное ограничение отображения строк до 99 999. Хотя платформа позволяет хранить больше строк, при попытке сохранить документ с таким огромным количеством строк напрямую могут возникнуть проблемы. Для миллиона строк целесообразнее работать с временными таблицами, Таблицами Значений (ТЗ) или выгружать данные в регистры напрямую, минуя прямое сохранение в ТЧ документа.

Метод 1: Оптимизация записи через НаборЗаписей.Загрузить()

Важным шагом в оптимизации массовой записи данных является правильное использование объекта НаборЗаписей. Текущий подход, когда каждая запись из ТЧ добавляется в набор в цикле, а затем набор записывается, является стандартным, но для очень больших объемов может быть не самым эффективным. Вместо поэлементного добавления, мы можем использовать метод НаборЗаписей.Загрузить(), который позволяет загрузить данные из уже подготовленной структуры, такой как ТаблицаЗначений или результат запроса, напрямую в набор записей.

Предположим, что наши данные, которые мы хотим записать в регистр, уже находятся в ТаблицеЗначений (ТЗ). Если они в Табличной Части (ТЧ), как в случае автора, мы можем сначала выгрузить их в ТЗ. Этот метод обычно оказывается значительно быстрее, так как он оптимизирован для массовой вставки данных на уровне платформы.

Разберем шаги:

  1. Выгрузка данных в ТаблицаЗначений: Если данные находятся в ТЧ, нам сначала нужно их выгрузить. Это можно сделать следующим образом:

    МояТаблицаЗначений = МояТЧ.Выгрузить();
  2. Соответствие колонок: Автор упоминает, что "названия реквизитов не сходятся многие, т.е. в ТЗ свои какие-то колонки". Это распространенная ситуация. Для успешной загрузки через НаборЗаписей.Загрузить() имена колонок в источнике данных (в нашей ТЗ) должны совпадать с именами полей регистра. Если они не совпадают, мы можем переименовать колонки в ТЗ перед загрузкой:

    
    // Предположим, у нас есть ТЗ с колонками "Колонка1", "Колонка2"
    // и регистр с измерениями "Измерение1", "Измерение2"
            
    // Переименовываем колонки в ТЗ
    Если МояТаблицаЗначений.Колонки.Найти("Колонка1") <> Неопределено Тогда
        МояТаблицаЗначений.Колонки.Найти("Колонка1").Имя = "Измерение1";
    КонецЕсли;
    Если МояТаблицаЗначений.Колонки.Найти("Колонка2") <> Неопределено Тогда
        МояТаблицаЗначений.Колонки.Найти("Колонка2").Имя = "Измерение2";
    КонецЕсли;
    // И так далее для всех необходимых полей регистра (измерений, ресурсов, реквизитов)
    

    Важно, чтобы все измерения, ресурсы и реквизиты, которые вы хотите заполнить, имели соответствующие колонки в ТЗ с правильными именами.

  3. Загрузка данных в НаборЗаписей: После подготовки ТЗ, мы можем загрузить ее в набор записей регистра:

    
    НаборЗаписейРегистра = РегистрыСведений.МойРегистрСведений.СоздатьНаборЗаписей();
    НаборЗаписейРегистра.Загрузить(МояТаблицаЗначений);
            
    // Очистка регистра перед записью, если требуется
    // НаборЗаписейРегистра.Очистить(); 
            
    // Запись набора. Внимание: в новых версиях платформы доступны режимы замещения
    НаборЗаписейРегистра.Записать();
    
  4. Новые режимы замещения (начиная с 8.3.25): В последних версиях платформы 1С:Предприятие (начиная с 8.3.25) для независимых регистров сведений появились дополнительные режимы при записи наборов записей, такие как РежимЗамещения.Слияние и РежимЗамещения.Удаление. Эти режимы могут быть полезны для более гибкого управления данными при массовой загрузке. При их использовании рекомендуется не устанавливать отбор для набора записей, что также может улучшить производительность.

    
    // Пример использования нового режима замещения
    // Если РегистрСведений.МойРегистрСведений.Метаданные().Независимый Тогда
    //    НаборЗаписейРегистра.Записать(Истина, РежимЗамещения.Слияние);
    // КонецЕсли;
    

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

Преимущества: Метод Загрузить() позволяет платформе выполнить массовую вставку данных более эффективно, чем создание каждой записи по отдельности в цикле. Это может значительно сократить время выполнения операции.

Метод 2: Архитектурная оптимизация регистра сведений

Один из самых критичных факторов, влияющих на производительность записи и чтения данных, является архитектура самого регистра сведений. Автор указывает, что в его регистре 8 измерений, 10 ресурсов и 3 реквизита. Такая структура многими экспертами 1С считается "ужасной архитектурой" или "толстым регистром". Давайте разберем, почему это так и что с этим делать.

Проблема "толстых" регистров

В 1С, измерения регистра сведений формируют его первичный ключ и по ним строится кластерный индекс. Кластерный индекс определяет физический порядок хранения данных в базе данных. Когда у вас 8 измерений, это означает, что кластерный индекс состоит из 8 полей (или 9, если регистр периодический). Такой "кучерявый" индекс имеет следующие негативные последствия:

  1. Размер индекса: Чем больше полей в индексе, тем больше его размер. При записи новых данных требуется актуализировать этот большой индекс, что замедляет процесс вставки.

  2. Производительность записи: Каждая новая запись должна быть вставлена в правильное место согласно индексу. Для сложного индекса СУБД тратит больше времени на поиск этого места и перебалансировку индекса. Это напрямую влияет на скорость записи в регистр.

  3. Производительность запросов: Хотя индексы предназначены для ускорения чтения, слишком сложный кластерный индекс может замедлить запросы, которые не используют все поля индекса в нужном порядке. Кроме того, большие индексы требуют больше дискового пространства и оперативной памяти для своей работы.

  4. Измерения vs. Ресурсы vs. Реквизиты: Важно понимать разницу. Измерения определяют уникальность записи и разрезы анализа. Ресурсы — это количественные или суммовые показатели. Реквизиты — это дополнительные атрибуты записи, не влияющие на ее уникальность. В "толстом" регистре часто бывает, что некоторые измерения на самом деле могли бы быть реквизитами, если они не являются частью ключа уникальности или не используются для основных отборов.

Решение: Деление регистра на несколько более компактных

Основная рекомендация — это разбивать "толстые" регистры на несколько более компактных. Каждый новый регистр должен отвечать за свою, более узкую задачу и иметь минимально необходимое количество измерений (обычно 2-4).

Проанализируйте ваш регистр с 8 измерениями. Задайте себе вопросы:

  1. Все ли 8 измерений необходимы для определения уникальности каждой записи?

  2. Все ли измерения используются для фильтрации данных в запросах с одинаковой частотой?

  3. Можно ли некоторые измерения перевести в реквизиты, если они не участвуют в уникальности записи и редко используются для отборов?

  4. Можно ли разделить функционал регистра? Например, если регистр хранит данные о параметрах номенклатуры для покупателей (как в одном из примеров в теме: 10 000 номенклатур, 100 покупателей = 1 млн записей), возможно, стоит иметь один регистр для общих параметров номенклатуры, и другой — для параметров номенклатуры, зависящих от покупателя. Или же, если есть различные группы ресурсов, относящихся к разным аспектам, их можно разнести по разным регистрам.

Пример: Если у вас есть измерения Номенклатура, Характеристика, Серия, Склад, Проект, Контрагент, Договор, Сделка, то это очень много. Вероятно, некоторые из них могут быть отнесены к разным сущностям и храниться в отдельных регистрах.

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

Современные подходы 1С к проектированию регистров сведений склоняются к созданию компактных регистров с 2-4 измерениями. Это значительно повышает производительность как записи, так и чтения, и делает систему более масштабируемой.

Метод 3: Использование многопоточности и фоновых заданий

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

Как работают фоновые задания?

Фоновые задания позволяют выполнять код на сервере 1С асинхронно, не блокируя пользовательский интерфейс. Это создает ощущение параллельной работы. Однако важно понимать, что "истинная" многопоточность в смысле одновременного выполнения нескольких потоков внутри одного процесса сервера 1С не всегда достигается (это зависит от СУБД и настроек сервера 1С). Тем не менее, это позволяет значительно разгрузить пользователя и потенциально ускорить общую обработку, если часть работы может быть разбита на независимые блоки.

Если ваша операция записи может быть логически разделена на несколько независимых частей (например, записи по разным измерениям или диапазонам), вы можете запускать несколько фоновых заданий, каждое из которых будет записывать свою часть данных. Это может сократить общее время выполнения операции.

Пример (концептуальный):


// Предположим, у нас есть большая ТаблицаЗначений, которую нужно записать
// Мы можем разбить ее на части
МассивЧастейТЗ = РазбитьТЗНаЧасти(МояБольшаяТЗ, КоличествоЧастей);

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

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

Метод 4: Оптимизация кода цикла и транзакций

В цикле заполнения набора записей, по словам автора, "есть несколько проверок и условий". Это может быть значительным фактором замедления, особенно при миллионе итераций. Мы проанализируем эти аспекты.

Минимизация проверок и условий в цикле

Каждая проверка и условие в цикле добавляет накладные расходы. При миллионе итераций даже небольшая задержка на каждой проверке накапливается в минуты. Мы рассмотрим следующие подходы:

  1. Вынесение условий за цикл: Если какие-то условия или настройки меняются от внешних параметров и не зависят от каждой конкретной строки данных, вынесите их расчет за пределы цикла.

  2. Предварительная подготовка данных: Если проверки касаются значений, которые можно рассчитать или подготовить заранее (например, получить справочные данные, необходимые для проверок, в начале процедуры с помощью запросов), сделайте это до цикла. Затем в цикле просто обращайтесь к уже подготовленным данным (например, к хеш-таблице или соответствию).

  3. Оптимизация самого кода проверок: Убедитесь, что код внутри проверок максимально эффективен. Избегайте лишних обращений к базе данных или сложных вычислений.

Управление транзакциями и блокировками

При массовых модификациях данных очень важно правильно управлять транзакциями. Мы всегда должны стремиться:

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

  2. Избегать блокировок: В 1С, при работе с регистрами, платформа автоматически управляет блокировками. Однако при одновременной работе нескольких пользователей или фоновых заданий с одними и теми же областями данных могут возникать проблемы. Рассмотрите возможность использования режима блокировки "Управляемые" для регистров сведений, если это позволяет архитектура и функционал, чтобы иметь более тонкий контроль над блокировками.

  3. Проблема эскалации блокировок: В некоторых СУБД при большом количестве блокировок строк может происходить "эскалация блокировок", когда СУБД блокирует всю таблицу вместо отдельных строк для экономии ресурсов. Это может быть полезно для одной большой транзакции, но крайне негативно сказывается на параллельной работе.

В случае массовой записи 1 миллиона записей через НаборЗаписей.Записать(), платформа, как правило, старается выполнить операцию в рамках одной транзакции и оптимизирует ее на уровне СУБД, что эффективно для одной большой операции.

Резюме и заключение

Оптимизация записи 1 миллиона записей в регистр сведений — это комплексная задача, требующая внимания как к коду, так и к архитектуре данных. Мы рассмотрели несколько ключевых направлений:

  1. Использование НаборЗаписей.Загрузить(): Это, вероятно, самый простой и быстрый способ улучшить производительность записи, если вы сейчас добавляете записи в набор в цикле. Не забудьте про подготовку ТаблицыЗначений и переименование колонок, а также про новые режимы замещения.

  2. Архитектурная реструктуризация регистра: Самое радикальное, но часто и самое эффективное решение. Если ваш регистр имеет 8 измерений, серьезно задумайтесь о его делении на несколько более компактных регистров. Это улучшит производительность не только записи, но и чтения, а также общую масштабируемость системы.

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

  4. Оптимизация кода и транзакций: Минимизация проверок в циклах и эффективное управление транзакциями всегда будут способствовать улучшению производительности.

Начните с замеров, затем попробуйте метод НаборЗаписей.Загрузить(). Если этого окажется недостаточно, или если проблема производительности носит системный характер, серьезно рассмотрите архитектурные изменения регистра. Именно в правильной архитектуре часто кроется ключ к высокопроизводительным решениям в 1С.

← На главную