Как реализовать «кешированную» разбивку часто изменяющихся страниц содержимого

1656678013 kak realizovat keshirovannuyu razbivku chasto izmenyayushhihsya stranicz soderzhimogo

Никита Козлов

1*ZOBmn2PAMPUtQkidXEPjtg

Каждый раз, когда нам нужно отобразить много содержимого, хранящегося на сервере, мы разбиваем его на части, а затем загружаем их по части. Это распространенный подход, и есть несколько причин, почему он так великолепен:

  1. Это улучшает взаимодействие с пользователем. Загрузка одной небольшой страницы занимает меньше времени, поэтому пользователь может начать потреблять содержимое быстрее.
  2. Это уменьшает нагрузку на сеть. Одна страница небольшая и легкая с точки зрения пропускной способности. Кроме того, можно оптимизировать потребление аккумулятора и сети для мобильных устройств, регулируя размер одной страницы.
  3. Это уменьшает нагрузку на заднюю часть. Обработка меньших деталей производится быстрее, чем больших. Обычно пользователям не требуется все содержимое сразу, поэтому средняя нагрузка на пользователя ниже.

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

Определение задачи

Представим коллекцию, которая часто меняется с течением времени. К примеру, мы могли бы просмотреть список статей, за которые пользователь хлопал на Medium, или список пожеланий в приложении для покупок, или вообще историю любых действий других пользователей. Пользователи могут «добавлять» сколько угодно элементов в эту коллекцию.

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

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

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

  1. Пользователь открывает список статей, которым он хлопал здесь, на Medium. Первая страница извлекается из серверной части.
  2. После этого пользователь искал что-нибудь новое, нашел еще одну интересную статью и решил ее порекомендовать.
  3. Теперь они хотят еще раз проверить список рекомендуемых статей.
1*kickseJ7nvQxQLOLlOTHYaw

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

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

Другой подход

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

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

1*aY5DZA9XOAH17uDjCGTqAA

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

Чего мы действительно хотим?

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

Этот кэш будет хранить не партии, а отдельные элементы в течение определенного времени. Мы возьмем ответ, получим доступ к его HTTP-заголовкам и получим информацию о времени кэширования. Затем мы поместим каждый элемент отдельно в кэш. В следующий раз, когда нам понадобится отобразить элементы, мы можем легко получить доступ к кэшированным и пригласить всех остальных. В коде это смотрится проще, чем звучит:

Давайте быстро пройдемся по коду. метод getOrderedItemsIds() возвращает порядок элементов и разбивается на страницы. Важнейшая часть – метод getItemsByIds(). Это место, где мы сначала проверяем, какие элементы есть в кэше, а затем спрашиваем остальные из серверной части. Обратите внимание, что для простоты приведенный выше код синхронен и, вероятно, не компилируется.

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

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

Делать это лучше

Идентификаторы элементов обычно представляют собой небольшие объекты, например a Строка или Универсальный уникальный идентификатор (UUID). Поэтому мы можем отправлять более крупные страницы. Увеличение количества идентификаторов возвращаемых вызовом заказа элементов уменьшает количество вызовов без злоупотребления пропускной способностью сети.

К примеру, вместо запроса 20–40 идентификаторов элементов мы можем запросить 100–200. Позднее уровень пользовательского интерфейса может модерировать количество деталей элемента, которые нужно отобразить, и запрашивать их соответственно. Тогда последовательность вызовов будет выглядеть примерно так:

  1. Спрашивайте первые 100 идентификаторов элементов и храните их в памяти.
  2. Запрашивать детали для первых 20 элементов (конечно, кэшировать их) и показывать их пользователю.
  3. Когда пользователь прокрутит первые 20 элементов, попросите вторую партию из 20 деталей.
  4. Повторите предыдущий шаг три раза и выполните аналогичные действия для следующей страницы идентификаторов элементов.

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

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

Вывод

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

  1. Запрос на порядок элементов дает нам возможность кэшировать детали, даже если нам пришлось написать модифицированный HTTP-кэш.
  2. Кэширование деталей элемента приводит к уменьшению использования пропускной способности.
  3. Увеличенный размер страниц заказа уменьшает количество вызовов.

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

Никита Козлов (@Nikita_E_Kozlov) | Twitter
Последние твиты Никиты Козлова (@Nikita_E_Kozlov): “https://t.co/wmGSJ7snW1″twitter.com

Спасибо, что вы уделили время чтению этой статьи. Если вам это нравится, не забудьте нажать ? below.

Добавить комментарий

Ваш адрес email не будет опубликован.