[openstack-dev] [Neutron] DB: transaction isolation and related questions
Jay Pipes
jaypipes at gmail.com
Wed Nov 19 19:58:49 UTC 2014
Hi Eugene, please see comments inline. But, bottom line, is that setting
the transaction isolation level to READ_COMMITTED should be avoided.
On 11/18/2014 01:38 PM, Eugene Nikanorov wrote:
> Hi neutron folks,
>
> There is an ongoing effort to refactor some neutron DB logic to be
> compatible with galera/mysql which doesn't support locking
> (with_lockmode('update')).
>
> Some code paths that used locking in the past were rewritten to retry
> the operation if they detect that an object was modified concurrently.
> The problem here is that all DB operations (CRUD) are performed in the
> scope of some transaction that makes complex operations to be executed
> in atomic manner.
Yes. The root of the problem in Neutron is that the session object is
passed through all of the various plugin methods and the
session.begin(subtransactions=True) is used all over the place, when in
reality many things should not need to be done in long-lived
transactional containers.
> For mysql the default transaction isolation level is 'REPEATABLE READ'
> which means that once the code issue a query within a transaction, this
> query will return the same result while in this transaction (e.g. the
> snapshot is taken by the DB during the first query and then reused for
> the same query).
Correct.
However note that the default isolation level in PostgreSQL is READ
COMMITTED, though it is important to point out that PostgreSQL's READ
COMMITTED isolation level does *NOT* allow one session to see changes
committed during query execution by concurrent transactions.
It is a common misunderstanding that MySQL's READ COMMITTED isolation
level is the same as PostgreSQL's READ COMMITTED isolation level. It is
not. PostgreSQL's READ COMMITTED isolation level is actually most
closely similar to MySQL's REPEATABLE READ isolation level.
I bring this up because the proposed solution of setting the isolation
level to READ COMMITTED will not work like you think it will on
PostgreSQL. Regardless, please see below as to why setting the isolation
level to READ COMMITTED is not the appropriate solution to this problem
anyway...
> In other words, the retry logic like the following will not work:
>
> def allocate_obj():
> with session.begin(subtrans=True):
> for i in xrange(n_retries):
> obj = session.query(Model).filter_by(filters)
> count = session.query(Model).filter_by(id=obj.id
> <http://obj.id>).update({'allocated': True})
> if count:
> return obj
>
> since usually methods like allocate_obj() is called from within another
> transaction, we can't simply put transaction under 'for' loop to fix the
> issue.
Exactly. The above code, from here:
https://github.com/openstack/neutron/blob/master/neutron/plugins/ml2/drivers/helpers.py#L98
has no chance of working at all under the existing default isolation
levels for either MySQL or PostgreSQL. If another session updates the
same row in between the time the first session began and the UPDATE
statement in the first session starts, then the first session will
return 0 rows affected. It will continue to return 0 rows affected for
each loop, as long as the same transaction/session is still in effect,
which in the code above, is the case.
> The particular issue here is
> https://bugs.launchpad.net/neutron/+bug/1382064 with the proposed fix:
> https://review.openstack.org/#/c/129288
>
> So far the solution proven by tests is to change transaction isolation
> level for mysql to be 'READ COMMITTED'.
> The patch suggests changing the level for particular transaction where
> issue occurs (per sqlalchemy, it will be reverted to engine default once
> transaction is committed)
> This isolation level allows the code above to see different result in
> each iteration.
Not for PostgreSQL, see above. You would need to set the level to READ
*UNCOMMITTED* to get that behaviour for PostgreSQL, and setting to READ
UNCOMMITTED is opening up the code to a variety of other issues and
should be avoided.
> At the same time, any code that relies that repeated query under same
> transaction gives the same result may potentially break.
>
> So the question is: what do you think about changing the default
> isolation level to READ COMMITTED for mysql project-wise?
> It is already so for postgress, however we don't have much concurrent
> test coverage to guarantee that it's safe to move to a weaker isolation
> level.
PostgreSQL READ COMMITTED is the same as MySQL's REPEATABLE READ. :) So,
no, it doesn't work for PostgreSQL either.
The design of the Neutron plugin code's interaction with the SQLAlchemy
session object is the main problem here. Instead of doing all of this
within a single transactional container, the code should instead be
changed to perform the SELECT statements in separate transactions/sessions.
That means not using the session parameter supplied to the
neutron.plugins.ml2.drivers.helpers.TypeDriverHelper.allocate_partially_specified_segment()
method, and instead performing the SQL statements in separate transactions.
Mike Bayer's EngineFacade blueprint work should hopefully unclutter the
current passing of a session object everywhere, but until that hits, it
should be easy enough to simply ensure that you don't use the same
session object over and over again, instead of changing the isolation level.
All the best,
-jay
> Your feedback is appreciated.
>
> Thanks,
> Eugene.
>
>
>
> _______________________________________________
> OpenStack-dev mailing list
> OpenStack-dev at lists.openstack.org
> http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
>
More information about the OpenStack-dev
mailing list