I think your last option is actually going to be the most workable. By
having the command entities you have a datestamped transaction log,
thus you won't lose updates. That's the most important part from a
reliability point of view. Transactional conflicts don't impact on
reliability, only on scalability. And if you have web clients sit
there and ajax ping a worker that is applying the command entities
until they are all done, then you can even show the user a progress
meter or other appropriate ui feedback.
This is something that will go away once we get background processing.
Something which the GAE team have said is on their radar. =)
Oddly enough, you have exactly the same set of concerns when you want
to write an online MMORPG...
I wonder if there is another way out of the box.
I suppose the question is, how often are you going to be making
changes to the transactional store vs reading the results pages?
--
Brett Morgan http://brett.morgan.googlepages.com/
What if you were to treat transactions (financial ones, not appengine
ones) as entity groups, rather than the account balances themselves?
Transferring funds from A to B would consist of creating a credit
object for B and a debit object for A in one atomic transaction. Basic
double-entry bookkeeping, and it guarantees that either the transfer
happens or it doesn't, no partial balance updates left in limbo.
Getting the current balance of an account gets more interesting, of
course - you have to run a query for all credit and debit objects for
an account and add them up. To optimize, you'd keep a
balance-at-point-in-time record attached to the account that you'd
update lazily, and then only search for debits and credits younger
than that.
The downside of this is that it's hard to protect an account from
being overdrawn, but it does guarantee consistent transactions.
There's things you can do to reduce the risk of overdrawing (if it
matters for your application), but I think solving it perfectly boils
down to a 2-phase commit anyway.
m.
The bit that worries me with this approach is that we turn what was a
straight read into a query + potential write. But, as always, I should
really write some code and test it before I open my trap =)
--
Brett Morgan http://brett.morgan.googlepages.com/
That's the bit that the datastore would look after for you
automatically. It will even retry a few times just in case, depending
on how or where it failed.
In some ways, you're less likely to trigger this sort of problem with
this model than if you have an entity group per account anyway. This
way, you're guaranteed that multiple transactions won't contend on
locks (since they're all independent entity groups). With one group
per account and n simultaneous transactions to the same account, n - 1
of them will have to be retried.
> Makes sense to me, but then the act of lazy compression, the creation
> of the balance-at-point-in-time record, becomes just as failure-
> sensitive, and I'm not sure how to do it.
Caching like this tends to be application-specific - you have to rely
on the nature of your data. Financial transactions are appended only,
they never vanish or change. Also, they are created with monotonically
increasing timestamps. So, based on this, a query for all transactions
for a given account that have a timestamp older than x, as long as x
is safely in the past, will always return the same data - meaning that
you can cache the results of any function over it, and you don't
really care whether it succeeds or fails, since it's just an optional
optimization.
m.
>
> -Matt
>
>
> >
>