2.04. Хранение данных веб-приложений
Хранение данных веб-приложений
Хранение данных — одна из фундаментальных функций любого веб-приложения. От того, как и где данные хранятся, зависит архитектура приложения, его производительность, безопасность, масштабируемость и поведение в условиях нестабильного сетевого соединения. В современном вебе данные могут находиться на разных уровнях: на клиенте, на сервере, в промежуточных кэшах или распределённых системах. При этом важно различать не только физическое расположение данных, но и их логический контекст: временные (volatile), постоянные (persistent), общие (shared), приватные (private), синхронизируемые (syncable), изолированные (sandboxed).
Веб-приложение не является монолитом, и его данные динамически перемещаются между средами выполнения. Так, загруженный пользователем файл сначала поступает в память браузера, затем может быть сохранён локально — например, в файловой системе через File System Access API или в IndexedDB, — после чего отправляется на сервер, где попадает в базу данных или файловое хранилище. Каждый этап сопровождается выбором механизма хранения, обусловленного требованиями к объёму, скорости доступа, долговечности и безопасности.
Ниже рассматриваются основные уровни и механизмы хранения данных в контексте веб-приложений, с акцентом на клиентскую сторону, а также анализируются сопутствующие концепции: сессии, куки, кэш, приватность и защита от трекинга.
Уровни хранения данных
Все данные, с которыми работает веб-приложение, можно условно разделить на три категории по месту размещения:
-
Данные, поступающие от пользователя напрямую — файлы, введённый текст, выбранная конфигурация. Такие данные изначально находятся вне приложения и могут быть импортированы, но не сохраняются автоматически. Примеры: изображения, PDF-документы, CSV-файлы, текстовые заметки. Они обрабатываются с помощью File API, FileReader API и, при необходимости, передаются дальше — либо на сервер, либо в локальное хранилище.
-
Данные, полученные из внешних источников через сеть — результаты вызова REST-, GraphQL- или gRPC-эндпоинтов, ответы от CDN, сторонние фиды. Такие данные приходят в виде структурированных ответов (чаще всего JSON) и обрабатываются в памяти JavaScript. Однако для повышения отзывчивости или обеспечения офлайн-доступа их часто кэшируют — в памяти, в localStorage или в Cache Storage.
-
Данные, сгенерированные или преобразованные самим приложением — внутреннее состояние (UI-состояние, история навигации), результаты вычислений, метаданные, токены аутентификации. Эти данные требуют явного сохранения, и для этого веб-платформа предоставляет несколько встроенных API.
Локальное хранение на стороне клиента
LocalStorage и SessionStorage
localStorage и sessionStorage — это интерфейсы веб-платформы, предоставляющие простейшее ключ-значное хранилище (key-value store) с синхронным API. Оба интерфейса доступны через объект window и имеют одинаковый методический набор: setItem, getItem, removeItem, clear, key.
Принципиальное различие между ними — в продолжительности жизни данных:
-
localStorageсохраняет данные бессрочно — до тех пор, пока пользователь вручную не очистит хранилище (например, через «Очистить историю» в браузере) или пока приложение не удалит их программно. ДанныеlocalStorageпривязаны к источнику (origin), то есть к комбинации протокола, домена и порта. Одно и то же приложение, запущенное наhttp://example.comиhttps://example.com, будет иметь два независимыхlocalStorage. -
sessionStorageдействует в пределах одного сеанса вкладки. Если пользователь открывает новую вкладку или копирует ссылку во вкладку, создаётся новое изолированноеsessionStorage. Но если он обновляет страницу — данные сохраняются. При закрытии вкладки содержимоеsessionStorageавтоматически удаляется.
Емкость localStorage и sessionStorage варьируется в зависимости от браузера, но обычно составляет от 5 до 10 мегабайт на источник. Важно понимать, что оба хранилища реализованы синхронно: операции блокируют основной поток выполнения, что может вызвать заметные задержки при частом доступе к большому объёму данных. Кроме того, поскольку данные хранятся в виде строк в кодировке UTF-16, сохранение бинарных данных требует предварительного преобразования (например, Base64), что увеличивает объём и снижает эффективность.
Тем не менее, localStorage остаётся популярным решением для хранения небольших структур: настроек интерфейса, токенов (хотя это небезопасно — см. ниже), идентификаторов устройств, флагов первого запуска.
IndexedDB
Когда данные достигают размеров в десятки мегабайт, требуют структурированного поиска, поддержки транзакций или долгосрочного хранения без синхронных блокировок — localStorage становится непригодным. Для таких случаев существует IndexedDB — полноценная клиентская база данных, реализованная в виде не реляционного, но транзакционного хранилища объектов.
IndexedDB работает в асинхронном режиме, использует API на основе событий и промисов (в современных реализациях — IDBRequest, IDBTransaction, IDBObjectStore). Данные хранятся как JavaScript-объекты (или структурированные клонируемые объекты), поддерживаются составные индексы, диапазонные запросы, уникальные и неуникальные ключи. Объём хранилища зависит от доступного места на устройстве и политик браузера: в Chrome, например, квота может достигать 60% от дискового пространства, но не менее 10 ГБ.
Основные сценарии использования IndexedDB:
- Офлайн-режим: сохранение справочников, пользовательских данных, черновиков форм.
- Кэширование медиа: аудио, видео, изображения — особенно в PWA, где требуется предзагрузка контента.
- Сложные клиентские приложения: редакторы кода с историей изменений, локальные базы знаний, игровые сохранения.
Хотя IndexedDB мощен, его API считается низкоуровневым и многословным. Поэтому на практике часто используют библиотеки-обёртки (например, Dexie.js), обеспечивающие удобный декларативный интерфейс и типизацию.
Следует отметить, что IndexedDB изолирован по источнику — как и localStorage. Доступ к базе данных с другого домена невозможен в силу политики одного источника (Same-Origin Policy).
Cache Storage
Отдельный класс хранилищ — Cache Storage, часть Service Worker API. В отличие от localStorage и IndexedDB, Cache Storage предназначен специально для сохранения сетевых ответов: HTTP-запросов и ответов на них. Каждый кэш — именованная коллекция пар «запрос → ответ», где запросы и ответы представлены как объекты Request и Response (из Fetch API).
Ключевая особенность Cache Storage — его интеграция с жизненным циклом Service Worker. При установке Service Worker может предварительно заполнить кэш статическими ресурсами (HTML, CSS, JS), а в процессе работы перехватывать сетевые запросы и отдавать кэшированные ответы при отсутствии интернета. Это лежит в основе Progressive Web Apps, обеспечивающих мгновенную загрузку и работу без сети.
Cache Storage не предназначен для хранения произвольных данных — только для HTTP-контента. Он не поддерживает транзакций в классическом понимании, но допускает атомарное добавление/удаление записей. Ёмкость, как и у IndexedDB, ограничена квотой браузера.
Сеанс, сессия и куки
Веб изначально построен на протоколе HTTP — протоколе без сохранения состояния (stateless). Каждый запрос от клиента к серверу рассматривается как независимый, без учёта предыдущих обращений. Это упрощает масштабирование и кэширование, но создаёт проблему: как серверу понять, что два запроса исходят от одного и того же пользователя, особенно если тот прошёл аутентификацию?
Для решения этой задачи используются механизмы управления состоянием: сеанс (session), сессия (session data) и куки (cookies). Несмотря на схожесть терминов, они относятся к разным уровням реализации.
Что такое сеанс
Сеанс — это логическая последовательность взаимодействий пользователя с веб-приложением в рамках одного непрерывного периода активности. Сеанс начинается с первого запроса (часто — открытия главной страницы) и завершается по истечении заданного периода бездействия (например, 30 минут), либо при явном завершении (выход из аккаунта), либо при закрытии браузера (в зависимости от политики приложения).
Сеанс — это абстракция, а не физическое хранилище. Он может быть привязан к IP-адресу, к пользовательскому устройству, к токену, к браузерной вкладке — но сам по себе не содержит данных. Вместо этого, сеанс идентифицируется уникальным идентификатором, который передаётся между клиентом и сервером.
Что такое сессия
Сессия — это набор данных, связанных с конкретным сеансом и хранящихся на сервере. Это может включать:
- Идентификатор авторизованного пользователя;
- Время последнего обращения;
- Параметры локализации (язык, часовой пояс);
- Состояние корзины покупок (до оформления заказа);
- Флаги, например «показано приветственное окно».
Сессионные данные обычно размещаются в сессионном хранилище — это может быть оперативная память (например, встроенный MemoryStore в Express.js), реляционная БД (PostgreSQL), ключ-значное хранилище (Redis, Memcached) или распределённая кэш-система. Redis наиболее распространён, поскольку обеспечивает высокую скорость чтения/записи, поддержку TTL (времени жизни записи) и масштабируемость.
Ключевая особенность: данные сессии никогда не передаются клиенту в открытом виде. Клиент получает только идентификатор сессии — короткую, криптографически стойкую строку (например, s%3Aabc123def456.ZxYwVuTsRqP...), которая служит ключом для поиска соответствующей записи на сервере.
Что такое куки
Куки (HTTP cookies) — это механизм хранения небольших фрагментов данных на стороне клиента, в браузере, с автоматической передачей этих данных при каждом последующем HTTP-запросе к тому же источнику.
Куки устанавливаются сервером через заголовок Set-Cookie в HTTP-ответе. Браузер сохраняет пару имя=значение вместе с метаданными: доменом, путём, временем жизни (Expires или Max-Age), флагами Secure, HttpOnly, SameSite.
Пример заголовка:
Set-Cookie: sid=abc123def456; Path=/; Domain=example.com; Secure; HttpOnly; SameSite=Lax
После этого при каждом запросе к example.com браузер автоматически включает в заголовок Cookie: sid=abc123def456, и сервер извлекает идентификатор для поиска сессии.
Ключевые атрибуты кук:
Secure— кука передаётся только по HTTPS. Обязателен для production-сред.HttpOnly— кука недоступна из JavaScript (черезdocument.cookie). Защищает от XSS-атак при компрометации клиентского кода.SameSite— регулирует, при каких условиях кука отправляется в кросс-сайтовых запросах. Возможные значения:Strict— кука отправляется только при прямом переходе (ввод URL, закладка, внутренняя навигация).Lax(по умолчанию в современных браузерах) — разрешает отправку при GET-запросах извне (например, переход по ссылке), но блокирует при POST, iframe, AJAX.None— разрешает отправку в любых кросс-сайтовых контекстах, но требует флагаSecure.
Куки — один из самых старых и надёжных механизмов, но они не без недостатков. Размер одной куки ограничен 4 КБ, общее число на домен — около 50, общий объём — до 8 КБ. Кроме того, куки участвуют в каждом HTTP-запросе, что увеличивает объём трафика, особенно при множестве мелких ресурсов.
Cookies vs Sessions
Часто возникает путаница между хранением сессии в куках и хранением идентификатора сессии в куках.
-
Хранение сессии в куках (client-side sessions) — сервер сериализует все сессионные данные (например, в JSON), шифрует или подписывает их (чтобы клиент не мог подделать), и отправляет целиком в куке. Клиент возвращает эту структуру при каждом запросе. Преимущество — отсутствие необходимости в серверном хранилище. Недостатки — ограниченный размер, риск раскрытия данных при неправильной подписи, сложность инвалидации (например, при выходе из аккаунта все ещё действительные куки останутся у браузеров).
-
Хранение идентификатора сессии в куках (server-side sessions) — сервер генерирует одноразовый идентификатор, сохраняет данные в памяти или БД, а клиенту отправляет только этот идентификатор. Это предпочтительный подход в большинстве enterprise-приложений, так как обеспечивает полный контроль над данными, их инвалидацией и безопасностью.
Кэш
Кэширование — это стратегия временного хранения копий данных для уменьшения задержек, снижения нагрузки на сервер и обеспечения работы при отсутствии сети.
В вебе существует несколько уровней кэширования, иерархически вложенных:
-
Кэш браузера (HTTP-кэш) — автоматический механизм, управляемый HTTP-заголовками (
Cache-Control,Expires,ETag,Last-Modified). Браузер сохраняет ответы на диск или в память и при повторных запросах использует их, если политика позволяет. Например,Cache-Control: max-age=3600означает, что ресурс считается актуальным в течение часа. -
Кэш Service Worker (Cache Storage) — программно управляемый кэш на уровне приложения. Позволяет реализовать сложные стратегии: cache-first, network-first, stale-while-revalidate. Именно этот уровень даёт контроль над офлайн-поведением.
-
Кэш CDN (Content Delivery Network) — промежуточный кэш между сервером и пользователем, размещённый географически ближе к клиенту. Ускоряет доставку статики и снижает нагрузку на origin-сервер.
-
Кэш на стороне сервера — Redis, Memcached, встроенные кэши ORM и фреймворков. Хранит результаты тяжёлых вычислений, SQL-запросов, рендеринга шаблонов.
Важно понимать, что кэш — это нестабильное хранилище. Данные в нём могут быть удалены в любой момент: при нехватке места, при истечении TTL, при ручной очистке. Поэтому при проектировании следует руководствоваться принципом: кэш ускоряет, но не заменяет источник истины.
Современные и экспериментальные API хранения
Помимо устоявшихся механизмов (localStorage, IndexedDB, Cache Storage), веб-платформа развивается в сторону более гибкого, безопасного и управляемого хранения. Появляются новые интерфейсы, отвечающие на вызовы времени: рост объёмов клиентских данных, требования к приватности, необходимость изоляции, поддержка мульти-профильных и мульти-клиентских сценариев.
Storage Buckets
Storage Buckets — концепция, введённая в рамках инициативы Storage Foundation и реализованная, в частности, в Chrome начиная с версии 96. Она позволяет логически разделять данные одного и того же источника (origin) на независимые «ведра» (buckets), каждое из которых имеет собственные:
- политики выживания (persistence mode):
default,persistent,volatile; - квоты (quota limits);
- уровень изоляции (например, очистка при сбросе настроек, но не при очистке истории);
- время жизни (expiration).
Например, приложение может создать отдельный bucket для кэшированных медиафайлов, задать ему режим volatile (данные могут быть удалены при нехватке места без предупреждения) и ограничить объём 2 ГБ. При этом данные пользовательских настроек будут храниться в другом bucket’е с режимом persistent — их удаление потребует явного согласия пользователя или сброса данных приложения.
Storage Buckets особенно полезны для:
- PWA с большим объёмом офлайн-контента;
- приложений, где часть данных временная (логи, черновики), а часть — критична (настройки, сохранения);
- сценариев, требующих «мягкой» очистки: например, сброс кэша без потери авторизации.
Доступ к buckets осуществляется через navigator.storageBuckets.open(name, options), возвращающий экземпляр StorageBucket, который, в свою очередь, предоставляет доступ к caches, indexedDB, localStorage внутри этого bucket’а. Таким образом, bucket — это не самостоятельное хранилище, а контекст изоляции для существующих API.
На момент 2025 года поддержка ограничена: полная реализация — в Chromium-браузерах; Firefox и Safari находятся на стадии рассмотрения спецификации. Однако архитектурно это направление считается перспективным и может стать стандартом де-факто.
Storage Foundation API
Storage Foundation API — экспериментальный интерфейс, предоставляющий прямой, байт-ориентированный доступ к дисковому хранилищу в рамках origin. В отличие от IndexedDB, он не оперирует объектами и индексами, а работает с фиксированными буферами (FixedLengthStream) и произвольным доступом (FileHandle), что позволяет реализовывать собственные форматы хранения: B+ деревья, WAL-журналы, кастомные СУБД.
Ключевая особенность — возможность делиться хранилищем между разными источниками при явном согласии пользователя через механизм storage sharing. Например, приложение app.example.com и его расширение chrome-extension://abc123 могут получить доступ к одному и тому же хранилищу, если пользователь подтвердит запрос.
Это важно для экосистем: офисные пакеты, облака с десктопными клиентами, интеграции «веб + расширение». Однако из-за рисков безопасности API требует строгой политики: только HTTPS, только user gesture для инициации обмена, отсутствие доступа к cookies/sessions.
На практике Storage Foundation пока редко используется вне исследовательских и enterprise-проектов. Его сложность и неготовность кросс-браузерной поддержки ограничивают применение, но он служит основой для будущих высокоэффективных клиентских СУБД.
File System Access API
Хотя локальные файлы не являются внутренними данными приложения, их импорт и экспорт — критичная часть многих веб-приложений (редакторы, IDE, медиаплееры). Ранее доступ к файлам был ограничен: input[type=file] позволял только чтение, а сохранение требовало генерации Blob и download-ссылки.
File System Access API (часть File System Living Standard) устраняет эти ограничения:
showOpenFilePicker()— открывает системный диалог выбора файлов с возможностью последующего сохранения изменений в тот же файл (при получении прав черезrequestPermission);showSaveFilePicker()— позволяет сохранять данные напрямую в указанный пользователем путь;showDirectoryPicker()— даёт доступ ко всей директории (например, для проектных редакторов);- Полученный
FileSystemFileHandleилиFileSystemDirectoryHandleможно использовать повторно (с сохранением в IndexedDB), что обеспечивает «постоянное» подключение к файлу между сеансами.
Важно: все операции требуют явного взаимодействия с пользователем (user activation), и браузер отображает индикатор доступа, как при использовании камеры или микрофона. Данные не копируются — приложение работает с реальными файлами на диске, но через sandbox’ированную абстракцию.
API поддерживается в Chrome, Edge, Opera; Firefox и Safari — частично или в экспериментальном режиме.
Extension Storage
Хранилище расширений (chrome.storage / browser.storage) — специализированный механизм для браузерных дополнений. Отличается от localStorage и IndexedDB следующим:
- Данные привязаны к расширению, а не к origin веб-страницы;
- Поддерживает синхронизацию между устройствами через аккаунт браузера (
storage.sync); - Имеет отдельную квоту (обычно ~100 КБ для
sync, ~5 МБ дляlocal); - API асинхронный и оптимизирован под частые мелкие операции;
- Доступен из background-скриптов, popup’ов, content-скриптов (с ограничениями).
Например, блокировщик рекламы может сохранять в storage.local список обновлённых фильтров, а в storage.sync — пользовательские правила, чтобы они были доступны на всех устройствах.
Приватность и защита от отслеживания
В условиях роста монетизации пользовательского внимания, сбора поведенческих данных и развития систем профилирования, вопросы приватности перестали быть нишевыми. Современные браузеры — не просто средство отображения контента, а активные агенты защиты пользователя. Они реализуют многоуровневую защиту, сочетающую технические ограничения, политики и интерфейсы для осознанного выбора.
Приватность ≠ анонимность
Приватность в вебе — это контроль над данными. Пользователь должен понимать, что собирается, кем, зачем и как долго. Анонимность — более сильное свойство: невозможность связать действие с конкретной личностью. В стандартном веб-сценарии анонимность невозможна: IP-адрес, User-Agent, разрешение экрана, установленные шрифты, поведенческие паттерны — всё это создаёт уникальный отпечаток устройства (device fingerprint).
Браузеры не скрывают IP (это задача VPN/Tor), но активно борются с усилением отпечатка и его использованием в трекинге.
Отслеживание и его инструменты
Трекинг — это процесс сбора, агрегации и анализа данных о поведении пользователя с целью профилирования. Основные каналы:
-
Куки третьих лиц (third-party cookies) — куки, устанавливаемые доменами, отличными от текущего (например,
ads.doubleclick.netв iframe наnews-site.com). Позволяют отслеживать перемещения между сайтами. На 2025 год полностью заблокированы по умолчанию в Safari и Firefox; в Chrome — отложено до конца 2024, но уже деактивированы в тестовых режимах. -
localStorage / IndexedDB третьих лиц — использовались как «замена кукам» (supercookies). Браузеры ввели изоляцию по first-party (partitioning): данные, записанные в
example.comчерез iframe сtracker.com, хранятся отдельно от данных, записанных при прямом заходе наtracker.com. -
Fingerprinting (сбор отпечатка) — определение уникального профиля через:
- Canvas API (рендеринг текста/изображений с небольшими вариациями);
- WebGL (идентификаторы GPU, расширения);
- Аудио API (характеристики синтеза звука);
- Списки шрифтов (
document.fonts); - Временные задержки операций (тайминг-атаки).
Современные браузеры применяют противодействие:
- Стандартизация отчётов (например,
navigator.userAgentData); - Округление значений (разрешение экрана, времени);
- Блокировка нетипичных API в скрытых вкладках;
- Предупреждения о попытках fingerprinting (в режиме разработчика).
Механизмы защиты
Content Security Policy (CSP)
CSP — HTTP-заголовок (Content-Security-Policy), задающий белый список доверенных источников для различных типов ресурсов: скриптов, стилей, шрифтов, фреймов, подключений. Пример политики:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; img-src * data:
Это означает:
- Все ресурсы по умолчанию загружаются только с того же origin (
'self'); - Скрипты — только с origin и
https://cdn.example.com; - Изображения — откуда угодно, включая
data:URI.
CSP эффективно предотвращает XSS: даже если злоумышленник внедрит <script src="https://evil.com/steal.js">, браузер его не выполнит, если evil.com не в белом списке.
Same-Origin Policy и CORS
Same-Origin Policy (SOP) — фундаментальный принцип безопасности: скрипт с site-a.com не может читать ответ от site-b.com через fetch() или XMLHttpRequest, даже если запрос технически выполнен. Это изолирует источники.
CORS (Cross-Origin Resource Sharing) — механизм, расширяющий SOP по инициативе сервера. Если сервер site-b.com хочет разрешить доступ с site-a.com, он включает в ответ заголовки:
Access-Control-Allow-Origin: https://site-a.com
Access-Control-Allow-Credentials: true
Только тогда браузер передаст данные в JavaScript. Важно: CORS не блокирует отправку запроса — он блокирует доступ к ответу. Поэтому критичные операции (изменение данных) всегда должны проверять не только CORS, но и CSRF-токены.
Redirect Tracking Protection
Как отмечалось ранее, цепочки переадресаций (t.co → bit.ly → example.com) используются для незаметного трекинга. Браузер может перехватывать такие цепочки и:
- Очищать URL от параметров вроде
?utm_source=...&fbclid=...; - Блокировать установку кук в промежуточных звеньях;
- Применять к ним политики изоляции, как к third-party.
Например, в Safari включена функция Intelligent Tracking Prevention (ITP), которая анализирует граф переходов и применяет эвристики для подавления скрытого трекинга.
Private State Tokens (ранее Trust Tokens)
Это механизм борьбы с ботами и мошенничеством без отслеживания. Принцип:
- Пользователь посещает доверенный сайт (например, Google), проходит проверку (капча, поведенческий анализ).
- Сайт выдаёт анонимный токен — криптографический признак «этот пользователь, вероятно, человек», без привязки к личности.
- При переходе на партнёрский сайт (например, банк), токен передаётся в заголовке
Sec-Redeem-Token. - Партнёрский сайт может подтвердить подлинность токена через issuer, но не может использовать его для идентификации или профилирования.
Токены одноразовые, не поддаются перехвату и не связывают сеансы между сайтами. Это альтернатива кукам в сценариях anti-abuse.
Topics API (преемник FLoC)
FLoC (Federated Learning of Cohorts) была отвергнута из-за рисков: даже анонимные когорты могли быть уникальны для малых групп (например, «1 человек в Уфе, интересующийся ядерной физикой»), что позволяло деанонимизировать.
Topics API — более безопасная замена. Принцип:
- Браузер (локально!) анализирует историю посещений за последнюю неделю;
- На основе доменов определяет до 5 тем (например, «Технологии», «Спорт», «Путешествия») по заранее согласованному иерархическому таксону;
- При запросе рекламы сайт может запросить одну тему из последних трёх недель (рандомизированно);
- Тема передаётся в заголовке
Sec-CH-Prefers-Topics; - Никаких идентификаторов, никакой передачи истории — только один обобщённый тег.
Пользователь может отключить Topics API в настройках. Все темы — общедоступные, не создаваемые динамически, что исключает создание «уникальных» когорт.
Сравнительный анализ механизмов хранения
Для осознанного проектирования архитектуры важно не просто знать доступные инструменты, но и понимать, в каких условиях каждый из них проявляет свои сильные и слабые стороны. Ниже приведено сравнение ключевых характеристик основных механизмов клиентского хранения по пяти критериям: ёмкость, срок жизни, доступность между контекстами, производительность и безопасность.
Ёмкость и лимиты
| Механизм | Типичный лимит (на origin) | Примечания |
|---|---|---|
localStorage / sessionStorage | 5–10 МБ | Жёстко задано в браузере; превышение вызывает QuotaExceededError. |
IndexedDB | До 60% свободного диска (Chrome), минимум 10 ГБ | Динамическая квота; можно запросить увеличение через navigator.storage.persist(). |
Cache Storage | Общая с IndexedDB квота | Управление — на уровне именованных кэшей; caches.delete() освобождает место. |
Cookies | 4 КБ на куку, ≤ 50 кук на домен, ≤ 8 КБ суммарно | Ограничения на уровне HTTP; сервер не получит куки, превышающие лимит. |
File System Access | Неограничено (пока есть место на диске) | Права выдаются пользователем индивидуально на файл/директорию. |
Extension Storage | local: ~5 МБ; sync: ~100 КБ | Синхронизация подчиняется политикам аккаунта браузера. |
Срок жизни данных
| Механизм | Сохраняется после перезагрузки страницы | Сохраняется после закрытия вкладки/браузера | Требует ручной очистки |
|---|---|---|---|
localStorage | Да | Да | Да (через UI или API) |
sessionStorage | Да | Нет (удаляется при закрытии вкладки) | Нет |
IndexedDB | Да | Да | Да |
Cache Storage | Да | Да | Да |
Cookies (с Max-Age) | Зависит от TTL | Да, если Max-Age > сессия | Да (браузер может сократить TTL при политике приватности) |
Cookies (сессионные) | Да | Нет (удаляются при завершении процесса браузера) | Нет |
File System Access | Нет (хэндлы утеряны) | Нет | — |
Примечание: хэндлы (FileSystemHandle) можно сохранить в IndexedDB — тогда доступ восстанавливается после перезагрузки приложения при условии, что файл не был удалён или перемещён. |
Доступность между контекстами
| Механизм | Доступен из iframe (same-origin) | Доступен из iframe (cross-origin) | Доступен из Service Worker | Доступен из Web Worker |
|---|---|---|---|---|
localStorage | Да | Нет (SOP) | Нет | Нет |
sessionStorage | Да | Нет | Нет | Нет |
IndexedDB | Да | Нет | Да | Да |
Cache Storage | Да | Нет | Да | Да |
Cookies | Да (автоматически) | Нет (если SameSite=Strict/Lax) | Да (через fetch) | Да (через fetch) |
File System Access | Да (если разрешено) | Нет | Нет | Нет |
Обратите внимание: IndexedDB и Cache Storage — единственные хранилища, доступные из Service Worker, что критично для реализации офлайн-логики.
Производительность
| Механизм | Тип доступа | Блокирует поток? | Задержка (типично) | Подходит для частых операций? |
|---|---|---|---|---|
localStorage | Синхронный | Да | Низкая (ns–мкс) | Нет (блокировки при > 1 КБ) |
sessionStorage | Синхронный | Да | Низкая | Нет |
IndexedDB | Асинхронный | Нет | Средняя (мс) | Да (транзакции, пакетная запись) |
Cache Storage | Асинхронный | Нет | Низкая–средняя | Да |
Cookies | Синхронный (для document.cookie) | Да (в JS) | Низкая (но сетевая задержка при передаче) | Нет (сетевая накладная) |
File System Access | Асинхронный | Нет | Высокая (диск/SSD) | Только для редких, объёмных операций |
Важно: синхронные операции (localStorage, document.cookie) могут вызывать «зависание» интерфейса при частом доступе, особенно на мобильных устройствах. Рекомендуется использовать их только для инициализации или редких событий.
Безопасность и приватность
| Механизм | Доступен из JS? | Передаётся в запросах? | Подвержен XSS? | Подвержен CSRF? | Изолирован в приватном режиме? |
|---|---|---|---|---|---|
localStorage | Да | Нет | Да | Нет | Да |
sessionStorage | Да | Нет | Да | Нет | Да |
IndexedDB | Да | Нет | Да | Нет | Да |
Cache Storage | Да | Нет (но может содержать куки в Response) | Условно | Нет | Да |
Cookies (HttpOnly) | Нет | Да (автоматически) | Нет | Да | Да |
Cookies (без HttpOnly) | Да | Да | Да | Да | Да |
File System Access | Да (через handle) | Нет | Нет (требует user gesture) | Нет | Да (но хэндлы не сохраняются) |
Ключевой вывод: никогда не храните токены аутентификации в localStorage или sessionStorage. При компрометации XSS-уязвимости злоумышленник мгновенно получит доступ к аккаунту. Единственно безопасное место для токена сессии — кука с флагами HttpOnly, Secure, SameSite=Strict/Lax.
Рекомендации по выбору технологии
Выбор механизма хранения должен быть продиктован функциональными и нефункциональными требованиями. Ниже — руководство по принятию решения.
Требуется сохранить небольшие настройки (язык, тема, флаги)?
→ Используйте localStorage.
Просто, надёжно, поддерживается везде. Убедитесь, что данные не содержат чувствительной информации.
Требуется временно сохранить состояние вкладки (шаг многостраничной формы, черновик)?
→ sessionStorage.
Автоматическая очистка при закрытии вкладки снижает риск утечки.
Требуется офлайн-режим, работа с большими объёмами структурированных данных?
→ IndexedDB (с библиотекой-обёрткой, например Dexie.js).
Это стандарт де-факто для сложных клиентских приложений: почтовые клиенты, редакторы, PWA.
Требуется кэширование сетевых ресурсов (JS, CSS, API-ответы) для ускорения или офлайн-доступа?
→ Cache Storage в связке с Service Worker.
Комбинируйте стратегии: stale-while-revalidate для API, cache-first для статики.
Требуется работать с пользовательскими файлами (редактор кода, графический редактор)?
→ File System Access API (с fallback на input[type=file] + Blob для старых браузеров).
Обеспечьте graceful degradation: если API недоступен, используйте загрузку/выгрузку через Blob URL.
Требуется синхронизация между устройствами (настройки расширения)?
→ chrome.storage.sync (для расширений) или облачное API (Firebase, собственный backend) для веб-приложений.
IndexedDB + периодическая синхронизация — распространённый паттерн для PWA.
Требуется хранить токен аутентификации?
→ HttpOnly-кука с Secure, SameSite=Strict.
Если SPA требует доступа к токену (например, для заголовка Authorization), используйте комбинированный подход:
— Идентификатор сессии в HttpOnly-куке (для аутентификации);
— Небольшой, подписанный JWT с правами в localStorage (только для UI-логики, не для аутентификации на сервере).
Требуется высокая производительность записи (логи, метрики)?
→ IndexedDB в режиме readonly/readwrite с пакетной записью или Service Worker + Background Sync.
Не пишите по одной записи — накапливайте буфер и отправляйте пакетами.
Угрозы и практики безопасного хранения
Несмотря на изоляцию, клиентское хранилище — среда с высоким уровнем риска. Пользователь имеет полный контроль над своим браузером, и любые данные, попавшие на клиент, могут быть прочитаны, изменены или удалены. Поэтому:
-
Никогда не храните на клиенте то, что не должно там быть:
— Пароли, приватные ключи, персональные данные без шифрования;
— Бизнес-логику, которую можно извлечь и скопировать (например, алгоритмы ценообразования);
— Неограниченные токены с правами администратора. -
Всегда проверяйте данные на сервере.
Клиент — недоверенная сторона. Даже если данные подписаны (JWT), сервер должен валидировать их срок, права и контекст использования. -
Используйте шифрование на клиенте только для защиты от случайного доступа (например, локальное шифрование заметок паролем).
Положитесь на Web Crypto API (SubtleCrypto), но помните: ключ хранится на том же устройстве, а значит, шифрование не защищает от целенаправленной атаки на устройство. -
Ограничивайте права Service Worker.
Service Worker имеет доступ к Cache и IndexedDB, может перехватывать все запросы. Убедитесь, что его код минимален, подписан (Subresource Integrity), и обновляется по безопасному каналу. -
Отслеживайте изменения в политике приватности браузеров.
Уже сегодня Safari блокирует доступ кdocument.referrerв кросс-сайтовых контекстах, Chrome ограничиваетUser-Agent, Firefox вводит:has()в CSP. То, что работает сегодня, может перестать работать завтра.