Кэширование результатов методов желудей ОСени аннотациями &Кэшируемый и &СбрасываетКэш: поставщики в памяти и в файлах, общий бюджет памяти в байтах, настройки каждого кэша по имени, вытеснение SLRU, срок жизни записей
Адрес пула в аргументе разбирает opm 1.7.0 и новее. Клиент постарше ответит «Пакет не найден» — ему нужен способ ниже на странице.
opm install hub.oscript.io/segate-ekb/autumn-cacheСкачанный файл ставится командой opm install -f <файл> — сеть для этого уже не нужна.
Установка на opm младше 1.7.0
Легаси-флоу. Клиент младше 1.7.0 адрес пула в аргументе не разбирает: пул сначала прописывают сервером пакетов, и только потом ставят через него. На 1.7.0 и новее этот раздел не нужен — хватает команды из шапки страницы.
Добавьте пул сервером пакетов в opm.cfg. Порт продублирован в «Сервер»: у opm push поле «Порт» не читается.
{
"СервераПакетов": [
{
"Имя": "segate-ekb",
"Сервер": "https://hub.oscript.io",
"Порт": 443,
"ПутьНаСервере": "/api/v1/pools/segate-ekb/download/",
"РесурсПубликацииПакетов": "/api/v1/pools/segate-ekb/push",
"Приоритет": 1
}
]
}И ставьте пакет, указывая сервер:
opm install -m segate-ekb autumn-cacheОписание
autumn-cache
Кэширование результатов методов желудей ОСени
аннотациями — аналог @Cacheable / @CacheEvict из Spring. Кэши в памяти и в файлах,
настройки у каждого кэша свои, общий бюджет памяти в байтах, срок жизни записей, счётчики.
Установка
opm install autumn-cache
Быстрый старт
Желудь, чьи чтения кэшируются:
// Классы/СправочникКурсов.os
Перем Источник;
&Кэшируемый("курсы")
Функция Курс(&КлючКэша Валюта, &КлючКэша НаДату) Экспорт
// Выполняется только на промахе; повтор с теми же Валюта и НаДату берёт значение из кэша.
Возврат Источник.ЗагрузитьКурс(Валюта, НаДату);
КонецФункции
&СбрасываетКэш("курсы")
Процедура ЗаписатьКурс(&КлючКэша Валюта, &КлючКэша НаДату, Курс) Экспорт
// После успешного вызова запись с ключом (Валюта, НаДату) удаляется из кэша.
Источник.ЗаписатьКурс(Валюта, НаДату, Курс);
КонецПроцедуры
&Желудь
Процедура ПриСозданииОбъекта(&Пластилин ИсточникКурсов)
Источник = ИсточникКурсов;
КонецПроцедуры
Приложение подключает библиотеку рядом с ОСенью, настройки — в autumn-properties.json:
#Использовать autumn
#Использовать autumn-cache
#Использовать "."
Поделка = Новый Поделка();
Поделка.ЗапуститьПриложение();
{
"cache": {
"курсы": { "Предел": 5000, "СрокЖизни": 300 }
}
}
Как организовать кэши
Что кэшировать
Кэшируйте чистые чтения: результат зависит только от параметров и меняется редко —
настройки, справочники, права, данные из базы или внешней службы. Аннотации работают на
экспортных методах желудей, которые создаёт ОСень, и перехватывают только вызов снаружи:
вызов метода из того же желудя (напрямую или через ЭтотОбъект) идёт мимо кэша. Поэтому
кэш ставится на границе — на публичных методах службы или хранилища, которые зовут другие
желуди.
Процедуры не кэшируются: &Кэшируемый на процедуре и аннотации кэша на неэкспортном методе —
ошибка при сборке желудя.
Ключ кэша
Ключ записи — значения параметров, помеченных &КлючКэша, в порядке объявления. Если в методе
нет ни одного &КлючКэша, ключом становятся все параметры.
Помечайте только то, от чего зависит ответ. Контекст запроса, журнал, службы, флаги отладки в ключ не входят — иначе одинаковые чтения разойдутся по разным записям, а объект в ключе вообще недопустим:
Перем ХранилищеПрав;
&Кэшируемый("права")
Функция Права(&КлючКэша Пользователь, &КлючКэша Раздел, Контекст) Экспорт
Возврат ХранилищеПрав.Права(Пользователь, Раздел);
КонецФункции
В ключ входят только значения типов Строка, Число, Дата, Булево,
УникальныйИдентификатор и Неопределено. Структура, Массив, сущность и любой другой
объект в ключе — исключение при вызове, метод при этом не выполняется. Если нужен объект —
передайте в ключ его идентификатор.
Как сравниваются ключи:
| Значения | Ключи |
|---|---|
1, 1.0, 2/2 |
один ключ: число без хвостовых нулей |
"1" и 1 |
разные: тип входит в ключ |
"Иванов", "иванов", " Иванов " |
разные: строка берётся как есть |
"" и Неопределено |
разные |
("ab", "c") и ("a", "bc") |
разные: части ключа не склеиваются |
Регистр и пробелы нормализуйте до вызова — в желуде, который зовёт кэшируемый метод:
Перем СлужбаПрав;
Функция ПраваНаОтчёты(Логин, Контекст)
Возврат СлужбаПрав.Права(НРег(СокрЛП(Логин)), "отчёты", Контекст);
КонецФункции
Метод без параметров — одна запись на весь кэш. Необязательный параметр со значением
Неопределено — полноправная часть ключа.
Одно имя кэша — одно пространство ключей. Имя метода в ключ не входит: два метода с одним именем кэша и одинаковыми значениями ключа читают одну запись. На этом держится сброс из другого метода, но это и ловушка:
Перем Пользователи;
// У каждого способа поиска свой кэш. В одном кэше идентификатор "42" и логин "42" дали бы
// один ключ, и методы прочли бы ответы друг друга.
&Кэшируемый("пользователи")
Функция ПоИдентификатору(&КлючКэша Идентификатор) Экспорт
Возврат Пользователи.ПоИдентификатору(Идентификатор);
КонецФункции
&Кэшируемый("пользователи-по-логину")
Функция ПоЛогину(&КлючКэша Логин) Экспорт
Возврат Пользователи.ПоЛогину(Логин);
КонецФункции
Делите одно имя кэша только между методами, которые возвращают одно и то же по одинаковому ключу: читателем и писателем одной записи.
Сброс
&СбрасываетКэш("<имя>") удаляет запись после успешного вызова метода: если метод бросил
исключение, кэш не трогается. Ключ сброса собирается по тем же правилам, что у читателя, и должен
совпасть с ним по составу, типам и порядку частей:
Перем ХранилищеПрав;
// Читатель
&Кэшируемый("права")
Функция Права(&КлючКэша Пользователь, &КлючКэша Раздел, Контекст) Экспорт
Возврат ХранилищеПрав.Права(Пользователь, Раздел);
КонецФункции
// Писатель: те же ключевые параметры в том же порядке — сбрасывается ровно эта запись.
&СбрасываетКэш("права")
Процедура ВыдатьПраво(&КлючКэша Пользователь, &КлючКэша Раздел, Право) Экспорт
ХранилищеПрав.Записать(Пользователь, Раздел, Право);
КонецПроцедуры
// Писатель без ключа читателя: сбросить кэш целиком.
&СбрасываетКэш(Значение = "права", ВсеЗаписи = Истина)
Процедура ЗагрузитьРоли(Роли) Экспорт
ХранилищеПрав.ЗагрузитьРоли(Роли);
КонецПроцедуры
Писатель, который принимает сущность, без &КлючКэша соберёт ключ из всех параметров, получит
объект в ключе и упадёт, не выполнившись. Пометьте параметр с ключом, сбросьте весь кэш через
ВсеЗаписи или удалите запись программно после сохранения:
Перем Хранилище;
Перем КэшПользователей;
Процедура Сохранить(Сущность) Экспорт
Хранилище.Сохранить(Сущность);
КэшПользователей.Удалить(Сущность.Идентификатор);
КонецПроцедуры
&Желудь
Процедура ПриСозданииОбъекта(&Пластилин("ХранилищеСущностейПользователь") ХранилищеПользователей,
&Пластилин МенеджерКэшей)
Хранилище = ХранилищеПользователей;
КэшПользователей = МенеджерКэшей.Кэш("пользователи");
КонецПроцедуры
На одном методе может стоять несколько &СбрасываетКэш — по одному на кэш.
Сброс действует только в своём процессе. Если приложение запущено в нескольких процессах, другие
процессы отдают старое значение, пока запись не истечёт: задавайте таким кэшам СрокЖизни.
Что кладётся в кэш
Исключения, Неопределено и NULL не кэшируются — следующий вызов снова выполнит метод.
Этим удобно не запоминать временную неудачу: вернули Неопределено — повторите в следующий раз.
Если же «записи нет» — тоже ответ, который стоит помнить, верните для него значение, например
Ложь.
Кэш в памяти с общим бюджетом (cache.ПределПамяти, см. ниже) принимает только неизменяемые
значения: Строка, Число, Дата, Булево, УникальныйИдентификатор, ДвоичныеДанные и
ФиксированнаяСтруктура, ФиксированноеСоответствие, ФиксированныйМассив из них. Он отдаёт
вызывающему ту же ссылку, что хранит, и изменяемое значение испортил бы первый же получатель.
Сущность, Структура, Массив и ТаблицаЗначений не кладутся: метод работает без кэша и пишет
ошибку в журнал. Из базы возвращайте снимок для чтения:
Перем Хранилище;
&Кэшируемый("пользователи")
Функция Пользователь(&КлючКэша Идентификатор) Экспорт
Сущность = Хранилище.ПолучитьОдно(Идентификатор);
Если Сущность = Неопределено Тогда
Возврат Ложь;
КонецЕсли;
Возврат Новый ФиксированнаяСтруктура("Идентификатор, Логин, Имя",
Сущность.Идентификатор, Сущность.Логин, Сущность.Имя);
КонецФункции
&Желудь
Процедура ПриСозданииОбъекта(&Пластилин("ХранилищеСущностейПользователь") ХранилищеПользователей)
Хранилище = ХранилищеПользователей;
КонецПроцедуры
Файловый кэш хранит только ДвоичныеДанные.
Настройки
Каждый кэш настраивается по своему имени ключами cache.<имя>.*:
| Ключ | Значение | По умолчанию |
|---|---|---|
Поставщик |
память или файлы |
память |
Предел |
записей для памяти, байт для файлов | общий бюджет, если задан cache.ПределПамяти, иначе 1000 записей; для файлов обязателен |
СрокЖизни |
секунды от записи, дробные допустимы | бессрочно |
Каталог |
каталог файлового кэша | для файлов обязателен |
{
"cache": {
"ПределПамяти": 150000000,
"права": { "СрокЖизни": 60 },
"курсы": { "Предел": 5000, "СрокЖизни": 300 },
"отчёты": { "Поставщик": "файлы", "Каталог": "./cache/reports", "Предел": 2147483648 }
}
}
Здесь «права» делят общий бюджет 150 МБ со всеми кэшами в памяти без своего предела, «курсы» держат до 5000 записей любого вида, «отчёты» — до 2 ГБ файлов на диске. Кэш без настроек живёт в памяти: в общем бюджете, если он задан, иначе с пределом 1000 записей. Фактические настройки каждого кэша пишутся в журнал при его создании — опечатка в ключе видна там. Имя кэша «ПределПамяти» занято.
Вытесняются давно не читанные записи; записи, которые читали повторно, защищены от потока разовых ключей. В общем бюджете и в файловом кэше одна запись не может занять больше десятой доли предела.
Общий бюджет памяти
cache.ПределПамяти — байты на все кэши в памяти, у которых не задан свой Предел. Память
достаётся записям, которые читают чаще, в каком бы кэше они ни были.
Вес записи — оценка памяти под неё целиком:
- 1750 байт — сама запись, её место в индексе и в очереди;
- ключ — 56 байт плюс 2 байта на символ текста ключа;
- значение — число 32 байта, дата 40, строка 56 + 2 на символ, фиксированная структура 700 + 100 на поле плюс ключи и значения полей.
Прикиньте число горячих записей и умножьте на вес одной; после запуска Статистика().Занято
покажет фактическое. Бюджет меньше ~18 КБ не примет ни одной записи: наибольшая запись — десятая
доля бюджета, а одни накладные весят 1,75 КБ. Оценка сходится с реальным приростом памяти
процесса в пределах ±15 %; к бюджету прибавляются ~80 МБ самого процесса и запас сборщика мусора —
ставьте с запасом.
Файловый кэш
Хранит ДвоичныеДанные, предел — байты на диске. При создании кэша из каталога удаляются файлы
*.cache прошлого запуска, поэтому каталог принадлежит одному кэшу одного процесса. Попадание
читает файл в память целиком.
Журнал
Журнал oscript.lib.autumn-cache на уровне Информация пишет настройки каждого созданного кэша, на
уровне Ошибка — значения, которые не удалось положить. На уровне Отладка виден каждый шаг:
Кэш "права": промах, ключ С4:ivanС6:отчёты
Кэш "права": положено, ключ С4:ivanС6:отчёты, вес 1906, занято бюджета 1906 из 20000
Кэш "права": попадание, ключ С4:ivanС6:отчёты, вес 1906, сегмент испытательный
Кэш "права": вытеснение, ключ С5:user1С6:отчёты, вес 1908, сегмент испытательный, давление 20988 из 20000
Кэш "права": сброс, ключ С4:ivanС6:отчёты, удалено записей 1, поколение 1
Ключ в строке — текст ключа: у каждой части метка типа и длина (С4:ivan — строка из четырёх
символов). Строки «не положено» называют причину: Неопределено, вес больше допустимого, сброс
во время вызова, изменяемое значение.
Включить отладку — переменной среды или файлом logos.cfg рядом со стартовым сценарием:
LOGOS_CONFIG="logger.oscript.lib.autumn-cache=DEBUG" oscript main.os
Ключи кэша попадают в журнал — не делайте ключом секреты.
Напильник кэша стоит на &Порядок(400), внутри напильников телеметрии (500 и выше): попадания
видны телеметрии как вызовы метода, а напильники с меньшим порядком выполняются только на промахе.
Статистика и программный доступ
Кэш по имени выдаёт желудь МенеджерКэшей — тот же объект, с которым работают аннотации:
Перем КэшПрав;
Функция ДоляПопаданий() Экспорт
Статистика = КэшПрав.Статистика();
Обращений = Статистика.Попадания + Статистика.Промахи;
Если Обращений = 0 Тогда
Возврат 0;
КонецЕсли;
Возврат Статистика.Попадания / Обращений;
КонецФункции
Процедура ЗабытьПрава(Пользователь, Раздел) Экспорт
КэшПрав.Удалить(Новый КлючКэша().Добавить(Пользователь).Добавить(Раздел));
КонецПроцедуры
&Желудь
Процедура ПриСозданииОбъекта(&Пластилин МенеджерКэшей)
КэшПрав = МенеджерКэшей.Кэш("права");
КонецПроцедуры
| Класс | Метод | Что делает |
|---|---|---|
МенеджерКэшей |
Кэш(Имя) |
кэш по имени |
Кэш |
Получить(Ключ) |
значение или Неопределено |
Положить(Ключ, Значение) |
кладёт значение; недопустимое — исключение | |
Удалить(Ключ), Очистить() |
удаляет запись или все записи | |
Статистика() |
Структура("Попадания, Промахи, Вытеснения, Занято") |
|
КлючКэша |
Добавить(Значение) |
ключ из нескольких частей |
Ключ из одного значения можно передать самим значением: Кэш.Удалить(Идентификатор) — то же, что
Кэш.Удалить(Новый КлючКэша().Добавить(Идентификатор)). Занято — вес записей этого кэша: число
записей или байты. Вытеснения считают записи, убранные по пределу и по сроку жизни.
Ограничения
- Параллельные промахи по одному ключу вызывают метод каждый.
- Сброс, пришедший, пока метод выполнялся, отбрасывает его результат — и любые другие значения этого кэша, которые кладутся в тот же момент.
- Кэш в памяти с пределом в записях хранит ссылки: изменение полученного объекта меняет и кэш.
- Срок жизни проверяется при чтении: истёкшая запись занимает место до обращения или вытеснения.
- На Windows файл, который читают в момент вытеснения, не удаляется и остаётся вне учёта до перезапуска.
Лицензия
MIT