Як козаки web-сторінки кешували
Усі ми, рано чи пізно, зустрічаємося з проблемою оптимізації, і класикою у цій галузі для веб-розробок є задача збільшення швидкості доступу до сторінок. Однак як виконати оптимізацію, не порушуючи виплеканої вами архітектури? Як перетворити її на «перлину» вашого проекту та отримати в результаті гнучкий механізм, який радуватиме серце та не стане джерелом майбутніх помилок? Спробую поділитися своїм досвідом у цій справі.
Спочатку думаємо…
Постало завдання прискорити завантаження сторінок на нашому проекті Crazymenu. Для цього вирішили більш активно використовувати кешування. Для розв’язання такої задачі необхідно було відповісти собі на ряд питань, і перше з них: “Що зберігати?”. Я буду вживати це слово замість «кешування», щоб хоч якось уникати тавтології.
Тут, власне, вибір не такий вже й великий, але є: безпосередньо HTML-код генерованих сторінок або об’єкти, що вибираються з бази даних (висловлюючись термінами шаблону MVC, відображення або модель). Необхідно відзначити, що в даному випадку вжите «або» зовсім не є виключним – просто необхідно визначити баланс між цими двома варіантами. Рішення в цьому питанні необхідно приймати, враховуючи об’єм пам’яті, який займатимуть об’єкти кешування, та витрати на формування ключа для них.
Наприклад, при зберіганні шматка JSP, який відображує назви ресторанів, може витрачатися менше пам’яті, ніж при кешуванні вибраних з бази об’єктів, які містять повну інформацію про них. Однак для зберігання сторінок необхідно буде формувати ключ із параметрів запиту, додавати якісь спеціальні символи до результату конкатенації безлічі рядків задля унікальності ключа і т.д.
Відчуваєте цю стародавню проблему вибору між швидкодією та об’ємом пам’яті? Словом, є над чим подумати: беріть чашку кави в руки та відкидайтеся на спинку крісла – хай інші думають, що ви отримуєте гроші за те, що просто п’єте гарячі напої у кріслі…
Потім робимо!
Подумали? Чудово! Можна йти далі. Наступне питання: “Як кешувати?“
Хочу підкреслити, що його потрібно розв’язувати не в першу чергу, а в другу – завжди потрібно чітко усвідомлювати, що хочеш зробити.
Для Java-проектів є досить багато бібліотек кешування. Їхній короткий огляд можна знайти у статті Caching Solutions in Java. Про те, як вибрати потрібну вам, можна говорити багато, ми ж поглянемо на це з іншого ракурсу. В нашому проекті вже використовувалася бібліотека ehcache. Її я і взяв за основу реалізації.
Кешування моделі варто здійснювати на рівнях DAO та бізнес-логіки (рис. 1). При використанні Spring архітектурне рішення для даного випадку може бути подібним до зображеного на рис.2.

Рис. 1. Місце кешування у вашому проекті
Захоплюємося…
Цікавішою проблемою є побудова механізму кешування відображення. Бібліотека ehcache дозволяє описувати фільтри, успадковуючи клас CacheFilter. Такі фільтри використовуватимуть потрібний вам кеш та зберігатимуть у ньому сторінки або їхні фрагменти.
Таким чином, визначивши в коді фільтра, який кеш використовується, як із об’єкта запиту формувати ключ, та задавши мапінг вашому дітищу, можна організувати зберігання вихідних даних, згенерованих за допомогою JSP, Velocity чи Freemarker.

Рис. 2. Кешування моделі
Цей підхід суттєво відрізняється від того, що пропонує інша популярна бібліотека кешування OSCache. У ній можна здійснювати кешування фрагментів JSP, використовуючи відповідні теги. Що ж краще? Особисто мені до душі перший варіант: у другому, при використанні тегів, ми виносимо логіку управління даними у код нарізки - не дуже витончено .
Але існують випадки, коли такі теги стають чи не єдиним правильним рішенням.
Цілком імовірно, що перша сторінка вашого сайту є таким-собі колажем, де зібрано багато-багато інформації. При цьому, як правило, кожна з вибірок, з яких складається сторінка, має власні параметри. У такому випадку ключ для всієї такої сторінки буде занадто складним, що приведе до того, що об’єкт у кеші матиме менше попадань, а це, у свою чергу, збільшить кількість елементів у пам’яті (нікому не потрібних елементів!). Тому тут краще зберігати кожен фрагмент сторінки окремо, кожен зі своїм, простішим, ключем. Але ж такі фрагменти описані або в одній JSP, або в різних та вставлені через звичайний jsp:include чи через tile при використанні Struts – тут фільтр уже безсилий. Що ж залишається? Тег!
Насправді автор сумлінно намагався відігнати таку думку (так уже йому не подобалася ця ідея – реалізувати кешування прямо у відображенні) і обдумував, чи не можна перехопити той самий jsp:include, щоб у ньому підставити значення з кешу. Однак хорошого універсального рішення так і не було знайдено. Отже, нам потрібно використовувати теги.
То як же бути?..
Нагадаю, що в проекті було підключено бібліотеку ehcache, у якій такого інструменту немає. Залучати ще одну бібліотеку (OSCache) дуже не хотілося. Таким чином і зародилося наступне рішення.
- Виділено єдиний менеджер кешу сторінок
- Розроблено власні фільтр (нащадок CacheFilter в ehcache) та тег.
Для менеджера описуються правила кешування. Кожне правило складається з ідентифікатора цього правила та списку імен параметрів/атрибутів запиту/сесії, значення яких формують ключ для об’єкта в кеші. І фільтр, і тег використовують методи менеджера для збереження, пошуку фрагменту сторінки та обчислення ключа.
У випадку фільтра URI запиту відіграє роль ідентифікатора правила у менеджера.
Для тегу такий ідентифікатор задається окремим атрибутом. Що стосується самих правил, то для них був вигаданий власний синтаксис:
<!-- Logic bean. -->
<bean name="pagesCacheManager" class="com.stanfy.crazymenu.common.cache.PagesCacheManager" >
<property name="cache" ref="pageFragmentsCache" />
<property name="rules">
<value>
<![CDATA[
restaurantMap --> restaurant_id
restaurantReviews --> restaurant_id, start, limit
url: /allxml.jsp -->
partOfProfile (!profile_id) --> profile_id
]]>
</value>
</property>
</bean>
Правило «url: /allxml.jsp» буде опрацьовано через фільтр, інші використовуються разом з тегом.

Рис. 3. Кешування відображення
І що?..
Після застосування такої схеми на Crazymenu швидше стали завантажуватися стартова сторінка, профайл користувача, сторінка ресторану. На даний момент проводяться спостереження за статистикою та експериментальний вибір параметрів управління кешем: кількість елементів у пам’яті, тривалість їхнього життя…
Щиро кажучи, я не зовсім задоволений цим рішенням з точки зору архітектури, бо й на далі притримуюся точки зору, що виносити кешування на рівень нарізки сторінок. Однак замінити використання тега можна, хіба що змінивши фреймфорк, що застосовується для реалізації вашої моделі MVC. Але що робити, коли в проекті кілька таких фреймворків? Словом, задача кешування при серйозному підході робиться зовсім нетривіальною.
Чекаю на ваші коментарі.






Роман Мазур
в 21:01, 27.01.2009… недочекався – пишу сам. Цікава стаття про кешування JSP: http://www.onjava.com/pub/a/onjava/2005/01/05/jspcache.html