Одной из важных особенностей любой системы локального хранения данных является ограничение доступного объёма. Несмотря на то что библиотека localForage предоставляет единый удобный интерфейс для работы с данными, фактическое хранилище создаётся средствами браузера. Именно браузер определяет максимально допустимый размер базы данных, правила распределения дискового пространства и механизм обработки ситуаций, когда свободное место заканчивается.
Понимание этих ограничений необходимо для проектирования надёжных веб-приложений, способных работать с большими объёмами информации без неожиданных ошибок.
localForage не реализует собственное файловое хранилище. Библиотека использует один из поддерживаемых механизмов браузера:
Каждый из этих механизмов обладает собственными лимитами.
При сохранении данных через localForage фактическая запись выполняется в выбранный драйвер. Поэтому максимальный объём определяется не самой библиотекой, а ограничениями используемой платформы.
Например:
await localforage.setItem("users", largeObject);
С точки зрения localForage операция выглядит одинаково независимо от драйвера, однако внутренние лимиты могут существенно отличаться.
Если localForage вынужден использовать localStorage в качестве резервного механизма хранения, доступный объём становится весьма ограниченным.
В большинстве браузеров лимит составляет около:
При достижении лимита дальнейшая запись становится невозможной.
Пример потенциально проблемного кода:
const hugeText = "A".repeat(10_000_000);
await localforage.setItem("cache", hugeText);
Если активен драйвер localStorage, подобная запись почти гарантированно завершится ошибкой.
Особенностью localStorage является то, что данные сохраняются в виде строк, что дополнительно увеличивает занимаемый объём из-за сериализации.
Основным драйвером localForage обычно является IndexedDB.
В отличие от localStorage, IndexedDB рассчитан на значительно большие объёмы данных.
Типичные ограничения могут достигать:
Конкретное значение зависит от:
Современные браузеры чаще используют не фиксированный лимит, а процент от доступного пространства устройства.
Например, если на диске много свободного места, приложению может быть разрешено хранить весьма крупные объёмы данных.
При использовании 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
или
DOMException: Quota exceeded
или
The quota has been exceeded.
Поэтому в коде рекомендуется анализировать сам факт возникновения ошибки, а не конкретный текст сообщения.
Особое внимание требуется при работе с бинарными объектами:
Пример:
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);
Это особенно эффективно для:
Недостатком становится дополнительная нагрузка на процессор при сжатии и распаковке.
Современные браузеры предоставляют 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);
}
Алгоритм состоит из двух этапов:
После удаления устаревших данных операция повторяется.
В приватных режимах браузеров ограничения часто становятся более жёсткими.
Некоторые браузеры:
Поэтому приложение, успешно работающее в обычном режиме, может столкнуться с ограничениями в режиме инкогнито значительно раньше.
Даже успешное сохранение информации не гарантирует её бессрочное существование.
При нехватке дискового пространства браузер может:
Это особенно актуально для мобильных устройств, где объём памяти ограничен.
Следовательно, localForage следует рассматривать как механизм локального хранения с вероятностью очистки, а не как абсолютную замену серверной базе данных.
При работе с localForage крупные объёмы данных требуют соблюдения нескольких принципов:
Такой подход позволяет создавать приложения, устойчивые к переполнению локального хранилища и корректно работающие даже при жёстких ограничениях браузерных квот.