W3C home > Mailing lists > Public > public-webapps@w3.org > October to December 2011

[IndexedDB] transaction order

From: Jonas Sicking <jonas@sicking.cc>
Date: Fri, 7 Oct 2011 14:52:21 -0700
Message-ID: <CA+c2ei8qkF9MJgbyOMF6_uSX5P_ZUuTewhj1jHnhjCe4OLS5_Q@mail.gmail.com>
To: Webapps WG <public-webapps@w3.org>
Hi All,

There is one edge case regarding transaction scheduling that we'd like
to get clarified.

As the spec is written, it's clear what the following code should do:

trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
trans1.objectStore("foo").put("value 1", "mykey");
trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
trans2.objectStore("foo").put("value 2", "mykey");

In this example it's clear that the implementation should first run
trans1 which will put the value "value 1" in object store "foo" at key
"mykey". The implementation should then run trans2 which will write
overwrite the same value with "value 2". The end result is that "value
2" is the value that lives in the object store.

Note that in this case it's not at all ambiguous which transaction
runs first. Since the two transactions have overlapping scope, trans2
won't even start until trans1 is committed. Even if we made the code
something like:

trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
trans1.objectStore("foo").put("value 1", "mykey");
trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
trans2.objectStore("foo").put("value 2", "mykey");
trans1.objectStore("foo").put("value 3", "mykey");

we'd get the same result. Both put requests placed against trans1 will
run first while trans2 is waiting for trans1 to commit before it
begins running since they have overlapping scopes.

However, consider the following example:
trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
trans2.objectStore("foo").put("value 2", "mykey");
trans1.objectStore("foo").put("value 1", "mykey");

In this case, while trans1 is created first, no requests are placed
against it, and so no database operations are started. The first
database operation that is requested is one placed against trans2. In
the firefox implementation, this makes trans2 run before trans1. I.e.
we schedule transactions when the first request is placed against
them, and not when the IDBDatabase.transaction() function returns.

The advantage of firefox approach is obvious in code like this:

someElement.onclick = function() {
  trans1 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
  ...
  trans2 = db.transaction(["foo"], IDBTransaction.READ_WRITE);
  trans2.objectStore.put("some value", "mykey");
  callExpensiveFunction();
}

In this example no requests are placed against trans1. However since
trans1 is supposed to run before trans2 does, we can't send off any
work to the database at the time when the .put call happens since we
don't yet know if there will be requests placed against trans1. Only
once we return to the event loop at the end of the onclick handler
will trans1 be "committed" and the requests in trans2 can be sent to
the database.

However, the downside with firefox approach is that it's harder for
applications to control which order transactions are run. Consider for
example a program is parsing a big hunk of binary data. Before
parsing, the program starts two transactions, one READ_WRITE and one
READ_ONLY. As the binary data is interpreted, the program issues write
requests against the READ_WRITE transactions and read requests against
the READ_ONLY transaction. The idea being that the read requests will
always run after the write requests to read from database after all
the parsed data has been written. In this setup the firefox approach
isn't as good since it's less predictable which transaction will run
first as it might depend on the binary data being parsed. Of course,
you could force the writing transaction to run first by placing a
request against it after it has been created.

I am however not able to think of any concrete examples of the above
binary data structure that would require this setup.

So the question is, which solution do you think we should go with. One
thing to remember is that there is a very small difference between the
two approaches here. It only makes a difference in edge cases. The
edge case being that a transaction is created, but no requests are
placed against it until another transaction, with overlapping scope,
is created.

Firefox approach has strictly better performance in this edge case.
However it could also have somewhat surprising results.

I personally don't feel strongly either way. I also think it's rare to
make a difference one way or another as it'll be rare for people to
hit this edge case.

But we should spell things out clearly in the spec which approach is
the conforming one.

/ Jonas
Received on Friday, 7 October 2011 21:53:20 GMT

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