Жизненный цикл базы данных и версионирование

Жизненный цикл базы данных в Dexie.js строится поверх механизма IndexedDB и управляется через декларативное версионирование схемы. Каждое изменение структуры хранения данных — добавление таблиц, индексов, изменение ключей или миграция данных — оформляется как новая версия базы данных, что позволяет контролировать эволюцию схемы без потери совместимости.

В основе модели лежит принцип: каждая версия базы — это неизменяемое описание структуры на определённый момент времени, а переход между версиями выполняется через управляемые миграции.


Версионная модель Dexie.js

Dexie.js использует метод version() для определения состояния схемы:

  • версия увеличивается целым числом;
  • каждая версия описывает набор таблиц и индексов;
  • переход на новую версию автоматически запускает миграционный процесс.
const db = new Dexie("AppDatabase");

db.version(1).stores({
  users: "++id,name,email"
});

db.version(2).stores({
  users: "++id,name,email,createdAt"
});

Каждый вызов version(n) фиксирует состояние схемы на конкретном этапе.

Ключевой момент: Dexie не модифицирует предыдущие версии, они остаются частью истории миграций.


Открытие базы и жизненный цикл подключения

При вызове:

await db.open();

Dexie выполняет последовательность шагов:

  1. Проверка существующей версии базы в IndexedDB.
  2. Сравнение с последней объявленной версией.
  3. Если версия совпадает — открытие без миграций.
  4. Если версия ниже — запуск upgrade-процесса.

Во время открытия создаётся транзакция уровня versionchange, в рамках которой выполняются все миграции.


Механизм upgrade и onupgradeneeded

Каждая новая версия активирует цепочку upgrade-операций:

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

Dexie позволяет явно описывать миграционную логику через upgrade():

db.version(2).stores({
  users: "++id,name,email,createdAt"
}).upgrade(tx => {
  return tx.table("users").toCollection().modify(user => {
    user.createdAt = Date.now();
  });
});

Внутри upgrade-функции:

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

Атомарность и транзакционный контекст

Миграции выполняются в рамках одной транзакции IndexedDB. Это означает:

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

Dexie дополнительно управляет порядком выполнения версий, гарантируя последовательную миграцию:

v1 → v2 → v3 → v4

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


Изменение схемы: stores() и структура таблиц

Метод stores() определяет структуру object store:

db.version(3).stores({
  users: "++id,name,email,age",
  orders: "++id,userId,total"
});

Синтаксис описания индексов:

  • ++id — автоинкрементный primary key;
  • &email — уникальный индекс;
  • *tags — multi-entry индекс;
  • name — обычный индекс.

Изменение строки stores() воспринимается как изменение схемы, что автоматически инициирует upgrade при следующем открытии базы.


Добавление и удаление таблиц

Добавление таблицы:

db.version(4).stores({
  users: "++id,name,email",
  orders: "++id,userId,total",
  logs: "++id,type,date"
});

Удаление таблицы происходит через исключение её из схемы новой версии:

db.version(5).stores({
  users: "++id,name,email"
});

Dexie автоматически удаляет неописанные object stores при переходе на новую версию.


Миграции данных между версиями

Dexie предоставляет гибкий механизм трансформации данных:

db.version(6).upgrade(async tx => {
  const users = tx.table("users");

  await users.toCollection().modify(user => {
    user.fullName = `${user.firstName} ${user.lastName}`;
    delete user.firstName;
    delete user.lastName;
  });
});

Особенности миграций:

  • выполняются последовательно по версиям;
  • используют курсоры IndexedDB под капотом;
  • поддерживают асинхронные операции;
  • полностью блокируют upgrade-транзакцию до завершения.

Состояние базы между версиями

Во время обновления:

  • база блокируется для чтения и записи;
  • все операции приложения ожидают завершения upgrade;
  • IndexedDB гарантирует эксклюзивный доступ.

Если приложение открыто в нескольких вкладках:

  • только одна вкладка выполняет upgrade;
  • остальные получают событие blocked.

Обработка конфликтов версий

Конфликт возникает, когда:

  • одна вкладка уже использует старую версию;
  • другая инициирует upgrade.

Dexie предоставляет обработчик:

db.on("blocked", () => {
  console.warn("Upgrade blocked by another tab");
});

Также существует событие:

  • versionchange — сигнал о необходимости закрыть соединение.

Откат и несовместимые изменения

Dexie не поддерживает автоматический rollback схемы. Причины:

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

Поэтому:

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

lazy upgrade и поведение при первом открытии

Если версия базы не существует в браузере:

  • создаётся новая база;
  • выполняются все версии последовательно;
  • начиная с версии 1 до последней.

Если база уже существует:

  • Dexie сравнивает версию и применяет только недостающие миграции.

Влияние версионирования на производительность

Факторы, влияющие на скорость upgrade:

  • количество записей в таблицах;
  • наличие modify() операций;
  • количество версий в цепочке;
  • сложность индексов.

Оптимизация достигается через:

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

Управление жизненным циклом в сложных приложениях

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

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

Dexie позволяет поддерживать линейную эволюцию схемы без необходимости ручного управления IndexedDB API.


Поведение при повреждённой или устаревшей базе

Возможные сценарии:

  • отсутствует часть object stores;
  • индекс не соответствует схеме;
  • версия базы выше ожидаемой.

Dexie реагирует следующим образом:

  • при несовместимости запускает upgrade;
  • при невозможности миграции выбрасывает ошибку открытия;
  • при повреждении структуры IndexedDB может потребоваться пересоздание базы.

Роль версии в архитектуре приложения

Версия базы в Dexie.js фактически выполняет функции:

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

Каждое изменение модели данных должно сопровождаться увеличением версии, иначе IndexedDB не инициирует upgrade-процесс.


Связь жизненного цикла с транзакциями Dexie

Все стадии жизненного цикла базы тесно связаны с транзакционной моделью:

  • открытие базы → системная транзакция;
  • upgrade → versionchange транзакция;
  • операции CRUD → readwrite/readonly транзакции.

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


Поведение при закрытии базы во время upgrade

Если база закрывается во время миграции:

  • транзакция прерывается;
  • изменения не фиксируются;
  • при следующем открытии upgrade начинается заново.

Dexie гарантирует целостность состояния, исключая частично применённые миграции.