Как быстро загрузить 5000 заказов в час в 1С из JSON, не используя поиск по ссылкам или ГУИД?

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

На собеседовании нам могут задать непростые вопросы, которые ставят в тупик даже опытных специалистов. Одним из таких вопросов является задача эффективной загрузки большого объема данных в 1С, например, 5000 заказов в час из внешнего источника в формате JSON, с жестким ограничением: нельзя использовать прямой поиск по ссылкам или по ГУИД. Последний тоже считается поиском, и сочетание "поиск + запись" признается слишком медленным. Давайте разберем, как мы можем подойти к решению этой высоконагруженной задачи, учитывая все ограничения.

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

Понимание проблемы: Почему поиск все равно необходим и как его можно переосмыслить

Мы должны понимать, что внутренне СУБД всегда выполняет операцию поиска (например, index seek), когда мы пытаемся обновить или удалить запись. И 1С не является исключением. Когда мы говорим «без поиска», подразумевается, что мы не хотим выполнять явные, потенциально долгие операции поиска в коде 1С для каждого объекта по отдельности.

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


// Предполагаем, что ГуидИзФайла - это строка с уникальным идентификатором из JSON
ВнешнийГУИД = Новый УникальныйИдентификатор(ГуидИзФайла);
СсылкаНаОбъект = Справочники.МойСправочник.ПолучитьСсылку(ВнешнийГУИД);

Если СсылкаНаОбъект.Пустая() Тогда
    // Объект не найден, это новый заказ
    НовыйОбъект = Справочники.МойСправочник.СоздатьЭлемент();
    НовыйОбъект.УникальныйИдентификатор = ВнешнийГУИД;
    // ... Заполняем остальные поля
    НовыйОбъект.Записать();
Иначе
    // Объект найден, это существующий заказ
    СуществующийОбъект = СсылкаНаОбъект.ПолучитьОбъект();
    // ... Обновляем поля
    СуществующийОбъект.Записать();
КонецЕсли;

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

Использование механизма соответствий (кеширование ссылок) для сокращения повторных поисков

Один из ключевых подходов для ускорения работы с существующими данными — это кеширование. Мы можем создать и поддерживать в памяти (или на диске для персистентности) соответствие между уникальным идентификатором внешней системы (например, GUID заказа) и ссылкой на соответствующий объект 1С. Это позволяет избежать повторного поиска в базе данных для часто обновляемых объектов.

  1. Создание и поддержание кеша: Мы будем хранить в памяти (например, в объекте Соответствие) пары GUID -> Ссылка1С. При первом обращении к заказу мы ищем его в 1С (возможно, с помощью ПолучитьСсылку()), а затем добавляем найденную ссылку в наш кеш.

  2. Актуализация кеша: Кеш может расти очень быстро. Мы должны регулярно его обновлять, например, загружая пары "ссылка - внешний идентификатор" из базы данных 1С для заказов за определенный период (скажем, последние Х дней). Вероятность обновления старых заказов значительно ниже, поэтому нет смысла держать их все в оперативном кеше. Если заказ не найден в кеше "за последние Х дней", то мы можем выполнить полный поиск по УникальномуИдентификатору. Также, мы можем использовать запросы к 1С для массовой загрузки идентификаторов и ссылок:

    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    ЗаказыКлиента.Ссылка,
        |    ЗаказыКлиента.УникальныйИдентификатор
        |ИЗ
        |    Документ.ЗаказКлиента КАК ЗаказыКлиента
        |ГДЕ
        |    ЗаказыКлиента.Дата >= &ДатаНачала";
    
    Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ТекущаяДата() - 30 * День)); // За последние 30 дней
    
    РезультатЗапроса = Запрос.Выполнить().Выгрузить();
            
    КешСсылок = Новый Соответствие;
    Для Каждого СтрокаТаблицы Из РезультатЗапроса Цикл
        КешСсылок.Вставить(СтрокаТаблицы.УникальныйИдентификатор, СтрокаТаблицы.Ссылка);
    КонецЦикла;
    
  3. Персистентность между сеансами: Если объем кеша велик или требуется его сохранение при перезапуске сервера 1С, мы можем использовать временные таблицы, хранилища значений или даже файлы на диске. Это позволит восстанавливать кеш быстрее, чем полностью перечитывать базу.

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

Пакетная обработка и асинхронная запись через фоновые задания

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

  1. Накопление данных: Мы собираем входящие JSON-объекты или их разобранные структуры в какой-либо временный буфер. Это может быть массив структур в памяти или, что более надежно для больших объемов, временный регистр сведений.

  2. Фоновые задания: Накопленные данные порциями (например, по 100-500 заказов) передаются в фоновые задания 1С. Эти задания будут осуществлять запись в базу данных в несколько параллельных потоков. Это позволяет разгрузить основной процесс получения данных и равномерно распределить нагрузку на сервер 1С и СУБД. Фоновые задания могут работать с собственной транзакцией и логикой записи.

Рассмотрим пример кода:


// В основном процессе, отвечающем за прием данных (например, HTTP-сервис)
Перем НакопительЗаказов Экспорт; // Может быть в глобальном контексте или объекте, доступном для HTTP-сервиса

Если НакопительЗаказов = Неопределено Тогда
    НакопительЗаказов = Новый Массив;
КонецЕсли;

// Получили данные нового заказа из JSON
СтруктураДанныхЗаказа = ПрочитатьJSONИРазобратьДанныеЗаказа(СтрокаJSON); 
НакопительЗаказов.Добавить(СтруктураДанныхЗаказа);

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

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

    Для Каждого ДанныеЗаказа Из ПорцияЗаказов Цикл
        ВнешнийГУИД = ДанныеЗаказа.УникальныйИдентификатор;
        СсылкаНаЗаказ = КешСсылокЗаказов.Получить(ВнешнийГУИД);

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

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

Использование брокера сообщений для масштабируемой интеграции

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

  1. Внешняя система отправляет данные в брокер: Вместо того чтобы напрямую вызывать 1С, внешняя система (например, сайт) отправляет JSON-сообщения с заказами в брокер сообщений. Это обеспечивает высокую скорость публикации данных и гарантированную доставку.

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

  3. Обработка и запись в 1С: Полученные из брокера данные 1С обрабатывает, используя кеширование ссылок (как описано выше), и формирует параллельные потоки записи через фоновые задания. Такая архитектура разделяет системы, сглаживает пиковые нагрузки, повышает отказоустойчивость и масштабируемость.

Новые режимы записи регистров сведений для пакетной обработки

Платформа 1С:Предприятие, начиная с версии 8.3.25, а затем и в 8.3.26 и 8.3.27, представила новые режимы записи для независимых регистров сведений, которые значительно повышают производительность при работе с большими объемами данных. Это очень мощный инструмент для решения нашей задачи.

  1. Режим "Слияние" (доступен с 8.3.25 для независимых РС): Этот режим позволяет выполнить пакетное добавление или обновление записей в регистре. Если запись с таким набором измерений уже существует, она будет обновлена; если нет – добавлена. Это идеальный вариант для первоначального "сброса" потока входящих данных в "сырой" регистр сведений, откуда они потом будут обрабатываться. Если заказы, прежде чем стать документами, сначала записываются в регистр сведений (например, "ВходящиеЗаказыJSON"), этот режим позволяет очень быстро загрузить большой объем данных без явного поэлементного поиска.

  2. Режим "Удаление" (с 8.3.26) и "Обновление" (с 8.3.27): Эти режимы предоставляют еще больше гибкости для пакетного управления записями, позволяя массово удалять или обновлять записи по заданным критериям. Они могут быть использованы на последующих этапах обработки данных из "сырого" регистра.

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

Пример использования режима "Слияние":


// Создаем набор записей для независимого регистра сведений
НаборЗаписей = РегистрыСведений.ВходящиеЗаказыJSON.СоздатьНаборЗаписей();

// ... в цикле по полученным из JSON данным
Для Каждого ДанныеЗаказа Из ПорцияЗаказов Цикл
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.УникальныйИдентификаторЗаказа = Новый УникальныйИдентификатор(ДанныеЗаказа.ГУИД);
    НоваяЗапись.ДатаЗаказа = ДанныеЗаказа.Дата;
    НоваяЗапись.СуммаЗаказа = ДанныеЗаказа.Сумма;
    НоваяЗапись.ДанныеJSON = ДанныеЗаказа.ИсходныйJSON; // Сохраняем исходный JSON целиком или его часть
    // ... Другие измерения и ресурсы регистра
КонецЦикла;

// Записываем весь набор в регистр сведений в режиме "Слияние"
// Это очень быстрая операция на уровне СУБД
НаборЗаписей.Записать(Истина, РежимЗаписиРегистраСведений.Слияние); 

Для случаев, когда нам нужно только добавить записи в пустой регистр (например, первичная загрузка), и мы уверены, что дубликатов нет, мы можем использовать параметр РежимЗамещения со значением Ложь в методе Записать() набора записей. Это позволяет вставлять записи без проверки на существование, что является самой быстрой операцией.

Оптимизация структуры данных и прямые запросы к СУБД (исключительный случай)

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

  1. Изменение структуры хранения данных: Если архитектура системы позволяет, мы можем рассмотреть изменение структуры документов и регистров таким образом, чтобы часть данных заказа хранилась не непосредственно в документе, а в связанных регистрах сведений. Это может облегчить их параллельную запись и обновление, так как данные будут сегментированы.

  2. Прямые запросы к СУБД: В крайне редких и высоконагруженных сценариях, при наличии соответствующего разрешения от 1С и глубокого понимания архитектуры базы данных, можно рассмотреть возможность использования прямых INSERT/UPDATE операций через SQL. Однако это является нестандартной практикой и несет в себе значительные риски нарушения целостности данных, проблем с поддержкой и обновлениями конфигурации. Тем не менее, это перекладывает всю логику проверки и записи непосредственно на СУБД, что в некоторых случаях может быть быстрее, чем обработка в 1С. Этот метод должен использоваться с крайней осторожностью и только после исчерпания всех стандартных возможностей платформы.

Общие принципы для высоконагруженных систем

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

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

  2. Горизонтальное масштабирование: Использование нескольких серверов 1С или рабочих процессов для параллельной обработки данных. Это распределяет нагрузку и увеличивает общую пропускную способность.

  3. Мониторинг производительности: Постоянный мониторинг производительности базы данных, сервера 1С и процессов интеграции для выявления узких мест. Без глубокого анализа нельзя эффективно оптимизировать систему.

  4. Использование индексов: Мы должны убедиться, что все поля, используемые для поиска, связи или в качестве ключевых измерений в регистрах, имеют соответствующие индексы в базе данных 1С. Это основа быстрой работы СУБД.

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

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

← На главную