Каков предлагаемый способ хранения ресурса ETag?
Где я должен хранить ETag для данного ресурса?
Подход A: вычислять на лету
Получите ресурс и вычисляйте ETag на лету при каждом запросе:
$resource = $repository->findByPK($id); // query
// Compute ETag
$etag = md5($resource->getUpdatedAt());
$response = new Response();
$response->setETag($etag);
$response->setLastModified($resource->getUpdatedAt());
if($response->isNotModified($this->getRequest())) {
return $response; // 304
}
Подход B: хранение на уровне базы данных
Экономия времени процессора при создании INSERT
а также UPDATE
операторы немного медленнее (мы используем триггеры для обновления ETag):
$resource = $repository->findByPK($id); // query
$response = new Response();
$response->setETag($resource->getETag());
$response->setLastModified($resource->getUpdatedAt());
if ($response->isNotModified($this->getRequest())) {
return $response;
}
Подход C: кэширование ETag
Это похоже на подход B, но ETag хранится в некотором промежуточном программном обеспечении кеша.
4 ответа
Я полагаю, это будет зависеть от стоимости наличия предметов, входящих в сам ETag.
Я имею в виду, пользователь отправляет запрос на данный ресурс; это должно вызвать операцию поиска в базе данных (или какую-либо другую операцию).
Если поиск - это что-то простое, например, выборка файла, тогда запрос статистики файла выполняется быстро, и нет необходимости хранить что-либо где-либо: достаточно MD5 пути к файлу плюс время его обновления.
Если получение подразумевает запрос к базе данных, то это зависит от того, можно ли разбить запрос без потери производительности (например, пользователь запрашивает статью по идентификатору. Вы можете извлечь соответствующие данные только из таблицы статьи. Таким образом, "попадание" в кэш будет влечет за собой один SELECT по первичному ключу, но "промах" в кеше означает, что вам придется снова запрашивать базу данных, тратя впустую первый запрос - или нет - в зависимости от вашей модели).
Если запрос (или последовательность запросов) хорошо разложим (и получающийся в результате код поддерживается), то я бы снова использовал динамический ETag.
Если это не так, то большая часть зависит от стоимости запроса и общей стоимости обслуживания решения с хранимым ETag. Если запрос является дорогостоящим (или вывод громоздким), а INSERT/UPDATE немного, то (и, я думаю, только тогда) будет выгодно хранить вторичный столбец (или таблицу) с ETag.
Что касается промежуточного программного обеспечения для кэширования, я не знаю. Если бы у меня была структура, отслеживающая все для меня, я мог бы сказать "делай это" - промежуточное программное обеспечение должно заботиться и реализовывать вышеприведенные пункты. Если промежуточное программное обеспечение не зависит от реализации (маловероятно, если только это не "зарезка и вставка", которая не является неслыханной), тогда будет или риск "отсеивания" обновлений ресурса, или, возможно, чрезмерная неловкость при вызове некоторого API очистки кеша при обновлениях. Оба фактора необходимо будет оценить по сравнению с улучшением нагрузки, предлагаемым поддержкой ETag.
Я не думаю, что в этом случае существует "серебряная пуля".
Изменить: в вашем случае есть небольшая - или даже нет - разница между случаями A и B. Чтобы иметь возможность реализовать getUpdatedAt(), вам нужно будет сохранить время обновления в модели.
В этом конкретном случае я думаю, что было бы проще и более легко поддерживать динамический, точный расчет ETag (случай A). В любом случае затраты на поиск возникают, а явные затраты на вычисление - это затраты на вычисления MD5, которые действительно бывают быстрыми и полностью связаны с процессором. Преимущества в ремонтопригодности и простоте, на мой взгляд, огромны.
На полусвязанной ноте мне приходит в голову, что в некоторых случаях (нечастые обновления базы данных и гораздо более частые запросы к ней) это может быть выгодно и почти прозрачно для реализации глобального Last-Modified
время для всей базы данных. Если база данных не изменилась, то ни один запрос к базе данных не сможет вернуть различные ресурсы, независимо от того, что это за запрос. В такой ситуации нужно только хранить Last-Modified
Глобальный флаг в каком-то легком и быстром для извлечения месте (не обязательно в базе данных). Например
function dbModified() {
touch('.last-update'); // creates the file, or updates its modification time
}
в любой UPDATE/DELETE
код. Затем ресурс добавит заголовок
function sendModified() {
$tsstring = gmdate('D, d M Y H:i:s ', filemtime('.last-update')) . 'GMT';
Header("Last-Modified: " . $tsstring);
}
информировать браузер о времени модификации этого ресурса.
Затем любой запрос на ресурс, включая If-Modified-Since
может быть возвращен обратно с 304 без какого-либо доступа к уровню постоянства (или, по крайней мере, к сохранению доступа ко всем постоянным ресурсам). Нет необходимости обновлять время на уровне записи:
function ifNotModified() {
// Check out timezone settings. The GMT helps but it's not always the ticket
$ims = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])
: -1; // This ensures the test will FAIL
if (filemtime('.last-update') <= $ims) {
// The database was never updated after the resource retrieval.
// There's no way the resource may have changed.
exit(Header('HTTP/1.1 304 Not Modified'));
}
}
Можно было бы сделать вызов ifNotModified() как можно раньше на маршруте поставки ресурсов, sendModified - как можно раньше в выходном коде ресурса, а dbModified() - везде, где база данных значительно изменяется в зависимости от ресурсов (т. Е. Вы можете и, вероятно, должны избегать этого при регистрации статистики доступа к базе данных, если она не влияет на содержимое ресурсов).
По моему мнению, постоянные ETag - это ПЛОХАЯ ИДЕЯ, если ваша бизнес-логика НЕ СОХРАНЯЕТ ETag. Например, когда вы пишете приложение для отслеживания пользователей на основе ETag, и это бизнес-функция:).
Потенциальная экономия времени выполнения будет небольшой или не будет существующей. Плохие стороны этого решения очевидны и растут по мере роста вашего приложения.
Согласно спецификации Ресурс в одной и той же версии должен давать разные E-метки в зависимости от конечной точки, из которой был получен.
С http://en.wikipedia.org/wiki/HTTP_ETag:
"Сравнение ETag имеет смысл только в отношении одного URL-адреса. ETag для ресурсов, полученных из разных URL-адресов, может быть равным или не равным, поэтому из их сравнения не может быть никакого значения".
Из этого вы можете сделать вывод, что вам следует сохранить не только ETag, но и его конечную точку и хранить столько ETag, сколько у вас есть точек. Звучит безумно?
Даже если вы хотите игнорировать HTTP-спецификацию и просто предоставить один Etag для Entity без метаданных о его конечных точках. Вы по-прежнему связываете как минимум 2 слоя (кеширование и бизнес-логика), которые в идеале не должны смешиваться. Идея создания Entity (а не некоторых потерянных данных) состоит в том, чтобы отделить и не связать в них бизнес-логику, а не загрязнять их такими вещами, как работа в сети, просмотр данных на уровне или... кэширование.
IHMO, это зависит от того, как часто обновляются ресурсы по сравнению с частотой чтения ресурсов.
Если каждый ETag считывается 1 или 2 раза между модификациями, то просто рассчитайте их на лету.
Если ваши ресурсы читаются намного чаще, чем они обновляются, то вам лучше их кэшировать, вычисляя ETag каждый раз, когда ресурс изменяется (так что вам не придется беспокоиться об устаревших кэшированных ETag).
Если ETag изменяются почти так же часто, как они читаются, то я все равно их кеширую, тем более что кажется, что ваши ресурсы хранятся в базе данных.
Если вашим ресурсом является, например, строка в базе данных PostgreSQL, вы можете определить функцию для вычисления значения ETag в запросе SELECT. Затем вы можете создать индекс с этой (точно такой же) функцией, чтобы фактические значения кэшировались внутри. Никакого промежуточного программного обеспечения не требуется.