Кеш

схема кеширования

Что такое кэш?

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

Как работает кэширование?

Данные в кэше обычно хранятся в аппаратных средствах быстрого доступа, таких как оперативная память (Random-access memory), а также могут использоваться в сочетании с программным компонентом. Основная цель кэша - повысить производительность поиска данных за счет снижения необходимости доступа к более медленному уровню хранения.

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

Обзор кэширования

Движки RAM и In-Memory

Благодаря высокой частоте запросов или IOPS (операций ввода-вывода в секунду), поддерживаемых системами RAM и In-Memory, кэширование позволяет повысить производительность поиска данных и снизить затраты при масштабировании. Для поддержки такого же масштаба при использовании традиционных баз данных и дискового оборудования потребовались бы дополнительные ресурсы. Эти дополнительные ресурсы увеличивают стоимость и все равно не позволяют достичь производительности с низкой задержкой, обеспечиваемой кэшем In-Memory.

Приложения

Кэши могут применяться и использоваться на различных технологических уровнях, включая операционные системы, сетевые уровни, включая сети доставки контента (CDN) и DNS, веб-приложения и базы данных. Вы можете использовать кэширование для значительного снижения задержки и повышения IOPS для многих приложений с высокой нагрузкой на чтение, таких как порталы вопросов и ответов, игры, обмен медиа и социальные сети. Кэшированная информация может включать результаты запросов к базе данных, вычислений, требующих больших вычислительных затрат, запросов/ответов API и веб-артефактов, таких как HTML, JavaScript и файлы изображений. Вычислительно-интенсивные рабочие нагрузки, манипулирующие наборами данных, такие как рекомендательные системы и высокопроизводительные вычислительные симуляции, также выигрывают от использования слоя данных In-Memory в качестве кэша. В этих приложениях доступ к очень большим наборам данных должен осуществляться в режиме реального времени на кластерах машин, которые могут состоять из сотен узлов. Из-за скорости основного оборудования манипулирование этими данными в дисковом хранилище является значительным узким местом для этих приложений.

Шаблоны проектирования

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

Лучшие практики кэширования

При внедрении уровня кэширования важно понимать достоверность кэшируемых данных. Успешное кэширование приводит к высокому коэффициенту попаданий, что означает, что данные присутствовали при получении. Промах кэша происходит, когда найденные данные отсутствуют в кэше. Для соответствующего истечения срока хранения данных могут применяться такие элементы управления, как TTL (время жизни). Еще одним аспектом может быть необходимость высокой доступности кэш-среды, что может быть обеспечено механизмами In-Memory, такими как Redis. В некоторых случаях слой In-Memory может использоваться в качестве автономного уровня хранения данных в отличие от кэширования данных из основного местоположения. В этом сценарии важно определить соответствующие RTO (Recovery Time Objective - время, необходимое для восстановления после сбоя) и RPO (Recovery Point Objective - последняя точка или транзакция, зафиксированная при восстановлении) для данных, хранящихся в механизме In-Memory, чтобы определить, подходит ли это. Стратегии проектирования и характеристики различных движков In-Memory могут быть применены для удовлетворения большинства требований RTO и RPO.

Пример

В примере кода caches является свойством ServiceWorkerGlobalScope. Оно содержит объект CacheStorage, с помощью которого можно получить доступ к интерфейсу CacheStorage.

Примечание: В Chrome перейдите по адресу chrome://inspect/#service-workers и щелкните на ссылке inspect под зарегистрированным service worker, чтобы просмотреть протоколирование различных действий, выполняемых скриптом service-worker.js.

const CACHE_VERSION = 1;
const CURRENT_CACHES = {
  font: `font-cache-v${CACHE_VERSION}`,
};

self.addEventListener("activate", (event) => {
  // Delete all caches that aren't named in CURRENT_CACHES.
  // While there is only one cache in this example, the same logic
  // will handle the case where there are multiple versioned caches.
  const expectedCacheNamesSet = new Set(Object.values(CURRENT_CACHES));
  event.waitUntil(
    caches.keys().then((cacheNames) =>
      Promise.all(
        cacheNames.map((cacheName) => {
          if (!expectedCacheNamesSet.has(cacheName)) {
            // If this cache name isn't present in the set of
            // "expected" cache names, then delete it.
            console.log("Deleting out of date cache:", cacheName);
            return caches.delete(cacheName);
          }
        }),
      ),
    ),
  );
});

self.addEventListener("fetch", (event) => {
  console.log("Handling fetch event for", event.request.url);

  event.respondWith(
    caches.open(CURRENT_CACHES.font).then((cache) => {
      return cache
        .match(event.request)
        .then((response) => {
          if (response) {
            // If there is an entry in the cache for event.request,
            // then response will be defined and we can just return it.
            // Note that in this example, only font resources are cached.
            console.log(" Found response in cache:", response);

            return response;
          }

          // Otherwise, if there is no entry in the cache for event.request,
          // response will be undefined, and we need to fetch() the resource.
          console.log(
            " No response for %s found in cache. About to fetch " +
              "from network…",
            event.request.url,
          );

          // We call .clone() on the request since we might use it
          // in a call to cache.put() later on.
          // Both fetch() and cache.put() "consume" the request,
          // so we need to make a copy.
          // (see https://developer.mozilla.org/en-US/docs/Web/API/Request/clone)
          return fetch(event.request.clone()).then((response) => {
            console.log(
              "  Response for %s from network is: %O",
              event.request.url,
              response,
            );

            if (
              response.status < 400 &&
              response.headers.has("content-type") &&
              response.headers.get("content-type").match(/^font\//i)
            ) {
              // This avoids caching responses that we know are errors
              // (i.e. HTTP status code of 4xx or 5xx).
              // We also only want to cache responses that correspond
              // to fonts, i.e. have a Content-Type response header that
              // starts with "font/".
              // Note that for opaque filtered responses
              // https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
              // we can't access to the response headers, so this check will
              // always fail and the font won't be cached.
              // All of the Google Web Fonts are served from a domain that
              // supports CORS, so that isn't an issue here.
              // It is something to keep in mind if you're attempting
              // to cache other resources from a cross-origin
              // domain that doesn't support CORS, though!
              console.log("  Caching the response to", event.request.url);
              // We call .clone() on the response to save a copy of it
              // to the cache. By doing so, we get to keep the original
              // response object which we will return back to the controlled
              // page.
              // https://developer.mozilla.org/en-US/docs/Web/API/Request/clone
              cache.put(event.request, response.clone());
            } else {
              console.log("  Not caching the response to", event.request.url);
            }

            // Return the original response object, which will be used to
            // fulfill the resource request.
            return response;
          });
        })
        .catch((error) => {
          // This catch() will handle exceptions that arise from the match()
          // or fetch() operations.
          // Note that a HTTP error response (e.g. 404) will NOT trigger
          // an exception.
          // It will return a normal response object that has the appropriate
          // error code set.
          console.error("  Error in fetch handler:", error);

          throw error;
        });
    }),
  );
});

Источники

  1. developer.mozilla.org/en-US/docs/Web/API/Cache,
  2. support.google.com/accounts/answer/32050?hl=en&co=GENIE.Platform%3DDesktop,
  3. aws.amazon.com/caching,
  4. docs.djangoproject.com/en/4.2/topics/cache.