При реализации сложных алгоритмов расчета (например, расчет произвольных формул с высокой степенью вложенности, финансовых показателей, оборотов за разные периоды) одни и те же показатели с одинаковыми параметрами могут вычисляться многократно. Для ускорения работы системы логично применить кэширование результатов в оперативной памяти сеанса.
Однако на практике разработчик сталкивается с проблемой: ключ кэша часто является составным и включает в себя ссылку на показатель, период (две даты) и произвольный отбор (например, структуру отбора СКД). Разберем, почему стандартные коллекции не всегда подходят для таких ключей напрямую и как правильно построить надежный механизм кэширования.
Посмотрим на частую ошибку: попытку использовать коллекцию Структура или ХранилищеЗначения в качестве ключа для объекта Соответствие.
Проанализируем тестовый пример, демонстрирующий данную проблему:
СоответствиеКэш = Новый Соответствие;
МойУИД = Новый УникальныйИдентификатор;
ХЗ = Новый ХранилищеЗначения(МойУИД);
СтруктураКлюча = Новый Структура;
СтруктураКлюча.Вставить("Номер", 5);
СтруктураКлюча.Вставить("ХЗ", ХЗ);
СоответствиеКэш.Вставить(СтруктураКлюча, "Значение для 5");
// Пытаемся получить значение по аналогичной структуре с теми же данными
ПоисковаяСтруктура = Новый Структура;
ПоисковаяСтруктура.Вставить("Номер", 5);
ПоисковаяСтруктура.Вставить("ХЗ", Новый ХранилищеЗначения(МойУИД));
Значение = СоответствиеКэш.Получить(ПоисковаяСтруктура);
// Результат: Неопределено
Выясним причину такого поведения платформы 1С:
Соответствие выполняется только для примитивных типов (числа, строки, даты, булево), ссылок базы данных и значений типа УникальныйИдентификатор.Структура, Массив, ТаблицаЗначений, ХранилищеЗначения, НастройкиКомпоновкиДанных). Даже если две структуры содержат абсолютно идентичные ключи и значения, они являются разными объектами в памяти, поэтому поиск по такому ключу вернет Неопределено.Для создания универсального строкового ключа или ключа типа УникальныйИдентификатор преобразуем составные параметры в детерминированную строку или бинарные данные и получим их хеш-сумму.
Разберем функцию формирования хеша на основе объекта ХешированиеДанных:
// Возвращает хэш-сумму значения, сравнение которой может заменить сравнение самих значений.
//
// Параметры:
// Значение - Произвольный - Любое значение.
//
// Возвращаемое значение:
// УникальныйИдентификатор - Уникальный идентификатор, соответствующий MD5-хэшу значения.
//
Функция ХэшЗначения(Значение) Экспорт
Хэширование = Новый ХешированиеДанных(ХешФункция.MD5);
Хэширование.Добавить(ЗначениеВСтрокуВнутр(Значение));
Хэш = НРег(СтрЗаменить(Строка(Хэширование.ХешСумма), " ", ""));
Возврат Новый УникальныйИдентификатор(
Сред(Хэш, 1, 8) + "-" +
Сред(Хэш, 9, 4) + "-" +
Сред(Хэш, 13, 4) + "-" +
Сред(Хэш, 17, 4) + "-" +
Сред(Хэш, 21, 12)
);
КонецФункции
Посмотрим на практическое применение хеширования для кэша:
СоответствиеКэш = Новый Соответствие;
// Формируем ключ для записи в кэш
КлючПараметров = Новый ФиксированныйМассив(Новый Массив);
// Заполняем массив строго в определенном порядке
МассивКлюча = Новый Массив;
МассивКлюча.Добавить(ПоказательСсылка);
МассивКлюча.Добавить(ДатаНачала);
МассивКлюча.Добавить(ДатаОкончания);
КлючФиксированный = Новый ФиксированныйМассив(МассивКлюча);
КлючКэша = ХэшЗначения(КлючФиксированный);
СоответствиеКэш.Вставить(КлючКэша, РассчитанныйОборот);
// При повторном расчете получаем результат по вычисленному хешу
ЗначениеИзКэша = СоответствиеКэш.Получить(КлючКэша);
Важное замечание: сериализация через ЗначениеВСтрокуВнутр() для обычной Структуры или ТаблицыЗначений может зависеть от порядка добавления свойств и колонок. Поэтому для составного ключа рекомендуется использовать ФиксированныйМассив или ФиксированнаяСтруктура.
Если набор параметров строго регламентирован (ссылка показателя, даты периода и строковые отборы), операция сериализации всего объекта может быть избыточной. В таких ситуациях быстрее и надежнее собрать строковый ключ вручную через разделитель.
Разберем по шагам создание канонического ключа:
Строка(ПоказательСсылка.УникальныйИдентификатор()) или уникальный код.Формат(Дата, "ДФ=yyyyMMddHHmmss").КомпоновщикНастроек.Настройки.Отбор.Элементы, отбираем только активные (Применение = Истина) и конкатенируем их значащие свойства (ЛевоеЗначение, ВидСравнения, ПравоеЗначение). Это исключает влияние динамически генерируемых платформой внутренних идентификаторов СКД.
Функция СформироватьКлючКэша(Показатель, ДатаНач, ДатаКон, ХешОтбора) Экспорт
Разделитель = "|";
СтрокаКлюча = Строка(Показатель.УникальныйИдентификатор()) + Разделитель
+ Формат(ДатаНач, "ДФ=yyyyMMddHHmmss") + Разделитель
+ Формат(ДатаКон, "ДФ=yyyyMMddHHmmss") + Разделитель
+ ХешОтбора;
Возврат СтрокаКлюча;
КонецФункции
При использовании сеансового кэширования важно учитывать следующие особенности:
Соответствие не очищается автоматически сборщиком мусора до тех пор, пока на нее есть ссылка. Если граф формул огромен, периодически очищайте коллекцию методом Очистить() по завершении сеанса расчета формулы.