W3C home > Mailing lists > Public > public-webapps@w3.org > January to March 2013

Re: IndexedDB, what were the issues? How do we stop it from happening again?

From: Alec Flett <alecflett@google.com>
Date: Tue, 19 Mar 2013 15:25:45 -0700
Message-ID: <CAHWpXeacGkZa5jVCGq6DH2=Cnph2vLC9Qo63rpuGEsgBVgSooA@mail.gmail.com>
To: Jonas Sicking <jonas@sicking.cc>
Cc: Alex Russell <slightlyoff@google.com>, Miko Nieminen <miko.nieminen@iki.fi>, Marcos Caceres <marcosscaceres@gmail.com>, Jeni Tennison <jeni@jenitennison.com>, Shwetank Dixit <shwetankd@opera.com>, "www-tag@w3.org" <www-tag@w3.org>, Webapps WG <public-webapps@w3.org>, Joshua Bell <jsbell@google.com>, Jonas Sicking <sicking@mozilla.com>, mlamouri <mlamouri@mozilla.com>, Tab Atkins <tabatkins@google.com>, Yehuda Katz <wycats@gmail.com>, Andrei Popescu <andreip@google.com>
Sorry in advance for the long reply here..
TL;DR is:
1) I still don't think transactions are a requirement
2) I think fixing the platform's motley crew of async apis, and giving
developers better control over transaction commits (When you do use them)
is probably most important.


On Tue, Mar 19, 2013 at 1:52 PM, Jonas Sicking <jonas@sicking.cc> wrote:

>
> var a;
> trans = db.transaction();
> trans.get("a").onsuccess = function(e) {
>   a = e.target.result;
> }
> trans.get("b").onsuccess = function(e) {
>   trans.set("b", e.target.result + a);
> }
>
> I'll be honest: as a web developer who has been bitten numerous times with
coordinating setTimeout and XHR callbacks,  I'd never trust that the above
worked at all, even with explicit transactions.

I just don't think you'd ever write code like that because you're relying
with the subtleties of how scoping works in JS, in addition to the order
guarantees of IndexedDB.

you'd write this instead:

db.objectStore("foo").get("a").onsuccess = function(e) {
  var a = e.target.result;
  db.objectStore("foo").get("b").onsuccess = function(e) {
    db.objectStore("foo").set("b", e.target.result + a);
  }
}

But this is still a degenerate contrived example that I just don't believe
is representative of real-world code. We'd be optimizing for a fairly
specific pattern and I think people are far more often bit by
auto-committing transactions than they are by races like this. If anything,
XHR and setTimeout (and all the new-fangled HTML5 APIs) have taught people
to be careful about races in async apis.



> But I don't think that requires that we get rid of transactions from
> the simple API. And I suspect that that doesn't need to meaningfully
> need to make the simple API that much more complicated.
>
>
It seems like there are two kinds of "races" that we're talking about here:
database races (i.e. read/writes not being atomic, the "A" in ACID) and
event races (i.e. any two arbitrary operations not having guaranteed order,
the "I" in ACID) - I think the latter is often solved with a better
asynchronous API abstraction like Futures/Promises - i.e. an async pattern
that lets you be explicit about your ordering rather than relying on a
containing abstraction like transactions.

I mean imagine your same identical code with XHR (drastically simplified,
but hopefully you get the idea)

xhr1.open("/url?key=key1");
xhr1.onsuccess = function(e) {
  a = xhr1.responseText;
}
xhr2.open("/url?key=key2");
xhr2.onsuccess = function(e) {
  xhr3.open("/update?first=" + a + "&second=" + xhr2.responseText);
}

in the context of XHR, it's now obvious to everyone watching that this is a
race condition.. just a very crude one. I guess I'm doing this to
demonstrate why no developer worth their salt would purposefully write the
race condition that you're afraid may happen without transactions.

No other Async api has a notion of "transactions" to work around races due
to async responses. If that's our concern, then we should be focusing on
getting to Futures/Promises. Having transactions doesn't solve races
between async subsystems, like when using XHR + IDB together.

The following pattern going to be far more common:

var key = ...
xhr1.open("/url?key=" + key);
xhr1.onsuccess = function(e) {
  var xhrValue = xhr1.responseText;
  indexedDB.get(key).onsuccess = function(e) {
    if (keyValue.value != e.target.result) {
       // update my cache...
    }
   }
}

but ultimately this is still ugly because you're serializing your
operations and it's complicated to write code that runs them both in
parallel and only compares them when both callbacks have fired. (Nevermind
the fact that if we were dealing with our current auto-committing
transactions, any open transaction would have committed while we were
waiting for the XHR response)

but with futures/promises and nice libraries like q.js you can imagine
stuff like:

Q.all([
    xhr1.open("/get?key=" + key)
    indexedDB.get(key)
  ])
  .spread(function(responseText, idbValue) {
    if (responseText != idbValue)  {
        // update my cache...
    }
  });

Bam. Ordering races are gone.

Alec
Received on Tuesday, 19 March 2013 22:26:36 GMT

This archive was generated by hypermail 2.3.1 : Tuesday, 26 March 2013 18:49:58 GMT