Медленно работает кэш jmix

jmix 2.7.1

Столкнулись с проблемой, что кэш в jmix работает медленно.
Прикрепляю демо проект. В нем выполняется 100000 запросов в базу.
Итоги наших замеров:

  1. Без кэша 31,1 сек
  2. С кэшом, когда искомой сущности нет 38,3 сек
  3. С кэшом, когда искомая сущность есть 54,8 сек
  4. С анотацией @Cacheable, когда искомой сущности нет 221 мс
  5. С анотацией @Cacheable, когда искомая сущность есть 315 мс
    Также прикрепляю снепшоты из VisualVM, числа в названиях файлов соответствуют пунктам выше. Нужный поток можно найти по Total Time (CPU).
    Как мы разобрались, основная часть времени уходит на парсинг каждого запроса, обработку параметров, и на создание prototype бинов при создании JmixEclipseLinkQuery. Т.е. происходит много работы ещё до того как дело доходит до кэша.

Шаги для воспроизведения

  1. Зайти в Application > Cached entities, нажать Test cache
  2. Раскомментировать eclipselink.cache.shared.CachedEntity=true в application.properties и @QueryHints(@QueryHint(name = PersistenceHints.CACHEABLE, value = “true”)) в CachedEntityRepository, нажать Test cache
  3. Создать сущность с названием “non-existent”, нажать Test cache
  4. Удалить сущность “non-existent” и раскомментировать @Cacheable(“testCache”) в CachedEntityService, нажать Test cache
  5. Создать сущность с названием “non-existent”, нажать Test cache

JmixCacheProblem.zip (184,3 КБ)

Дмитрий, здравствуйте!

Спасибо за приложенный проект и снапшоты.

Пока могу сказать, что кэши сущностей и запросов - это два дополняющих друг друга механизма.
Кэш сущностей - механизм Eclipselink и он хранит сущности по id. Он хорошо работает для больших сущностей и при долгом соединении с бд.
Кэш запросов - механизм Jmix - который позволяет избежать повторной обработки запроса ( результат его применения виден в замерах (4) и (5)).

Использовать их лучше вместе, для достижения наилучшего результата.

Почему такая большая разница во времени выполнения запроса только с кэшом сущностей - посмотрю подробнее в ближайшее время.

Не подскажете, возникла где-то необходимость использовать только кэш сущностей и при этом делать очень много запросов в короткий промежуток времени?

С уважением,
Дмитрий

Добрый день, Дмитрий!
Вы ошибаетесь, в замерах 2 и 3 включен и кэш запросов и кэш сущностей.
Что касается замеров 4 и 5 - там используется аннотация @Cacheable из Spring Framework, т.е. кэш проверяется снаружи, ещё до вызова метода репозитория или DataManager.load. Здесь эти замеры приведены просто для сравнения с кешем jmix.
Можно критиковать замеры 4 и 5 за то, что кэш там очень примитивный и не подойдет для реальных задач, но хотелось бы что-то близкое по скорости, или хотя бы понять почему это невозможно.

Ещё хочу отметить, дело тут не совсем даже в кэше. Скорее сам вызов DataManager.load уже добавляет некоторые издержки, которые за 100000 вызовов накапливаются.
Но если эти издержки удастся устранить хотя бы для случая с использованием кэша, то это тоже подойдет.

Дмитрий, здравствуйте!

Вы ошибаетесь, в замерах 2 и 3 включен и кэш запросов и кэш сущностей.

Да, проглядел это в шагах воспроизведеия, но в таком случае, я не очень понимаю, каким образом получены эти замеры.

В первую очередь, бросилось в глаза то, что при нескольких последовательных запусках по описаным шагам данные сильно разнятся. Поэтому я доработал метод, добавив 5 запусков на “разогрев“, а далее 10 последовательных запусков и вывод среднего времени работы. Так же максимально увеличил выделенную память, чтобы абсолютно точно исключить проблемы с ней:

jmix-cache-problem_withWarmupAndMeanValue.zip (121,0 КБ)

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

  • Без кэшей: 231.7 секунд.
  • И Entity, и Query кэши включены: 14.8 секунд.

Для более “жизненных” батчей по 10000 вызовов:

  • Без кэшей: 19.6 секунд.
  • И Entity, и Query кэши включены: 1.5 секунды.

Вот логи измерений: logs.txt (5,6 КБ)

Таким образом, я не вижу описанной в первом сообщении и заголовке проблемы. Даже в приведенном вырожденном примере, на приложенном проекте кэши Ecipselink и Jmix при совместном применении ускоряют выполнение пачки запросов более чем на порядок.

Единственный способ, которым мне удалось получить результаты, примерно похожие на ваши - это включать только Entity кэши, т.е. не раскомментировать @QueryHints.

Об издержках

Упомянутые издержки (создание или разбор и модификация jpql запроса) необходимы для добавления в него дополнительных параметров, а именно применения Condition-ов, добавления условий для тенанта, jpql row level security и других параметров, необходимых для работы разных механизмов Jmix.
Если пытаться кэшировать результаты до модификации/создания jpql-запроса, то мы получим некорректные данные.

Если хочется совсем избавиться от издержек и получить что-то близкое по скорости к @Cacheable, то лучше делать это в конкретном месте на стороне приложения, где будет гарантироваться, что для закэшированного результата не изменится пользователь, тенант, Condition-ы, хинты и все остальное, что влияет на список возвращаемых сущностей.

С уважением,
Дмитрий