Search code examples
javascriptcordovainternet-explorer-10indexeddblawnchair

IndexedDB via Lawnchair gets larger with every save


I am using Lawnchair.js on a mobile app I am building at work targeting iOS, Android, and Windows phones. My question is I have a relatively simple function(see below), that reads data from an object and saves it in the indexeddb database. It's about 4MB of data and on the first go round when I inspect in Internet explorer(via internet options), I can see the database is about 7MB. If I reload the page and re-run the same function with the same data, it increases to 14MB and then 20MB. Im using the same keys so my understanding is that this should just update the record but it's almost as if it's just inserting all new records every time. I have also had similar behavior using Lawnchair on mobile safari using websql adapter. Has anyone seen this before or have any suggestions as to why this might be ??.

The following code is from a function I am using to populate the database.

populateDatabase: function(database,callback) {
    'use strict';
    var key;
    try {
      for(key in MasterData){
        if(MasterData.hasOwnProperty(key)){
          var itemInfo = DataConfig.checkForDataUpdates[DataConfig.keyMap[key]];
          database.save({key:itemInfo["name"],hash:itemInfo["version"],url:itemInfo["url"],data:MasterData[key]});
        }
      }
      callback(true);
    } catch(e){
      callback(false);
    }
}

MasterData is the large data file and itemInfo contains the key name, a hash that is later used to check an api for updates, and the relative url of where to update from. After I create the database I pass it into this function and then pass back true if the inserts are successful and false otherwise.

As previously mentioned, I have seen similar issues in iOS where calling database.save() was allocating a lot of memory but not releasing it and eventually causing a crash if it populated the database and then tried to update some records. Removing Lawnchair from the equation has kept it from crashing but it is still allocating a lot of memory when saving data. Not sure if this is normal for persistent storage on mobile devices, a bug in Lawnchair, or me being a noob and doing something terribly wrong but I could use some pointers on this as well as why indexeddb just keeps getting larger and larger on every save (at least during initial testing in IE10)??

EDIT: Source Code for indexed-db adapter is here: https://github.com/brianleroux/lawnchair/blob/master/src/adapters/indexed-db.js

and here is the code for the save function I am using:

save:function(obj, callback) {
    var self = this;
    if(!this.store) {
        this.waiting.push(function() {
            this.save(obj, callback);
        });
        return;
     }

     var objs = (this.isArray(obj) ? obj : [obj]).map(function(o){if(!o.key) { o.key = self.uuid()} return o})

     var win  = function (e) {
       if (callback) { self.lambda(callback).call(self, self.isArray(obj) ? objs : objs[0] ) }
     };

     var trans = this.db.transaction(this.record, READ_WRITE);
     var store = trans.objectStore(this.record);

     for (var i = 0; i < objs.length; i++) {
      var o = objs[i];
      store.put(o, o.key);
     }
     store.transaction.oncomplete = win;
     store.transaction.onabort = fail;

     return this;
},

When Creating a new instance, Lawnchair uses the init function from the indexed-db adapter which is the following.

init:function(options, callback) {
    this.idb = getIDB();
    this.waiting = [];
    this.useAutoIncrement = useAutoIncrement();
    var request = this.idb.open(this.name, STORE_VERSION);
    var self = this;
    var cb = self.fn(self.name, callback);
    if (cb && typeof cb != 'function') throw 'callback not valid';
    var win = function() {
        // manually clean up event handlers on request; this helps on chrome
        request.onupgradeneeded = request.onsuccess = request.error = null;
        if(cb) return cb.call(self, self);
    };

    var upgrade = function(from, to) {
        // don't try to migrate dbs, just recreate
        try {
            self.db.deleteObjectStore('teststore'); // old adapter
        } catch (e1) { /* ignore */ }
        try {
            self.db.deleteObjectStore(self.record);
        } catch (e2) { /* ignore */ }

        // ok, create object store.
        var params = {};
        if (self.useAutoIncrement) { params.autoIncrement = true; }
        self.db.createObjectStore(self.record, params);
        self.store = true;
    };
    request.onupgradeneeded = function(event) {
        self.db = request.result;
        self.transaction = request.transaction;
        upgrade(event.oldVersion, event.newVersion);
        // will end up in onsuccess callback
    };
    request.onsuccess = function(event) {
       self.db = event.target.result; 

        if(self.db.version != (''+STORE_VERSION)) {
          // DEPRECATED API: modern implementations will fire the
          // upgradeneeded event instead.
          var oldVersion = self.db.version;
          var setVrequest = self.db.setVersion(''+STORE_VERSION);
          // onsuccess is the only place we can create Object Stores
          setVrequest.onsuccess = function(event) {
              var transaction = setVrequest.result;
              setVrequest.onsuccess = setVrequest.onerror = null;
              // can't upgrade w/o versionchange transaction.
              upgrade(oldVersion, STORE_VERSION);
              transaction.oncomplete = function() {
                  for (var i = 0; i < self.waiting.length; i++) {
                      self.waiting[i].call(self);
                  }
                  self.waiting = [];
                  win();
              };
          };
          setVrequest.onerror = function(e) {
              setVrequest.onsuccess = setVrequest.onerror = null;
              console.error("Failed to create objectstore " + e);
              fail(e);
          };
        } else {
            self.store = true;
            for (var i = 0; i < self.waiting.length; i++) {
                  self.waiting[i].call(self);
            }
            self.waiting = [];
            win();
        }
    }
    request.onerror = function(ev) {
        if (request.errorCode === getIDBDatabaseException().VERSION_ERR) {
            // xxx blow it away
            self.idb.deleteDatabase(self.name);
            // try it again.
            return self.init(options, callback);
        }
        console.error('Failed to open database');
    };
},

Solution

  • I think you keep adding data instead of updating the present data.

    Can you provide some more information about the configuration of the store. Are you using an inline or external key? If it's an internal what is the keypath.