Стратегия инвалидации кэша

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


Основные подходы к инвалидации кэша

Workbox поддерживает несколько стратегий обновления ресурсов, каждая из которых решает разные задачи:

  1. Cache First (кэш прежде всего) Этот подход сначала ищет ресурс в кэше, а если его нет — обращается к сети. Используется для статических ресурсов, которые редко изменяются (например, изображения, шрифты).

    import {registerRoute} from 'workbox-routing';
    import {CacheFirst} from 'workbox-strategies';
    import {CacheableResponsePlugin} from 'workbox-cacheable-response';
    import {ExpirationPlugin} from 'workbox-expiration';
    
    registerRoute(
      ({request}) => request.destination === 'image',
      new CacheFirst({
        cacheName: 'images-cache',
        plugins: [
          new CacheableResponsePlugin({statuses: [0, 200]}),
          new ExpirationPlugin({maxEntries: 50, maxAgeSeconds: 30 * 24 * 60 * 60}) // 30 дней
        ]
      })
    );

    Ключевые моменты:

    • ExpirationPlugin контролирует время жизни и количество кэшированных объектов.
    • Слишком долгий срок кэширования может привести к устаревшей информации, поэтому важно подобрать оптимальные значения maxAgeSeconds и maxEntries.
  2. Network First (сеть прежде всего) Используется для ресурсов, которые часто обновляются (API-запросы, данные в реальном времени). Сначала запрашивается сеть, а при сбое используется кэш.

    import {NetworkFirst} from 'workbox-strategies';
    
    registerRoute(
      ({url}) => url.pathname.startsWith('/api/'),
      new NetworkFirst({
        cacheName: 'api-cache',
        plugins: [
          new CacheableResponsePlugin({statuses: [0, 200]}),
          new ExpirationPlugin({maxEntries: 100, maxAgeSeconds: 5 * 60}) // 5 минут
        ]
      })
    );

    Ключевые моменты:

    • Сохраняет последние успешные ответы сети.
    • Обеспечивает быстрый доступ при отсутствии сети, но при этом гарантирует актуальность данных при подключении к интернету.
  3. Stale While Revalidate (устаревший, пока обновляется) Позволяет использовать кэшированную версию мгновенно, одновременно обновляя ресурс в фоне. Подходит для ресурсов, где небольшая задержка актуализации допустима.

    import {StaleWhileRevalidate} from 'workbox-strategies';
    
    registerRoute(
      ({request}) => request.destination === 'script' || request.destination === 'style',
      new StaleWhileRevalidate({
        cacheName: 'static-resources',
        plugins: [
          new CacheableResponsePlugin({statuses: [0, 200]})
        ]
      })
    );

    Ключевые моменты:

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

Контроль версии кэша

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

const CACHE_VERSION = 'v2';
const STATIC_CACHE = `static-cache-${CACHE_VERSION}`;

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(STATIC_CACHE).then(cache => cache.addAll([
      '/index.html',
      '/styles.css',
      '/main.js'
    ]))
  );
});

self.addEventListener('activate', event => {
  event.waitUntil(
    caches.keys().then(keys => 
      Promise.all(
        keys.filter(key => key !== STATIC_CACHE)
            .map(key => caches.delete(key))
      )
    )
  );
});

Особенности подхода:

  • Устаревшие версии автоматически удаляются на этапе activate.
  • Явное указание версии упрощает контроль над изменениями в ресурсах.

Использование Workbox Expiration Plugin

ExpirationPlugin позволяет автоматически очищать устаревший кэш по количеству элементов или времени жизни:

import {ExpirationPlugin} from 'workbox-expiration';

new CacheFirst({
  cacheName: 'dynamic-cache',
  plugins: [
    new ExpirationPlugin({
      maxEntries: 100,
      maxAgeSeconds: 24 * 60 * 60 // 1 день
    })
  ]
});

Важно помнить:

  • maxEntries защищает от переполнения кэша.
  • maxAgeSeconds гарантирует, что устаревшие ресурсы будут удалены, а свежие — загружены заново.

Инвалидация по хешу

Для файлов с уникальным именем или хешем (например, main.abc123.js) Workbox позволяет обходиться без явной версии кэша, так как изменение имени файла автоматически приводит к новому кэшированию. Это особенно удобно для сборки проектов с Webpack или Vite:

registerRoute(
  ({request}) => request.destination === 'script',
  new CacheFirst({
    cacheName: 'js-cache',
    plugins: [
      new CacheableResponsePlugin({statuses: [0, 200]})
    ]
  })
);

Преимущества хеширования:

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

Инвалидация динамических данных

Для API и других часто обновляемых ресурсов рекомендуется использовать комбинацию стратегий:

  • Network First для критичных данных.
  • Stale While Revalidate для не критичных, но требующих быстрой загрузки.
  • Ограничение количества кэшированных элементов через ExpirationPlugin.

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


Практические советы

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

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