Permalink
Browse files

(#6068) - safely reimplement IDB docCount optimization

  • Loading branch information...
1 parent 5b692a1 commit 4a1b4e4b6ac7a46b306a7df6870b9e346ba2fea6 @nolanlawson nolanlawson committed with daleharvey Jan 2, 2017
@@ -3,7 +3,8 @@ import { collectConflicts } from 'pouchdb-merge';
import {
ATTACH_STORE,
BY_SEQ_STORE,
- DOC_STORE
+ DOC_STORE,
+ META_STORE
} from './constants';
import {
decodeDoc,
@@ -15,7 +16,6 @@ import {
} from './utils';
import runBatchedCursor from './runBatchedCursor';
import getAll from './getAll';
-import countDocs from './countDocs';
function createKeyRange(start, end, inclusiveEnd, key, descending) {
try {
@@ -64,7 +64,7 @@ function idbAllDocs(opts, idb, callback) {
keyRangeError.name, keyRangeError.message));
}
- var stores = [DOC_STORE, BY_SEQ_STORE];
+ var stores = [DOC_STORE, BY_SEQ_STORE, META_STORE];
if (opts.attachments) {
stores.push(ATTACH_STORE);
@@ -78,13 +78,14 @@ function idbAllDocs(opts, idb, callback) {
txn.onabort = idbError(callback);
var docStore = txn.objectStore(DOC_STORE);
var seqStore = txn.objectStore(BY_SEQ_STORE);
+ var metaStore = txn.objectStore(META_STORE);
var docIdRevIndex = seqStore.index('_doc_id_rev');
var results = [];
var docCount;
- countDocs(txn, function (thisDocCount) {
- docCount = thisDocCount;
- });
+ metaStore.get(META_STORE).onsuccess = function (e) {
+ docCount = e.target.result.docCount;
+ };
// if the user specifies include_docs=true, then we don't
// want to block the main cursor while we're fetching the doc
@@ -17,24 +17,24 @@ import { DETECT_BLOB_SUPPORT_STORE } from './constants';
function checkBlobSupport(txn) {
return new Promise(function (resolve) {
var blob = createBlob(['']);
- txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key');
+ var req = txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key');
- txn.onabort = function (e) {
- // If the transaction aborts now its due to not being able to
- // write to the database, likely due to the disk being full
- e.preventDefault();
- e.stopPropagation();
- resolve(false);
- };
-
- txn.oncomplete = function () {
+ req.onsuccess = function () {
var matchedChrome = navigator.userAgent.match(/Chrome\/(\d+)/);
var matchedEdge = navigator.userAgent.match(/Edge\//);
// MS Edge pretends to be Chrome 42:
// https://msdn.microsoft.com/en-us/library/hh869301%28v=vs.85%29.aspx
resolve(matchedEdge || !matchedChrome ||
parseInt(matchedChrome[1], 10) >= 43);
};
+
+ txn.onabort = function (e) {
+ // If the transaction aborts now its due to not being able to
+ // write to the database, likely due to the disk being full
+ e.preventDefault();
+ e.stopPropagation();
+ resolve(false);
+ };
}).catch(function () {
return false; // error, so assume unsupported
});
@@ -16,7 +16,8 @@ import {
ATTACH_STORE,
BY_SEQ_STORE,
DOC_STORE,
- LOCAL_STORE
+ LOCAL_STORE,
+ META_STORE
} from './constants';
import {
@@ -36,7 +37,9 @@ function idbBulkDocs(dbOpts, req, opts, api, idb, callback) {
var bySeqStore;
var attachStore;
var attachAndSeqStore;
+ var metaStore;
var docInfoError;
+ var metaDoc;
for (var i = 0, len = docInfos.length; i < len; i++) {
var doc = docInfos[i];
@@ -53,6 +56,8 @@ function idbBulkDocs(dbOpts, req, opts, api, idb, callback) {
return callback(docInfoError);
}
+ var allDocsProcessed = false;
+ var docCountDelta = 0;
var results = new Array(docInfos.length);
var fetchedDocs = new Map();
var preconditionErrored = false;
@@ -70,7 +75,8 @@ function idbBulkDocs(dbOpts, req, opts, api, idb, callback) {
var stores = [
DOC_STORE, BY_SEQ_STORE,
ATTACH_STORE,
- LOCAL_STORE, ATTACH_AND_SEQ_STORE
+ LOCAL_STORE, ATTACH_AND_SEQ_STORE,
+ META_STORE
];
var txnResult = openTransactionSafely(idb, stores, 'readwrite');
if (txnResult.error) {
@@ -84,6 +90,12 @@ function idbBulkDocs(dbOpts, req, opts, api, idb, callback) {
bySeqStore = txn.objectStore(BY_SEQ_STORE);
attachStore = txn.objectStore(ATTACH_STORE);
attachAndSeqStore = txn.objectStore(ATTACH_AND_SEQ_STORE);
+ metaStore = txn.objectStore(META_STORE);
+
+ metaStore.get(META_STORE).onsuccess = function (e) {
+ metaDoc = e.target.result;
+ updateDocCountIfReady();
+ };
verifyAttachments(function (err) {
if (err) {
@@ -94,9 +106,24 @@ function idbBulkDocs(dbOpts, req, opts, api, idb, callback) {
});
}
+ function onAllDocsProcessed() {
+ allDocsProcessed = true;
+ updateDocCountIfReady();
+ }
+
function idbProcessDocs() {
processDocs(dbOpts.revs_limit, docInfos, api, fetchedDocs,
- txn, results, writeDoc, opts);
+ txn, results, writeDoc, opts, onAllDocsProcessed);
+ }
+
+ function updateDocCountIfReady() {
+ if (!metaDoc || !allDocsProcessed) {
+ return;
+ }
+ // caching the docCount saves a lot of time in allDocs() and
+ // info(), which is why we go to all the trouble of doing this
+ metaDoc.docCount += docCountDelta;
+ metaStore.put(metaDoc);
}
function fetchExistingDocs() {
@@ -214,6 +241,9 @@ function idbBulkDocs(dbOpts, req, opts, api, idb, callback) {
isUpdate, resultsIdx, callback);
}
+ docCountDelta += delta;
+ updateDocCountIfReady();
+
finishDoc(docInfo, winningRev, winningRevIsDeleted,
isUpdate, resultsIdx, callback);
}
@@ -390,17 +390,16 @@ function init(api, opts, callback) {
var updateSeq;
var docCount;
- var txnResult = openTransactionSafely(idb, [DOC_STORE, BY_SEQ_STORE], 'readonly');
+ var txnResult = openTransactionSafely(idb, [META_STORE, BY_SEQ_STORE], 'readonly');
if (txnResult.error) {
return callback(txnResult.error);
}
var txn = txnResult.txn;
- countDocs(txn, function (thisDocCount) {
- docCount = thisDocCount;
- });
- var cursor = txn.objectStore(BY_SEQ_STORE).openCursor(null, 'prev');
- cursor.onsuccess = function (event) {
- var cursor = event.target.result;
+ txn.objectStore(META_STORE).get(META_STORE).onsuccess = function (e) {
+ docCount = e.target.result.docCount;
+ };
+ txn.objectStore(BY_SEQ_STORE).openCursor(null, 'prev').onsuccess = function (e) {
+ var cursor = e.target.result;
updateSeq = cursor ? cursor.key : 0;
};
@@ -705,65 +704,90 @@ function init(api, opts, callback) {
cachedDBs.delete(dbName);
};
+ // Do a few setup operations (in parallel as much as possible):
+ // 1. Fetch meta doc
+ // 2. Check blob support
+ // 3. Calculate docCount
+ // 4. Generate an instanceId if necessary
+ // 5. Store docCount and instanceId on meta doc
+
var txn = idb.transaction([
META_STORE,
DETECT_BLOB_SUPPORT_STORE,
DOC_STORE
], 'readwrite');
- var getMetaReq = txn.objectStore(META_STORE).get(META_STORE);
-
- var blobSupport = null;
- var instanceId = null;
-
- getMetaReq.onsuccess = function (e) {
- var complete = false;
- function checkSetupComplete() {
- if (blobSupport !== null && instanceId !== null && !complete) {
- complete = true;
- api._meta = {
- name: dbName,
- instanceId: instanceId,
- blobSupport: blobSupport
- };
+ var storedMetaDoc = false;
+ var metaDoc;
+ var docCount;
+ var blobSupport;
+ var instanceId;
- cachedDBs.set(dbName, {
- idb: idb,
- global: api._meta
- });
- callback(null, api);
- }
+ function completeSetup() {
+ if (typeof blobSupport === 'undefined' || !storedMetaDoc) {
+ return;
}
+ api._meta = {
+ name: dbName,
+ instanceId: instanceId,
+ blobSupport: blobSupport
+ };
- //
- // fetch/store the id
- //
+ cachedDBs.set(dbName, {
+ idb: idb,
+ global: api._meta
+ });
+ callback(null, api);
+ }
- var meta = e.target.result || {id: META_STORE};
- if (dbName + '_id' in meta) {
- instanceId = meta[dbName + '_id'];
- checkSetupComplete();
+ function storeMetaDocIfReady() {
+ if (typeof docCount === 'undefined' || typeof metaDoc === 'undefined') {
+ return;
+ }
+ var instanceKey = dbName + '_id';
+ if (instanceKey in metaDoc) {
+ instanceId = metaDoc[instanceKey];
} else {
- instanceId = uuid();
- meta[dbName + '_id'] = instanceId;
- txn.objectStore(META_STORE).put(meta).onsuccess = function () {
- checkSetupComplete();
- };
+ metaDoc[instanceKey] = instanceId = uuid();
}
+ metaDoc.docCount = docCount;
+ txn.objectStore(META_STORE).put(metaDoc);
+ }
- //
- // check blob support
- //
+ //
+ // fetch or generate the instanceId
+ //
+ txn.objectStore(META_STORE).get(META_STORE).onsuccess = function (e) {
+ metaDoc = e.target.result || { id: META_STORE };
+ storeMetaDocIfReady();
+ };
- if (!blobSupportPromise) {
- // make sure blob support is only checked once
- blobSupportPromise = checkBlobSupport(txn);
- }
+ //
+ // countDocs
+ //
+ countDocs(txn, function (count) {
+ docCount = count;
+ storeMetaDocIfReady();
+ });
- blobSupportPromise.then(function (val) {
- blobSupport = val;
- checkSetupComplete();
- });
+ //
+ // check blob support
+ //
+ if (!blobSupportPromise) {
+ // make sure blob support is only checked once
+ blobSupportPromise = checkBlobSupport(txn);
+ }
+
+ blobSupportPromise.then(function (val) {
+ blobSupport = val;
+ completeSetup();
+ });
+
+ // only when the metadata put transaction has completed,
+ // consider the setup done
+ txn.oncomplete = function () {
+ storedMetaDoc = true;
+ completeSetup();
};
};

0 comments on commit 4a1b4e4

Please sign in to comment.