Robert
> --
> You received this message because you are subscribed to the Google Groups "Google App Engine" group.
> To post to this group, send email to google-a...@googlegroups.com.
> To unsubscribe from this group, send email to google-appengi...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/google-appengine?hl=en.
>
>
http://code.google.com/appengine/docs/python/datastore/transactions.html
First it says, "Make sure your transactions are idempotent" and then it gives an example which isn't.
I'm not sure it's possible to do the task in that example correctly if you cannot tell whether a transaction succeeded or failed when it throws an exception. I just tried sketching out a solution that stored a transaction ID in the model, but that won't work because there could be multiple writers. The whole thing seems rather intractable, and the idea that you cannot tell whether a transaction succeeded or failed violates the principle of least surprise for anyone who's ever used a database!
Can some googlers weigh in, and explain how, for example, the example in the documentation could be implemented correctly?
--
You received this message because you are subscribed to the Google Groups "Google App Engine" group.
To view this discussion on the web visit https://groups.google.com/d/msg/google-appengine/-/bm54SrfYFdAJ.
There are other solutions too, depending on the app and data model.
Robert
--
You received this message because you are subscribed to the Google Groups "Google App Engine" group.
To view this discussion on the web visit https://groups.google.com/d/msg/google-appengine/-/mP_8kv_-LlMJ.
Could work, except that you'll need to do #6 with a task queue task, because in the case of an exception, they only say it might "eventually" make it into the database. So if you just requery and it isn't there, then you don't know that it didn't succeed… you just know it didn't succeed *yet*. And since the exception which leads to this problem is "InternalError", and we know when those start coming up you can expect to see them continue for a long time, you're going to have a lot of stuff in that queue.
So that means you need to throw this check into the task queue, and keep checking it for a while. Which probably means you need to have a really big list of "recent" transactions (not just 10).
This hack is similar to what I suggested, which is to create a transaction record in the database as a child of the entity you are updating in the transaction. When you get the exception, you wait a while (how long???) and then see if the transaction record exists using an ancestor query. If it does, then the transaction succeeded, and if not, then it didn't. This avoids having to add goofy hacky attributes to the thing you are updating, and in principle, you can schedule a task to delete any transaction records for successful transactions (or periodically sweep up old ones with a cron job).
So, I suppose, it is possible to create a framework that can really do transactions using the building blocks GAE has provided us, but to say that GAE supports transactions on its own is a stretch.
-Joshua
--
This method doesn't really make sense if you read the version number
in the transaction, do stuff, increment the version, then reput within
the same transaction.
However, I do use something like this in some cases, except I have
read the current version prior to 'computing' my updated values. So I
pass the expected version number into the transaction. If I get a
mismatch then I know the update values might be wrong and the update
either needs skipped, recomputed, or the user needs to OK the changes
(and of course this happens based on the current version number).
> Okay, so instead of using a version number we use a random transaction stamp
> (this could be a random number plus some hashed in data that is being
> transacted, etc. to make it really unique). Then instead of a version number
> property, we add a transaction stamp list that holds, for example, the last
> 5 or 10 stamps, or however many you want. So it goes like this:
> 1.) open a transaction
> 2.) perform update
> 3.) add unique transaction stamp to list, if list has reached maximum remove
> the oldest one (FIFO).
> 4.) commit transaction
> 5.) if everything is fine, go on our merry way
> 6.) if exception is generated, then re-query the entity if the entity has
> "our" unique timestamp in its list then the transaction really did succeed,
> if not then the transaction really did fail.
Again, the unique id needs generated outside the transaction so that
if the transaction were to run again the id would be the same. Also,
instead of the list property you could use a separate entity (in the
same entity group) with no properties as a marker. That avoids the
deserialization cost and is totally scalable. With some minor tweaks
you would even be able to cleanup the old marker entities.
Also, as Stephen notes in a later comment, if the next read is in a
transaction it will force any outstanding writes to be applied -- so
it is always consistent. Note that a db.get(some_key) implicitly uses
a transaction.
Robert
Hi Jose,
> 2) There is no fake-real-transaction, for example, one of the other uncommon
> exception that someone could get in the commit() would be Timeout exception.
> With any distributed system, unless you are willing to wait for an answer
> forever, there is always the possibility that you will get a timeout before
> the remote system responds with the result of the operation you requested
> (this is true in all databases), what GAE warranty is that the transaction
> either commits successfully or it does not, so if you implement some retry
> logic in transactions, having it idempotent will warranty you don't do the
> transaction twice.
Just to be sure, are you saying that for a timeout exception the
transaction has actually been committed or not? In other words, should
a Timeout exception be dealt with the same way a
TransactionFailedError is?
I observe Timeout exceptions now and then where there is too much
contention on the same entity group. But I've only seen a
TransactionFailedError once in 2 months.
The transaction could not be committed. Please try again.
Traceback (most recent call last):
File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/webapp/__init__.py", line 702, in __call__
handler.post(*groups)
File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/deferred/deferred.py", line 269, in post
run(self.request.body)
File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/deferred/deferred.py", line 131, in run
return func(*args, **kwds)
File "/base/python_runtime/python_lib/versions/1/google/appengine/ext/deferred/deferred.py", line 174, in invoke_member
return getattr(obj, membername)(*args, **kwargs)
File "/base/data/home/apps/q-tracker/0-9-9-9.352045897303914982/qtrack/models/contract.py", line 268, in defer_create_worksheet
db.run_in_transaction(contract.create_worksheet,start_of_week(week))
File "/base/python_runtime/python_lib/versions/1/google/appengine/api/datastore.py", line 2187, in RunInTransaction
DEFAULT_TRANSACTION_RETRIES, function, *args, **kwargs)
File "/base/python_runtime/python_lib/versions/1/google/appengine/api/datastore.py", line 2294, in RunInTransactionCustomRetries
'The transaction could not be committed. Please try again.')
TransactionFailedError: The transaction could not be committed. Please try again.It won't help avoid the collision. It will let you keep track of previous transactions.
On Jul 29, 2011 8:20 PM, "vlad" <vlad.tr...@gmail.com> wrote:
>
> Joushua,
>
> I was thinking of putting child entities approach as well. The question is does writing a new child entity blocks the whole entity group? If answer is yes. Then you get no benefit from that other than keeping meticulous track of what is happening. I would consider that approach if it lets me avoid transaction collisions as well.
>
> --
> You received this message because you are subscribed to the Google Groups "Google App Engine" group.
> To view this discussion on the web visit https://groups.google.com/d/msg/google-appengine/-/TQa0MN-_vtEJ.
Robert