Фрагментное кеширование в Rails увеличивает скорость, когда кэшируется большая часть страницы. Это сложнее для страниц с большим количеством динамического или пользовательского контента. Решение состоит в том, чтобы использовать брызги JavaScript, которые похожи на hagelslag, но без шоколада и с дополнительными запросами на загрузку пользовательского контента после того, как остальная часть страницы обслуживается напрямую из кеша. .

Кеширование фрагментов

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

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

Непрочитанные ответы

В качестве примера возьмем блог на основе Rails. В этом блоге каждая статья может иметь несколько отзывов. Была добавлена ​​система аутентификации, позволяющая пользователям входить в систему и публиковать ответы со своими учетными записями. На каждой странице статьи мы перечисляем ответы. Чтобы упростить нашим пользователям, мы помечаем новые ответы значком и другим цветом фона.

Если мы заключим каждую статью в cache блок, мы рискуем отметить неправильные ответы как непрочитанные. Когда пользователь запрашивает индексную страницу, ответ кешируется. Когда другой пользователь позже запросит ту же страницу, он получит кешированный фрагмент с непрочитанными ответами первого пользователя.

# app/views/articles/show.html.erb
<%= cache(@article) do %>
  <h1><%= @article.title %></h1>

  <%= simple_format(@article.content) %>

  <section id="responses">
    <h2>Responses</h2>

    <% @article.responses.each do |response| %>
      <div class="<%= response.read_by?(@current_user) ? 'read' : 'unread' %>">
        <%= time_tag(response.created_at) %>
        <strong><%= response.name %></strong>: <%= response.content %>
      </div>
    <% end %>
  </section>
<% end %>

Способ решить эту проблему - добавить текущего пользователя, вошедшего в систему, к ключу кэша, используя [@article, @current_user] вместо просто @article в качестве аргумента, передаваемого вспомогательному методу cache.

# app/views/articles/show.html.erb
<%= cache([@article, @current_user]) do %>
  <h1><%= @article.title %></h1>

  # ...
<% end %>

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

JavaScript разбрызгивает

Поскольку большая часть страницы одинакова для всех пользователей, мы хотели бы повторно использовать кешированный фрагмент статьи для каждого посетителя. Для этого мы можем загрузить статью из кеша и добавить дополнительный запрос для получения пользовательского контента после загрузки страницы с помощью запроса JavaScript. Использование для этого небольшого количества функций JavaScript имеет несколько преимуществ:

  1. Страницу можно кэшировать один раз и повторно использовать для гостей, не прошедших аутентификацию, а также для других пользователей без необходимости отдельно кэшировать фрагменты для каждого пользователя.
  2. Наиболее важный контент загружается в первую очередь для максимального времени отклика, а второстепенные функции, такие как количество непрочитанных сообщений, загружаются позже.
  3. Поскольку дополнительный запрос выполняется через JavaScript, вся страница может быть кэширована на границе для дальнейшего повышения производительности.

Очистка

Сначала мы удалим динамическое содержание с наших страниц, чтобы упростить их кеширование. Мы снова удалим @current_user из ключа кеширования в блоке cache, чтобы они больше не кешировались для каждого пользователя. Затем мы удалим запрос, который находит счетчики непрочитанных данных из контроллера, и удалим имена классов CSS из представления.

# app/views/articles/show.html.erb
<%= cache(@article) do %>
  <h1><%= @article.title %></h1>

  <%= simple_format(@article.content) %>

  <section id="responses">
    <h2>Responses</h2>

    <% @article.responses.each do |response| %>
      <div data-response-id="<%= response.id %>">
        <%= time_tag(response.updated_at) %>
        <strong><%= response.name %></strong>: <%= response.content %>
      </div>
    <% end %>
  </section>
<% end %>

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

Конечная точка

Сначала мы создадим конечную точку, чтобы найти непрочитанные ответы для пользователя. Мы хотели бы изменить текущее состояние страницы после ее отображения, поэтому мы запросим у нее JSON.

# app/controllers/unread_responses_controller.rb
class UnreadResponsesController < ApplicationController
  def index
    @article = Article.find(params[:article_id])
    @responses = @article.unread_responses_for(@current_user)
  end
end
# app/views/unread_responses/index.json.jbuilder
json.array! @responses do |response|
  json.extract! response, :id
end
# config/routes.rb
Rails.application.routes.draw do
  resources :articles do
    resources :responses
    resources :unread_responses
  end
end

Наша конечная точка выдаст список идентификаторов непрочитанных ответов.

# GET /articles/1/unread_responses.json
[{"id":1},{"id":2},{"id":3}]

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

Отображение непрочитанных ответов

Вместо того, чтобы жестко кодировать URL-адрес конечной точки непрочитанных ответов в коде JavaScript, мы добавим его в атрибут данных в представлении, чтобы мы могли ссылаться на него позже.

# app/views/articles/show.html.erb
<section id="responses" data-url="<%= article_unread_responses_path(@article, json: true) %>">
  # ...
</section>

После завершения загрузки страницы мы запросим идентификаторы непрочитанных ответов с нашей новой конечной точки. Затем мы будем использовать данные из этого списка, чтобы отмечать каждую статью как непрочитанную, добавляя к ней класс CSS.

// app/assets/javascripts/application.js
document.addEventListener("turbolinks:load", function(){
  responses = document.getElementById("responses")

  if(!responses.dataset.loaded) {
    Rails.ajax({
      url: responses.dataset.url,
      type: "GET",
      success: function(data) {
        responses.dataset.loaded = true;

        data.forEach(function(response) {
          element = document.querySelector("[data-response-id='" + response.id + "']");
          element.classList.add("unread");
        })
      }
    });
  }
})

Поскольку наше приложение Rails использует Turbolinks, мы будем ждать загрузки страницы, ожидая turbolinks:loadevent. Когда это событие срабатывает, мы найдем поле ответов, используя его идентификатор.

Затем мы проверим, есть ли у элемента ответов атрибут данных loaded. Мы установим этот атрибут после обновления непрочитанных ответов позже, поэтому мы не будем делать никаких дополнительных запросов в случае перезагрузки страницы с помощью кнопки возврата в браузере.

При первой загрузке атрибут loaded еще не установлен, поэтому мы продолжим делать запрос к нашей конечной точке. В случае успеха мы переберем каждую статью в возвращенном результате, найдем элемент ответа по его идентификатору и добавим к нему наш «непрочитанный» класс CSS.

Брызги!

Фрагментное кэширование многократно используемого контента и добавление JavaScript на ваши страницы для добавления в дальнейшем специфичных для пользователя динамических битов могут ускорить начальное время отклика вашего приложения за счет обслуживания большей части важного контента прямо из кеша. Хотя для выполнения дополнительного запроса и обновления страницы после загрузки основного содержимого требуется время, отсрочка динамического содержимого сделает ваше приложение более быстрым, даже если полный запрос занимает больше времени, чем обслуживание всего, включая пользовательские части, из кеш.

Для более сложных настроек обязательно ознакомьтесь с Stimulus, библиотекой JavaScript, которая оборачивает шаблон брызг в структуру, которая связывает ваши HTML-представления с вашим JavaScript.

Мы надеемся, что вам понравилось это введение в JavaScript в приложениях Rails. Если у вас есть какие-либо комментарии относительно этой статьи, блога или любых других тем, которые вы хотели бы осветить, напишите нам в @AppSignal.

Первоначально опубликовано на blog.appsignal.com 17 июля 2018 г.