Dexie.js предоставляет высокоуровневый API над IndexedDB, где пакетные операции являются ключевым инструментом для снижения накладных расходов при массовых вставках, обновлениях и удалениях данных. Основная идея пакетной обработки заключается в минимизации количества транзакций и переходов между JavaScript-слоем и движком IndexedDB, поскольку именно эти границы создают наибольшие задержки при интенсивных операциях.
Каждая операция записи в IndexedDB требует сериализации данных (structured clone algorithm), постановки задачи в очередь транзакции и последующего подтверждения. При выполнении большого количества одиночных операций создаётся значительная нагрузка:
Dexie.js агрегирует эти проблемы через пакетные методы:
bulkAddbulkPutbulkDeletedb.transaction(...)Метод bulkAdd оптимизирован для добавления множества
новых записей:
await db.transaction('rw', db.users, async () => {
await db.users.bulkAdd([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
]);
});
Особенность bulkAdd заключается в том, что Dexie
выполняет вставку внутри одной транзакции и минимизирует количество
внутренних операций commit.
addbulkPut выполняет вставку или обновление в зависимости
от наличия ключа:
await db.transaction('rw', db.products, async () => {
await db.products.bulkPut([
{ sku: 'A1', price: 100 },
{ sku: 'B2', price: 200 }
]);
});
Это устраняет классическую проблему N+1:
// неэффективно
for (const item of items) {
await db.products.put(item);
}
Удаление множества элементов также критично для производительности:
await db.transaction('rw', db.logs, async () => {
await db.logs.bulkDelete([1, 2, 3, 4, 5]);
});
Dexie передаёт массив ключей напрямую в IndexedDB, что позволяет избежать индивидуальных операций поиска.
Dexie.js строит пакетную обработку вокруг транзакций. Любая
оптимизация начинается с правильного использования
transaction.
await db.transaction('rw', db.users, db.orders, async () => {
await db.users.bulkPut(users);
await db.orders.bulkAdd(orders);
});
Преимущества:
IndexedDB имеет ограничения на размер транзакций и сериализацию. Передача слишком больших массивов может приводить к блокировке UI или ошибкам переполнения памяти.
Оптимальный подход — разбиение на чанки:
function chunk(array, size) {
const result = [];
for (let i = 0; i < array.length; i += size) {
result.push(array.slice(i, i + size));
}
return result;
}
const chunks = chunk(largeDataset, 1000);
for (const part of chunks) {
await db.transaction('rw', db.items, async () => {
await db.items.bulkPut(part);
});
}
Каждая запись проходит через structured clone, что становится узким местом при больших объёмах данных.
Оптимизации:
Избыточные вложенные объекты увеличивают стоимость копирования.
// хуже
{
id: 1,
metadata: {
tags: ['a', 'b'],
history: { created: Date.now() }
}
}
// лучше
{
id: 1,
tags: 'a,b',
created: 1710000000000
}
Подготовка данных до входа в Dexie снижает нагрузку внутри транзакции.
Эффективность bulk-операций зависит от структуры индексов. Каждый индекс увеличивает стоимость записи, так как требует дополнительного обновления B-tree структуры.
db.version(1).stores({
users: '++id, email, status, createdAt'
});
whereПри bulkPut каждый индекс обновляется синхронно, поэтому
раздутая схема напрямую снижает throughput.
Dexie позволяет контролировать поток операций через асинхронные очереди.
for (const batch of batches) {
await db.items.bulkPut(batch);
}
await Promise.all(
batches.map(batch => db.items.bulkPut(batch))
);
Параллельное выполнение увеличивает throughput, но может привести к:
Чаще применяется ограниченная конкуренция (2–3 параллельных транзакции).
Для условных обновлений эффективнее использовать
modify:
await db.users
.where('status')
.equals('inactive')
.modify({ status: 'active' });
Преимущество:
for (const item of items) {
await db.items.put(item);
}
Проблемы:
await db.items.bulkPut(items);
Результат:
Большие пакетные операции могут блокировать интерфейс из-за синхронных участков внутри IndexedDB.
Практика управления:
await db.transaction('rw', db.items, async () => {
for (let i = 0; i < items.length; i += 1000) {
await db.items.bulkPut(items.slice(i, i + 1000));
await new Promise(requestAnimationFrame);
}
});
Такой подход снижает длительность блокировок и распределяет нагрузку.
Dexie прерывает bulkAdd при первой ошибке ключа. Для
устойчивых загрузок используется обработка ошибок:
await db.items.bulkAdd(items, { allKeys: true })
.catch(err => {
// анализ частично вставленных данных
});
Альтернативный подход — предварительная дедупликация:
const unique = new Map(items.map(i => [i.id, i]));
await db.items.bulkPut([...unique.values()]);
При проектировании схемы данных в Dexie.js пакетные операции становятся базовым режимом работы:
bulk*modify или bulkPutТакая модель минимизирует количество round-trip операций между JS и IndexedDB и обеспечивает стабильную производительность даже при сотнях тысяч записей.
В системах с высокой частотой обновлений (телеметрия, события UI, синхронизация состояния) применяется агрегация:
let buffer = [];
function pushEvent(e) {
buffer.push(e);
if (buffer.length >= 500) {
flush();
}
}
async function flush() {
const batch = buffer;
buffer = [];
await db.events.bulkAdd(batch);
}
Буферизация снижает: