При проектировании высоконагруженных систем на платформе 1С:Предприятие разработчики часто сталкиваются с дилеммой: где и как хранить большие объемы связанных данных. Типичный пример — история рассылки уведомлений, где есть шапка («Уведомление» с общими реквизитами) и строки («Получатели» с индивидуальными статусами доставки). Когда счет записей идет на миллионы, выбор неверной архитектуры хранения может привести к катастрофическому падению производительности, транзакционным блокировкам и раздуванию базы данных.
В этой статье мы подробно разберем три основных подхода к решению этой задачи, проанализируем внутренние механизмы платформы 1С и СУБД, а также выясним, какое решение будет наиболее отказоустойчивым и быстрым на больших объемах данных.
На первый взгляд, это самое очевидное и простое решение. Мы создаем справочник СправочникСсылка.Уведомления, добавляем в него реквизиты шапки, а список получателей храним в табличной части Адресаты. Давайте проанализируем, к каким последствиям это приведет при работе с миллионами строк.
Проблема избыточного кэширования и чтения данных. Главная особенность объектной модели 1С заключается в том, что платформа всегда оперирует объектом целиком. Если нам потребуется прочитать хотя бы один реквизит шапки (например, дату создания уведомления) через обращение к ссылке Ссылка.ДатаСоздания, или при вызове метода ПолучитьОбъект(), платформа считает из базы данных СУБД весь элемент справочника целиком вместе со всей его табличной частью.
Если в вашей табличной части содержится 200–300 строк, то при обычном обходе выборки в цикле или при проверке условий в фоновом процессе сервер 1С будет гонять гигабайты лишних данных из СУБД в оперативную память сервера приложений и на клиент. Это вызовет колоссальные просадки производительности и перерасход памяти.
Транзакционные блокировки. При записи справочника с табличной частью СУБД блокирует всю запись элемента и все связанные строки этой табличной части. Если в системе работают параллельные потоки (например, несколько фоновых заданий рассылают СМС и пытаются параллельно записать статус доставки разным получателям в рамках одного уведомления), они неизбежно начнут конфликтовать между собой. Запись изменения по одному сотруднику заблокирует возможность записи для другого сотрудника, что приведет к ошибкам превышения времени ожидания блокировки.
Ограничения СУБД. Табличная часть физически хранится в отдельной таблице СУБД с жестким кластерным индексом Ссылка + НомерСтроки. Мы никак не можем повлиять на этот индекс. Если нам потребуется быстро находить информацию в разрезе сотрудников (например, построить отчет «Все уведомления по сотруднику Иванову за прошлый год»), СУБД будет вынуждена делать полное сканирование (Table Scan) огромной таблицы ТЧ, что приведет к зависанию отчетов.
Рассмотрим более продвинутый и профессиональный подход. Шапку уведомления мы оставляем в «легком» справочнике СправочникСсылка.Уведомления (в нем нет табличных частей, только базовые реквизиты). А список сотрудников и статусов отправки переносим в независимый регистр сведений РегистрСведений.АдресатыУведомлений.
Связь между ними организуется через измерение регистра с типом СправочникСсылка.Уведомления. Давайте разберем по шагам, какие преимущества дает эта схема:
Сотрудник + Уведомление, то поиск всех писем по конкретному физическому лицу среди сотен миллионов записей СУБД выполнит за миллисекунды (операция Index Seek).Записать() с установленным отбором, не затрагивая сами объекты справочника. Это минимизирует нагрузку на СУБД.Рассмотрим пример кода для записи статуса отправки в такой регистр сведений:
// Пример записи статуса отправки в независимый регистр сведений
НаборЗаписей = РегистрыСведений.АдресатыУведомлений.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Уведомление.Установить(СсылкаНаУведомление);
НаборЗаписей.Отбор.Сотрудник.Установить(ТекущийСотрудник);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Уведомление = СсылкаНаУведомление;
НоваяЗапись.Сотрудник = ТекущийСотрудник;
НоваяЗапись.СтатусОтправки = Перечисления.СтатусыОтправки.Доставлено;
НоваяЗапись.ДатаОтправки = ТекущаяДата();
НаборЗаписей.Записать(Истина); // Записываем только конкретную строку без блокировки всего объекта
Еще одна интересная альтернатива — это связка «Справочник-родитель + Подчиненный справочник». В данном случае шапка уведомления — это элемент родительского справочника, а каждая строка получателя — это элемент подчиненного справочника СправочникСсылка.АдресатыУведомлений, у которого стандартный реквизит Владелец ссылается на родительское уведомление.
Этот вариант очень похож по своим плюсам на вариант с регистром сведений:
GUID), что позволяет легко ссылаться на конкретную строку отправки из других документов или регистров системы (например, привязать к строке лог ошибок отправки СМС).Однако у этого подхода есть и минусы по сравнению с регистром сведений. Справочник — это более тяжелый объект для платформы 1С. Создание и запись элемента справочника требует больше ресурсов СУБД (проверка уникальности кода, заполнение стандартных реквизитов, запись в системные таблицы табличных частей, даже если они пустые). На объемах в десятки миллионов записей регистр сведений покажет себя быстрее и компактнее с точки зрения занимаемого места на диске.
Давайте сопоставим все три архитектурных решения в виде итоговой таблицы для наглядности:
| Критерий сравнения | Справочник с ТЧ | Справочник + Регистр сведений | Подчиненный справочник |
|---|---|---|---|
| Скорость чтения шапки | Низкая (читается вся ТЧ) | Высокая (ТЧ отсутствует) | Высокая (ТЧ отсутствует) |
| Блокировки при записи строк | Высокие (блокируется весь объект) | Минимальные (запись по набору) | Минимальные (запись элемента) |
| Возможность ссылки на строку | Нет (у строк ТЧ нет GUID) | Нет (нужен искусственный ключ) | Да (полноценная ссылка) |
| Удобство очистки истории | Очень низкое (нужна перезапись объектов) | Высокое (удаление наборов записей) | Среднее (удаление объектов) |
| Размер базы данных в СУБД | Средний | Минимальный (оптимальные таблицы) | Максимальный (избыточные метаданные) |
Подводя итог нашему анализу, мы можем дать следующие рекомендации по проектированию систем под высокие нагрузки:
Если в вашей задаче предполагаются миллионные объемы данных, то от использования классических табличных частей в справочниках или документах следует категорически отказаться. Это убережет вашу систему от проблем с кэшированием памяти и взаимными транзакционными блокировками.
Для хранения истории событий, уведомлений и логов идеальным решением является архитектурный шаблон «Легкий справочник для шапки + Независимый регистр сведений для строк». Он обеспечивает максимальную скорость работы СУБД, гибкое индексирование по любым измерениям и простую процедуру архивации или удаления устаревших записей.
Используйте подчиненный справочник вместо регистра сведений только в том случае, если на конкретную строку (например, факт отправки конкретного письма конкретному человеку) вам обязательно нужно ссылаться из других мест конфигурации с помощью стандартного ссылочного типа данных.