Разработчики крупных информационных систем на платформе «1С:Предприятие 8.3» часто сталкиваются с задачей оптимизации скорости открытия ресурсоемких отчетов. Естественным желанием в данном случае является кэширование предварительно рассчитанных данных. Попытки реализовать такое кэширование «в лоб» через механизмы временного хранилища (ВременноеХранилище) или параметры сеанса часто приводят к неожиданным проблемам: данные бесследно исчезают, сеансы зацикливаются, а сервер начинает испытывать повышенную нагрузку на дисковую подсистему.
В этой статье мы подробно разберем, как устроено временное хранилище «под капотом», почему разрушаются вложенные адреса, как избежать рекурсивного зацикливания параметров сеанса в фоновых заданиях и какие архитектурные альтернативы кэширования наиболее эффективны в реальной практике.
Рассмотрим классическую архитектурную ошибку. Разработчик создает «вечное» временное хранилище (например, привязанное к уникальному идентификатору сеанса или модуля приложения) и помещает туда структуру, содержащую адреса других временных хранилищ, созданных в общем модуле. Спустя короткое время при попытке обратиться к данным по вложенным адресам платформа возвращает Неопределено.
Выясним причину такого поведения платформы. Временное хранилище управляется жестким алгоритмом сборщика мусора (garbage collector). Время жизни объекта во временном хранилище определяется вторым параметром метода ПоместитьВоВременноеХранилище():
Когда мы помещаем в основное («вечное») временное хранилище адреса других временных хранилищ, платформа не отслеживает эту зависимость. Сами вложенные адреса для сборщика мусора выглядят как бесхозные временные файлы и безжалостно удаляются. Надеяться на синхронизацию времени жизни вложенных адресов с основным хранилищем на уровне платформы нельзя.
Важно понимать, что временное хранилище — это не сверхбыстрая оперативная память (ОЗУ) сервера приложений. При помещении данных в хранилище платформа выполняет их глубокую сериализацию и записывает в специальный сеансовый кэш на диске сервера (каталог кластера snccntx).
Если мы пытаемся хранить тяжелые таблицы значений отчетов во временном хранилище на весь сеанс, то при каждом обращении к ним сервер 1С выполняет ресурсоемкие операции чтения/записи и десериализации данных. При высокой нагрузке и большом количестве пользователей это приводит к быстрой фрагментации дисковой подсистемы сервера и замедлению работы всей системы.
Часто для обхода проблем с временным хранилищем разработчики пытаются использовать параметры сеанса (ПараметрыСеанса). Однако при попытке рассчитать данные тяжелого отчета в фоновом задании (ФЗ) и записать результат обратно в параметр сеанса возникает бесконечная рекурсия. Проанализируем, почему это происходит.
В модуле сеанса пишется обработчик инициализации:
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
// Пытаемся запустить фоновое задание для расчета кэша
ФоновыеЗадания.Выполнить("МойОбщийМодуль.РассчитатьДанныеДляКэша");
КонецПроцедуры
При запуске фонового задания платформа выполняет следующие шаги:
УстановкаПараметровСеанса() в модуле сеанса.УстановкаПараметровСеанса() вновь запускает фоновое задание.Возникает бесконечный цикл создания фоновых заданий, который быстро приводит к исчерпанию ресурсов сервера и аварийному завершению работы рабочего процесса rphost.
Чтобы исключить это зацикливание, в процедуре УстановкаПараметровСеанса() необходимо обязательно делать проверку на то, запущен ли текущий сеанс под фоновым заданием:
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
// Если сеанс вызван фоновым заданием - прерываем тяжелую инициализацию
Если ПолучитьФоновоеЗадание() <> Неопределено Тогда
Возврат;
КонецЕсли;
// Здесь выполняем стандартную инициализацию для обычных сеансов пользователей
ИнициализироватьБазовыеПараметры();
КонецПроцедуры
Наиболее надежным, масштабируемым и архитектурно верным решением для кэширования тяжелых отчетов является использование физических таблиц базы данных — непериодических Регистров сведений.
Рассмотрим этот подход по шагам:
КэшТяжелыхОтчетов).ИдентификаторКэша (тип Строка или УникальныйИдентификатор), НомерСтроки (тип Число). Разделение на строки позволяет избежать проблем с ограничением размера одной записи СУБД при хранении бинарных данных.Преимущества очевидны: данные доступны мгновенно через быстрые индексированные запросы СУБД, кэш не зависит от стабильности рабочих процессов сервера 1С (при перезапуске rphost кэш не сбрасывается) и не расходует оперативную память сеансов.
Если данные кэша не слишком объемные, но должны быть индивидуальными для каждого пользователя и доступны на протяжении всего сеанса, можно использовать параметры сеанса со специальным типом ФиксированнаяСтруктура или ФиксированноеСоответствие.
Разберем пример правильного добавления и изменения данных кэша без перезапуска полной инициализации параметров сеанса:
// В модуле сеанса при старте создаем пустую структуру кэша
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
Если ПараметрыСеанса.КэшПользователя = Неопределено Тогда
ПараметрыСеанса.КэшПользователя = Новый ФиксированнаяСтруктура;
КонецЕсли;
КонецПроцедуры
// Функция обновления кэша в общем модуле
Процедура ОбновитьКэшСеанса(КлючКэша, ДанныеКэша) Экспорт
// Получаем текущую фиксированную структуру из параметра сеанса
ТекущаяСтруктура = ПараметрыСеанса.КэшПользователя;
// Преобразуем ее в обычную структуру для модификации
ВременнаяСтруктура = Новый Структура;
Для Каждого Элемент Из ТекущаяСтруктура Цикл
ВременнаяСтруктура.Вставить(Элемент.Ключ, Элемент.Значение);
КонецЦикла;
// Добавляем или обновляем нужный фрагмент кэша
ВременнаяСтруктура.Вставить(КлючКэша, ДанныеКэша);
// Записываем обратно в параметр сеанса как фиксированную структуру
ПараметрыСеанса.КэшПользователя = Новый ФиксированнаяСтруктура(ВременнаяСтруктура);
КонецПроцедуры
Использование ФиксированнаяСтруктура гарантирует неизменяемость кэша со стороны клиента и исключает случайные ошибки рассинхронизации данных между сервером и клиентом.
Если требуется легкий кэш «на лету», лучшим инструментом являются общие модули с установленным свойством «Повторное использование возвращаемых значений» (на время вызова или на время сеанса).
Выясним особенности их работы в контексте кэширования тяжелых отчетов:
rphost. Это обеспечивает мгновенный доступ к данным без обращений к диску или СУБД.Out Of Memory.rphost, балансировщик может перенаправить очередной запрос пользователя на другой процесс, где данный кэш еще не инициализирован. Разработчик должен быть готов к тому, что функция повторного вычисления выполнится повторно.Для принудительного сброса (инвалидации) такого кэша разработчики часто используют изящный прием с передачей управляющего параметра (версии кэша) в качестве аргумента функции:
// Функция в общем модуле с повторным использованием значений
Функция ПолучитьДанныеОтчета(ВерсияКэша, ПараметрыОтчета) Экспорт
// Если ВерсияКэша изменилась, платформа не найдет результат в кэше
// по старым аргументам и выполнит тяжелый расчет заново
Данные = РассчитатьТяжелыйОтчет(ПараметрыОтчета);
Возврат Данные;
КонецФункции
Подводя итог анализу механизмов кэширования в 1С 8.3, можно сформулировать следующие рекомендации:
ФиксированнаяСтруктура, строго контролируя код в процедуре УстановкаПараметровСеанса.