Ограничение объёма и поведение при переполнении

Одной из важных особенностей любой системы локального хранения данных является ограничение доступного объёма. Несмотря на то что библиотека localForage предоставляет единый удобный интерфейс для работы с данными, фактическое хранилище создаётся средствами браузера. Именно браузер определяет максимально допустимый размер базы данных, правила распределения дискового пространства и механизм обработки ситуаций, когда свободное место заканчивается.

Понимание этих ограничений необходимо для проектирования надёжных веб-приложений, способных работать с большими объёмами информации без неожиданных ошибок.


Откуда берутся ограничения объёма

localForage не реализует собственное файловое хранилище. Библиотека использует один из поддерживаемых механизмов браузера:

  • IndexedDB;
  • WebSQL (если доступен);
  • localStorage.

Каждый из этих механизмов обладает собственными лимитами.

При сохранении данных через localForage фактическая запись выполняется в выбранный драйвер. Поэтому максимальный объём определяется не самой библиотекой, а ограничениями используемой платформы.

Например:

await localforage.setItem("users", largeObject);

С точки зрения localForage операция выглядит одинаково независимо от драйвера, однако внутренние лимиты могут существенно отличаться.


Ограничения localStorage

Если localForage вынужден использовать localStorage в качестве резервного механизма хранения, доступный объём становится весьма ограниченным.

В большинстве браузеров лимит составляет около:

  • 5 МБ на источник данных (origin);
  • иногда до 10 МБ в зависимости от браузера.

При достижении лимита дальнейшая запись становится невозможной.

Пример потенциально проблемного кода:

const hugeText = "A".repeat(10_000_000);

await localforage.setItem("cache", hugeText);

Если активен драйвер localStorage, подобная запись почти гарантированно завершится ошибкой.

Особенностью localStorage является то, что данные сохраняются в виде строк, что дополнительно увеличивает занимаемый объём из-за сериализации.


Ограничения IndexedDB

Основным драйвером localForage обычно является IndexedDB.

В отличие от localStorage, IndexedDB рассчитан на значительно большие объёмы данных.

Типичные ограничения могут достигать:

  • десятков мегабайт;
  • сотен мегабайт;
  • нескольких гигабайт.

Конкретное значение зависит от:

  • браузера;
  • операционной системы;
  • свободного места на диске;
  • политики хранения данных конкретного браузера.

Современные браузеры чаще используют не фиксированный лимит, а процент от доступного пространства устройства.

Например, если на диске много свободного места, приложению может быть разрешено хранить весьма крупные объёмы данных.


Ограничения WebSQL

При использовании WebSQL ситуация зависит от реализации браузера.

Исторически многие браузеры применяли квоты, которые могли:

  • автоматически расширяться;
  • требовать подтверждения пользователя;
  • ограничиваться определённым размером.

Поскольку WebSQL считается устаревшей технологией, поведение разных браузеров может существенно различаться.


Квоты браузера

Браузеры используют механизм квотирования для предотвращения чрезмерного использования дискового пространства отдельными сайтами.

Квота представляет собой максимально допустимый объём данных для конкретного происхождения (origin).

Под origin понимается комбинация:

протокол + домен + порт

Например:

https://example.com

и

https://api.example.com

получают разные квоты хранения.

Это означает, что данные одного сайта не влияют на доступное пространство другого сайта.


Что происходит при достижении лимита

Когда приложение пытается сохранить данные сверх разрешённого объёма, браузер отклоняет операцию записи.

Для localForage это выглядит как отклонённый Promise.

Пример:

try {
    await localforage.setItem("largeData", hugeData);
} catch (error) {
    console.error("Недостаточно места", error);
}

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

  • потерей данных;
  • незавершёнными операциями;
  • повреждённым кэшем;
  • частично сохранёнными структурами.

Поэтому обработка исключений становится обязательной частью работы с большими объёмами данных.


Ошибка QuotaExceededError

Наиболее распространённая ошибка переполнения называется:

QuotaExceededError

Она означает, что выделенный объём памяти исчерпан.

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

QuotaExceededError

или

DOMException: Quota exceeded

или

The quota has been exceeded.

Поэтому в коде рекомендуется анализировать сам факт возникновения ошибки, а не конкретный текст сообщения.


Переполнение при сохранении бинарных данных

Особое внимание требуется при работе с бинарными объектами:

  • Blob;
  • File;
  • ArrayBuffer;
  • TypedArray.

Пример:

const file = new Blob([largeBinaryData]);

await localforage.setItem("backup", file);

Файл размером 100 МБ может успешно сохраниться в одном браузере и вызвать переполнение в другом.

