Как управлять временем жизни временного хранилища в 1С 8.3 и правильно кэшировать данные тяжелых отчетов?

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

Разработчики крупных информационных систем на платформе «1С:Предприятие 8.3» часто сталкиваются с задачей оптимизации скорости открытия ресурсоемких отчетов. Естественным желанием в данном случае является кэширование предварительно рассчитанных данных. Попытки реализовать такое кэширование «в лоб» через механизмы временного хранилища (ВременноеХранилище) или параметры сеанса часто приводят к неожиданным проблемам: данные бесследно исчезают, сеансы зацикливаются, а сервер начинает испытывать повышенную нагрузку на дисковую подсистему.

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

Почему «вложенные» адреса во временном хранилище быстро исчезают?

Рассмотрим классическую архитектурную ошибку. Разработчик создает «вечное» временное хранилище (например, привязанное к уникальному идентификатору сеанса или модуля приложения) и помещает туда структуру, содержащую адреса других временных хранилищ, созданных в общем модуле. Спустя короткое время при попытке обратиться к данным по вложенным адресам платформа возвращает Неопределено.

Выясним причину такого поведения платформы. Временное хранилище управляется жестким алгоритмом сборщика мусора (garbage collector). Время жизни объекта во временном хранилище определяется вторым параметром метода ПоместитьВоВременноеХранилище():

  1. Привязка к уникальному идентификатору формы: Данные гарантированно живут до тех пор, пока открыта соответствующая форма управляемого приложения.
  2. Передача «левого» (случайно сгенерированного) УИД: В старой документации и синтакс-помощнике утверждалось, что такие данные живут до конца сеанса. На практике в современных версиях платформы 8.3 это не так. Если сборщик мусора обнаруживает адрес, который не привязан к активному контексту формы, он считает это хранилище «бесхозным» (orphaned) и очищает его при ближайшей балансировке процессов или по завершении текущего серверного вызова.
  3. Без передачи второго параметра: Хранилище живет только в течение одного серверного вызова.

Когда мы помещаем в основное («вечное») временное хранилище адреса других временных хранилищ, платформа не отслеживает эту зависимость. Сами вложенные адреса для сборщика мусора выглядят как бесхозные временные файлы и безжалостно удаляются. Надеяться на синхронизацию времени жизни вложенных адресов с основным хранилищем на уровне платформы нельзя.

Как устроено временное хранилище на уровне сервера?

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

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

Разбор критической ошибки: зацикливание при инициализации параметров сеанса

Часто для обхода проблем с временным хранилищем разработчики пытаются использовать параметры сеанса (ПараметрыСеанса). Однако при попытке рассчитать данные тяжелого отчета в фоновом задании (ФЗ) и записать результат обратно в параметр сеанса возникает бесконечная рекурсия. Проанализируем, почему это происходит.

В модуле сеанса пишется обработчик инициализации:


Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
    
    // Пытаемся запустить фоновое задание для расчета кэша
    ФоновыеЗадания.Выполнить("МойОбщийМодуль.РассчитатьДанныеДляКэша");
    
КонецПроцедуры

При запуске фонового задания платформа выполняет следующие шаги:

  1. Фоновое задание стартует в рамках нового, отдельного системного сеанса.
  2. Для этого нового сеанса платформа обязана выполнить инициализацию параметров сеанса и вызывает предопределенную процедуру УстановкаПараметровСеанса() в модуле сеанса.
  3. Процедура УстановкаПараметровСеанса() вновь запускает фоновое задание.
  4. Новое фоновое задание создает еще один сеанс, который снова вызывает инициализацию...

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

Чтобы исключить это зацикливание, в процедуре УстановкаПараметровСеанса() необходимо обязательно делать проверку на то, запущен ли текущий сеанс под фоновым заданием:


Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
    
    // Если сеанс вызван фоновым заданием - прерываем тяжелую инициализацию
    Если ПолучитьФоновоеЗадание() <> Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Здесь выполняем стандартную инициализацию для обычных сеансов пользователей
    ИнициализироватьБазовыеПараметры();
    
КонецПроцедуры

Эффективное решение №1: Использование регистров сведений для кэширования отчетов

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

Рассмотрим этот подход по шагам:

  1. Создаем непериодический регистр сведений (например, КэшТяжелыхОтчетов).
  2. В качестве измерений определяем: ИдентификаторКэша (тип Строка или УникальныйИдентификатор), НомерСтроки (тип Число). Разделение на строки позволяет избежать проблем с ограничением размера одной записи СУБД при хранении бинарных данных.
  3. В качестве ресурсов используем поля данных отчета (например, аналитику, показатели) или сериализованную строку.
  4. Регламентное или фоновое задание периодически производит расчет данных, очищает старый кэш в регистре и записывает новые данные пакетным методом.

Преимущества очевидны: данные доступны мгновенно через быстрые индексированные запросы СУБД, кэш не зависит от стабильности рабочих процессов сервера 1С (при перезапуске rphost кэш не сбрасывается) и не расходует оперативную память сеансов.

Эффективное решение №2: Кэширование в параметрах сеанса через стабильную структуру

Если данные кэша не слишком объемные, но должны быть индивидуальными для каждого пользователя и доступны на протяжении всего сеанса, можно использовать параметры сеанса со специальным типом ФиксированнаяСтруктура или ФиксированноеСоответствие.

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


// В модуле сеанса при старте создаем пустую структуру кэша
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
    Если ПараметрыСеанса.КэшПользователя = Неопределено Тогда
        ПараметрыСеанса.КэшПользователя = Новый ФиксированнаяСтруктура;
    КонецЕсли;
КонецПроцедуры

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

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

Эффективное решение №3: Общие модули с повторным использованием возвращаемых значений

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

Выясним особенности их работы в контексте кэширования тяжелых отчетов:

Для принудительного сброса (инвалидации) такого кэша разработчики часто используют изящный прием с передачей управляющего параметра (версии кэша) в качестве аргумента функции:


// Функция в общем модуле с повторным использованием значений
Функция ПолучитьДанныеОтчета(ВерсияКэша, ПараметрыОтчета) Экспорт
    
    // Если ВерсияКэша изменилась, платформа не найдет результат в кэше 
    // по старым аргументам и выполнит тяжелый расчет заново
    Данные = РассчитатьТяжелыйОтчет(ПараметрыОтчета);
    Возврат Данные;
    
КонецФункции

Резюме: что выбрать для вашего проекта?

Подводя итог анализу механизмов кэширования в 1С 8.3, можно сформулировать следующие рекомендации:

← На главную