On 4/19/15 5:58 PM, George Reilly wrote:
The aggregated centralized log looks like
first interleaved request, 254c1046-0da701ca, at 19:09:03, which
switches moods from `['Grumpy', 'Tired', 'Disgruntled']` to `['Elated']`
Apr 15 19:09:03 [3843:MainThread] INFO [254c1046-0da701ca]
REST request: PUT http://<hostname>/avatar/<avatar_id> received...
Second interleaved request, 711bc322-df5afba6, at 19:09:04, also
with moods=`['Elated']`
Apr 15 19:09:04 [4572:MainThread] INFO [711bc322-df5afba6]
REST request: PUT http://<hostname>/avatar/<avatar_id> received...
Second request fails
Apr 15 19:09:05 [4572:MainThread] WARNI [711bc322-df5afba6]
REST request: PUT http://<hostname>/avatar/<avatar_id> failed: 400 Bad
Request (GENERAL): 'DELETE statement on table 'avatar_moods' expected
to delete 3 row(s); Only 0 were matched.' (DELETE statement on table
'avatar_moods' expected to delete 3 row(s); Only 0 were matched.)
First request succeeds
Apr 15 19:09:05 [3843:MainThread] INFO [254c1046-0da701ca]
REST response: [1570ms] PUT http://<hostname>/avatar/<avatar_id>
responded: 200 OK
you saw what's called a phantom read:
http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Phantom_reads
Transaction #2 in PostgreSQL now had visibility to those updated rows
in the DB,
so it returned "0 rows deleted" to SQLAlchemy.
This confounded SA's expectations and it blew up.
I think.
sure, the ORM tries where possible to catch scenarios where what's in
the database does not match the mutations being sent in. Because of
course to continue with incorrect assumptions instead of stopping
immediately is a good way to really whack things up.
Finally, my question: What, if anything, could we have done to
mitigate this on the server?
Well, while it is not usually used, serializable isolation will solve
the problem of phantom reads. You will lose concurrency because
serialzable still has to lock things. If you wanted to go this route,
you could have just the methods in question use this isolation level
individually, so that they will not have this activity against each
other, without the rest of the application methods being impacted by
this. The Core has a lot of isolation level directives but also
recently I added the ability for a Session to set an isolation level per
individual transaction as well, see
http://docs.sqlalchemy.org/en/rel_1_0/orm/session_transaction.html#setting-isolation-for-individual-transactions
for that.
Another way to go is to have a method wrapped in a "retry" decorator of
some kind, which anticipates known error conditions such as a potential
race condition, and just retries the entire method all over again,
starting from re-loading the current data and trying the operation again.
A more specialized form of "retry" is to keep the main transaction going
and use a savepoint within the critical section, where you can again
retry the operation. Savepoints are available in the ORM via
begin_nested():
http://docs.sqlalchemy.org/en/rel_1_0/orm/session_transaction.html#using-savepoint
Yet another way, is to emit a SELECT for these rows using FOR UPDATE,
which will load the rows and lock them at the same time. You'd need to
emit a query that will work directly against the avatar_moods table so
that these rows are selected and set up as read-only to other
transactions. The various FOR UPDATE options are available at the ORM
level using with with_for_update() method:
http://docs.sqlalchemy.org/en/rel_1_0/orm/query.html#sqlalchemy.orm.query.Query.with_for_update.
PG supports a few options here where you can see an overview at
http://www.postgresql.org/docs/9.0/static/sql-select.html#SQL-FOR-UPDATE-SHARE.
SQLAlchemy supports FOR UPDATE, FOR SHARE, with or without the NOWAIT
and OF options of Postgresql here.
Looking at dependency.py for 0.9.9, it looks like SQLAlchemy considers
this
to be an unrecoverable error.
you'd probably find that Postgresql itself would forbid the transaction
from continuing after this error in any case.
PS Congratulations on releasing SQLAlchemy 1.0! I've been a happy user
since 2008.
--
that's great !
You received this message because you are subscribed to the Google
Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send
an email to [email protected]
<mailto:[email protected]>.
To post to this group, send email to [email protected]
<mailto:[email protected]>.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.