Кроме того, несколько крупных файлов способны быстро исчерпать выделенную квоту.


Неочевидный рост объёма данных

Размер объекта в памяти приложения не всегда совпадает с размером после сохранения.

Например:

const data = {
    users: bigArray
};

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

  • метаданные структуры;
  • кодировка строк;
  • внутренние индексы базы;
  • служебная информация движка хранения.

По этой причине фактический размер базы может оказаться значительно больше ожидаемого.


Частичное заполнение хранилища

Достижение лимита редко происходит мгновенно.

Чаще наблюдается постепенное заполнение пространства:

10 МБ
25 МБ
60 МБ
120 МБ
200 МБ
...

На определённом этапе очередная операция записи завершается ошибкой.

При этом ранее сохранённые данные обычно остаются доступными.

Например:

await localforage.setItem("cache1", data1);
await localforage.setItem("cache2", data2);
await localforage.setItem("cache3", data3);

Если места не хватает только для cache3, значения cache1 и cache2 продолжают существовать.


Стратегии предотвращения переполнения

Ограничение размера кэша

Наиболее распространённый подход — хранить только актуальные данные.

Пример:

const MAX_ITEMS = 100;

После достижения лимита старые записи удаляются.

Такой механизм напоминает работу LRU-кэша.


Удаление устаревших записей

Каждый объект может содержать время создания:

{
    value: data,
    createdAt: Date.now()
}

При запуске приложения выполняется очистка старых элементов.

Пример логики:

if (item.createdAt < expirationTime) {
    await localforage.removeItem(key);
}

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


Разделение крупных данных

Большие объекты нежелательно хранить единым блоком.

Плохо:

await localforage.setItem("allUsers", usersArray);

Лучше:

await localforage.setItem("user_1", user1);
await localforage.setItem("user_2", user2);
await localforage.setItem("user_3", user3);

Преимущества:

  • проще обновлять данные;
  • проще удалять записи;
  • снижается риск отказа из-за одной огромной операции записи.

Сжатие данных

В некоторых сценариях данные могут храниться в сжатом виде.

Например:

const compressed = compress(data);

await localforage.setItem("cache", compressed);

Это особенно эффективно для:

  • больших JSON-документов;
  • текстовых данных;
  • логов;
  • экспортируемых настроек.

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


Проверка доступного пространства

Современные браузеры предоставляют API оценки объёма хранилища.

Пример:

const estimate = await navigator.storage.estimate();

console.log(estimate.usage);
console.log(estimate.quota);

Результат может содержать:

{
    usage: 45000000,
    quota: 2000000000
}

Где:

  • usage — уже занятое пространство;
  • quota — доступный лимит.

Такая информация позволяет заранее контролировать заполнение базы.


Очистка хранилища при нехватке места

В некоторых приложениях используется автоматическая очистка.

Пример:

try {
    await localforage.setItem(key, value);
} catch (error) {
    await clearOldCache();
    await localforage.setItem(key, value);
}

Алгоритм состоит из двух этапов:

  1. Попытка записи.
  2. Освобождение места при ошибке.

После удаления устаревших данных операция повторяется.


Влияние режима инкогнито

В приватных режимах браузеров ограничения часто становятся более жёсткими.

Некоторые браузеры:

  • уменьшают доступную квоту;
  • очищают данные после закрытия окна;
  • используют временное хранилище;
  • полностью запрещают определённые механизмы хранения.

Поэтому приложение, успешно работающее в обычном режиме, может столкнуться с ограничениями в режиме инкогнито значительно раньше.


Автоматическое удаление данных браузером

Даже успешное сохранение информации не гарантирует её бессрочное существование.

При нехватке дискового пространства браузер может:

  • очистить временные данные;
  • удалить редко используемые базы;
  • освободить место для других сайтов.

Это особенно актуально для мобильных устройств, где объём памяти ограничен.

Следовательно, localForage следует рассматривать как механизм локального хранения с вероятностью очистки, а не как абсолютную замену серверной базе данных.


Проектирование приложений с учётом ограничений

При работе с localForage крупные объёмы данных требуют соблюдения нескольких принципов:

  • обработка ошибок записи;
  • контроль размера кэша;
  • периодическая очистка устаревших данных;
  • разбиение крупных структур на небольшие записи;
  • использование IndexedDB вместо localStorage;
  • мониторинг заполненности хранилища;
  • готовность к автоматическому удалению данных браузером;
  • резервное восстановление информации из внешних источников.

Такой подход позволяет создавать приложения, устойчивые к переполнению локального хранилища и корректно работающие даже при жёстких ограничениях браузерных квот.