Инвалидация кэша: стратегии и реализация

idb-keyval — это легковесная библиотека для работы с IndexedDB через простой интерфейс ключ-значение. Основная задача при работе с кэшем — гарантировать, что данные, получаемые из хранилища, актуальны. Для этого используются стратегии инвалидации кэша, которые позволяют автоматически или вручную удалять устаревшие записи.

Методы хранения и извлечения данных

Базовые методы библиотеки:

import { get, set, del, clear } from 'idb-keyval';

// Сохранение данных
await set('userProfile', { name: 'Alice', age: 30 });

// Получение данных
const profile = await get('userProfile');

// Удаление данных
await del('userProfile');

// Очистка всего хранилища
await clear();

Эти методы являются фундаментом для реализации любой стратегии кэширования. Для контроля актуальности данных важно внедрять метаданные, такие как время создания или время жизни (TTL — Time To Live).

Временные метки и TTL

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

const setWithTTL = async (key, value, ttl) => {
    const record = {
        value,
        expiry: Date.now() + ttl
    };
    await set(key, record);
};

const getWithTTL = async (key) => {
    const record = await get(key);
    if (!record) return null;
    if (Date.now() > record.expiry) {
        await del(key);
        return null;
    }
    return record.value;
};

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

  • expiry хранит точку во времени, после которой данные считаются устаревшими.
  • При извлечении проверяется актуальность записи; при необходимости она удаляется автоматически.
  • TTL может быть задан индивидуально для разных типов данных.

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

1. FIFO (First In, First Out) Подходит для кэшей с ограниченной емкостью. Старые записи удаляются первыми, чтобы освободить место для новых. Реализуется через хранение очереди ключей:

const cacheQueue = [];

const setFIFO = async (key, value, maxSize = 50) => {
    await set(key, value);
    cacheQueue.push(key);
    if (cacheQueue.length > maxSize) {
        const oldestKey = cacheQueue.shift();
        await del(oldestKey);
    }
};

2. LRU (Least Recently Used) Удаляются данные, которые дольше всего не использовались. Для этого нужно вести учет времени последнего доступа:

const setLRU = async (key, value) => {
    const record = { value, lastAccessed: Date.now() };
    await set(key, record);
};

const getLRU = async (key) => {
    const record = await get(key);
    if (!record) return null;
    record.lastAccessed = Date.now();
    await set(key, record);
    return record.value;
};

3. Инвалидация по событиям Данные кэша можно сбрасывать при возникновении определенных событий, например, обновлении данных на сервере:

const invalidateOnUpdate = async (keys) => {
    for (const key of keys) {
        await del(key);
    }
};

Комбинированные подходы

В реальных приложениях часто используют комбинацию TTL и LRU:

  • TTL обеспечивает автоматическое удаление устаревших данных.
  • LRU предотвращает переполнение хранилища, удаляя редко используемые записи.

Пример комбинированного подхода:

const setCache = async (key, value, ttl, cacheSize = 50) => {
    await setLRU(key, { value, expiry: Date.now() + ttl });
    if (cacheQueue.length > cacheSize) {
        const lruKey = cacheQueue.shift(); // удаляем наименее используемый
        await del(lruKey);
    }
};

const getCache = async (key) => {
    const record = await getLRU(key);
    if (!record || Date.now() > record.expiry) {
        await del(key);
        return null;
    }
    return record.value;
};

Учет ошибок и откатов

Работа с IndexedDB через idb-keyval может быть прервана или завершена ошибкой. Рекомендуется оборачивать операции в блоки try-catch:

try {
    await set('config', { theme: 'dark' });
} catch (err) {
    console.error('Ошибка сохранения данных в IndexedDB:', err);
}

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

const safeDel = async (key) => {
    try {
        await del(key);
    } catch (err) {
        console.warn(`Не удалось удалить ключ ${key}:`, err);
    }
};

Мониторинг и аналитика кэша

Для оптимизации стратегии инвалидации важно вести статистику использования:

  • Количество хитов и промахов кэша.
  • Частота доступа к конкретным ключам.
  • Объем данных, хранящихся в IndexedDB.

Простейший пример счетчиков:

const cacheStats = { hits: 0, misses: 0 };

const getWithStats = async (key) => {
    const value = await get(key);
    if (value) cacheStats.hits++;
    else cacheStats.misses++;
    return value;
};

Это позволяет адаптировать стратегию под реальные условия работы приложения и минимизировать задержки при доступе к данным.

Заключение по подходам

Инвалидация кэша с использованием idb-keyval — это не просто удаление старых данных. Грамотное внедрение TTL, LRU и событийной инвалидации обеспечивает:

  • Актуальность информации.
  • Контроль объема хранилища.
  • Высокую производительность при обращении к данным.

Сочетание этих подходов позволяет создавать гибкие и надежные клиентские кэши, адаптированные под конкретные сценарии использования.