Некоторое время назад я спросил, как сделать Дополнительные обновления с использованием кеша браузера. Здесь я даю краткое описание проблемы - для получения более подробного контекста, особенно причины, по которой я хочу это сделать, обратитесь к старому вопросу. Я бы хотел, чтобы вы обзор и улучшение мою идею решения (просто идею, так что не отправляйте меня обзор кода: D).
Клиент (одностраничное приложение) получает с сервера довольно большие списки. Это отлично работает и фактически экономит ресурсы сервера как
Некоторые из этих списков относятся к конкретному пользователю, другие - к группе пользователей, другие - к глобальному.
Все эти списки могут измениться в любое время, и мы никогда не хотим обслуживать устаревшие данные (HTTP-заголовки Cache-Control и Expires здесь не используются).
Мы используем 304 NOT MODIFIED, который помогает в случае изменения ничего такого.
Когда что-то меняется, изменения обычно небольшие, но HTTP вообще не поддерживает этот случай, поэтому мы должны отправить весь список, включая неизмененные части.
Вместо этого мы можем отправить дельту, но нет очевидного способа, как это может быть эффективно кэшировано браузером (кеширование в localStorage или аналогичном далеко не так хорошо, как я объяснил в моем связанном вопросе).
Важным свойством наших списков является то, что каждый элемент имеет уникальный id и последний измененный timestamp.
timestamp позволяет нам легко вычислить дельту, найдя элементы, которые недавно изменились.
id позволяет нам применять дельту, просто заменяя соответствующие элементы (список внутренне является Map<Id, Item>).
Это не сработает для удалений, но пока не будем их игнорировать.
Я предлагаю использовать несколько списков (любое число должно работать) разного размера, при этом более крупный список может кэшироваться в течение длительного времени. Предположим, подходящей единицей времени является день, и воспользуемся следующими тремя списками:
WEEK
Это базовый список, содержащий элементы все, поскольку они существовали в произвольный момент времени в текущая неделя.
DAY
Список, содержащий все элементы с изменилось на этой неделе, кроме сегодняшнего дня, поскольку они существовали в произвольный момент времени в сегодня.
Элементы, измененные сегодня, могут быть включены или не включены.
CURRENT
Список, содержащий все элементы, которые изменили Cегодня, поскольку они существуют прямо сейчас.
Клиент получает все три списка. Он начинается с WEEK, применяет DAY (т.е. вставляет новые элементы и заменяет старые) и, наконец, применяет CURRENT.
Предположим, что в списке 1000 элементов, из которых 10 элементов меняются в день.
Список WEEK содержит все 1000 элементов, но его можно кэшировать до конца недели.
Его точное содержимое не указано, и у разных клиентов могут быть разные его версии (при условии, что выполняется условие из вышеприведенного маркера).
Это позволяет серверу кэшировать данные в течение целой недели, но также позволяет отбрасывать их, поскольку обслуживание текущего состояния тоже нормально.
Список DAY содержит до 70 элементов и может храниться в кэше до конца дня.
Список CURRENT содержит до 10 элементов и может быть кэширован только до тех пор, пока что-либо не изменится.
Клиент ничего не должен знать об используемой шкале времени, но ему нужно знать количество списков, которые нужно запросить. "Классический" запрос вроде
GET /api/order/123 // get the whole list with up to date content
будут заменены тремя запросами вроде
GET /api/0,order/123 // get the WEEK list
GET /api/1,order/123 // get the DAY list
GET /api/2,order/123 // get the CURRENT list
Обычно изменения действительно такие, как описано, но иногда все элементы меняются сразу. Когда это происходит, тогда все три списка содержат все элементы, а это означает, что нам нужно обслуживать в три раза больше данных. К счастью, такие события очень редки (например, когда мы добавляем атрибут), но я хотел бы увидеть способ, позволяющий избежать таких всплесков?
Вы видите другие проблемы с этой идеей?
Есть ли какое-либо решение для удаления, кроме простой пометки элементов как удаленных и откладывания физического удаления до истечения срока действия кешей (т.е. до конца недели в моем примере).
Есть улучшения?





Да, я вижу в этом большие проблемы. То, что это большой список, подразумевает, что клиенту предстоит много работы, чтобы получить необходимые ему ресурсы. Это сильно влияет на производительность.
All these lists may change anytime and we never want to serve stale data
Таким образом, вы должны использовать большое время кеширования и URL-адреса для очистки кеша.
We're using 304 NOT MODIFIED
Это наихудший способ решить проблему. Большая часть затрат на извлечение связана с задержкой. Если вы отвечаете с ответом 304, значит, вы уже понесли большую часть затрат - это будет особенно заметно, когда вы имеете дело с небольшими фрагментами данных. HTTP / 2 помогает (по сравнению с 1.0 и 1.1), но не снижает затрат.
Я бы также поставил под сомнение многие предположения, сделанные в вашем первоначальном вопросе.
К сожалению, я действительно не понимаю, как использовать URL-адреса блокировки кеша. Переключение с style.v1.css на style.v2.css можно жестко запрограммировать в клиенте, поскольку изменение содержимого style.css происходит при обновлении клиента. Изменение содержимого /api/orders может произойти в любое время, и клиент просто не может знать. Даже если бы мы предоставили /api/orders?version=123456, клиент все равно будет спрашивать сервер, когда версия для загрузки.
Я предполагаю, что вы понимаете следующие общие проблемы своего подхода:
localStorage, это немного «умно», что имеет последствия для долгосрочной ремонтопригодности. Необходимы четкие документы и набор тестов.Исходя из этого, мне нравится ваш подход.
Я могу изменить кое-что. Это добавляет немного гибкости, но также немного усложняет. Это может быть, а может и не быть хорошей идеей.
Вместо жесткого кодирования трех URL-адресов на клиенте вы можете отправлять явные гиперссылки в заголовках ответов. Вот как это может работать:
Клиент запрашивает жестко запрограммированную «точку входа»:
> GET /api/order/123?delta=all
< 200 OK
< Cache-Control: max-age=604800
< Delta-Location: /api/order/123?delta=604800
<
< [...your WEEK list...]
Увидев заголовок Delta-Location, клиент затем запрашивает его и применяет полученную дельту:
> GET /api/order/123?delta=604800
< 200 OK
< Cache-Control: max-age=86400
< Delta-Location: /api/order/123?delta=86400
<
< [...your DAY list...]
И так до тех пор, пока в ответе нет Delta-Location.
Это позволяет серверу в любое время в одностороннем порядке изменять структуру дельты. (Конечно, он все еще должен поддерживать старую структуру до тех пор, пока ее можно кэшировать на клиентах.)
В частности, это позволяет решить проблему всплесков. После выполнения изменения массы вы можете начать обслуживать гораздо меньшие дельты (с соответственно меньшим max-age), так что они исключают изменение массы. Затем вы постепенно увеличиваете размеры дельты с течением времени. Это потребует дополнительной логики / настройки на стороне сервера, но я уверен, что вы сможете понять, если пакеты действительно беспокоят вас.
В идеале вы должны разрешить Delta-Location по URL-адресу запроса, чтобы он вел себя как стандартные заголовки Location и Content-Location для единообразия и гибкости. Один из способов сделать это в JavaScript - это объект URL.
Другие вещи, которые вы можете настроить в этом подходе с гиперссылками:
max-age немного меньше, чем delta, чтобы учесть задержки в сети.Link вместо нестандартного Delta-Location. Но вам все равно понадобится нестандартный тип отношения, поэтому непонятно, что это принесет вам пользу.Спасибо за хорошие идеи! Я подумаю об этом. Несколько комментариев: +++. Что касается накладных расходов клиента, есть декомпрессия, в которой нужно обработать больше данных (в худшем случае на 200% больше, обычно всего несколько процентов), и замена, которая в основном бесплатна, поскольку мой клиент использует карту ({} в javascript) во всяком случае, внутренне. +++ "Ум" действительно может быть проблемой. Для angular я хочу предоставить HttpInterceptor, который молча выполняет всю работу. +++ Я хотел абстрагироваться от delta у клиентов, но я передумаю.
Что касается пакетов, то все сложнее, поскольку я не хочу увеличивать задержку и поэтому планировал, что клиент будет запрашивать все три части одновременно. Я думаю о том, чтобы позволить серверу отправить весь список по одному из ответов и сделать остальные пустыми с нулевым сроком действия. В заголовках я сообщу клиенту, что происходит, и позволю ему в следующий раз спросить «умнее» (используя что-то похожее на то, что вы описали).
Что касается влияния на производительность: при запуске клиент получает несколько списков одновременно, и это обычно означает некоторую задержку (в настоящее время в среднем менее одной секунды; в любом случае, наши пользователи не слишком заботятся о начальной задержке, поскольку они не случайные люди вводят нас в заблуждение случайными ссылками в Интернете).
+++"произносится, когда вы имеете дело с небольшими фрагментами данных" - Поэтому мы используем довольно большие списки. После некоторой начальной задержки у клиента есть все необходимое.