<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Nov 19, 2014, at 1:49 PM, Ryan Moats <<a href="mailto:rmoats@us.ibm.com" class="">rmoats@us.ibm.com</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div class=""><p class=""><font size="2" face="sans-serif" class="" style="font-size: 15px;">I was waiting for this because I think I may have a slightly different (and outside of the box) view on how to approach a solution to this.</font><br class="" style="font-size: 20px;">
<br class="" style="font-size: 20px;">
<font size="2" face="sans-serif" class="" style="font-size: 15px;">Conceptually (at least in my mind) there isn't a whole lot of difference between how the example below (i.e. updates from two concurrent threads) is handled</font><br class="" style="font-size: 20px;">
<font size="2" face="sans-serif" class="" style="font-size: 15px;">and how/if neutron wants to support a multi-master database scenario (which in turn lurks in the background when one starts thinking/talking about multi-region support).</font><br class="" style="font-size: 20px;">
<br class="" style="font-size: 20px;">
<font size="2" face="sans-serif" class="" style="font-size: 15px;">If neutron wants to eventually support multi-master database scenarios, I see two ways to go about it:</font><br class="" style="font-size: 20px;">
<br class="" style="font-size: 20px;">
<font size="2" face="sans-serif" class="" style="font-size: 15px;">1) Defer multi-master support to the database itself.</font><br class="" style="font-size: 20px;">
<font size="2" face="sans-serif" class="" style="font-size: 15px;">2) Take responsibility for managing the conflict resolution inherent in multi-master scenarios itself.</font><br class="" style="font-size: 20px;">
<br class="" style="font-size: 20px;">
<font size="2" face="sans-serif" class="" style="font-size: 15px;">The first approach is certainly simpler in the near term, but it has the down side of restricting the choice of databases to those that have solved multi-master and further, may lead to code bifurcation based on possibly different solutions to the conflict resolution scenarios inherent in multi-master.</font><br class="" style="font-size: 20px;"></p></div></div></blockquote><blockquote type="cite" class=""><div class=""><div class=""><p class=""><font size="2" face="sans-serif" class="" style="font-size: 15px;">The second approach is certainly more complex as neutron assumes more responsibility for its own actions, but it has the advantage that (if done right) would be transparent to the underlying databases (with all that implies)</font><br class="" style="font-size: 20px;"></p></div></div></blockquote><div><div>multi-master is a very advanced use case so I don’t see why it would be unreasonable to require a multi-master vendor database.   Reinventing a complex system like that in the application layer is an unnecessary reinvention.</div><div><br class=""></div><div>As far as working across different conflict resolution scenarios, while there may be differences across backends, these differences will be much less significant compared to the differences against non-clustered backends in which we are inventing our own multi-master solution.   I doubt a home rolled solution would insulate us at all from “code bifurcation” as this is already a fact of life in targeting different backends even without any implication of clustering.   Even with simple things like transaction isolation, we see that different databases have different behavior, and if you look at the logic in oslo.db inside of <a href="https://github.com/openstack/oslo.db/blob/master/oslo/db/sqlalchemy/exc_filters.py" class="">https://github.com/openstack/oslo.db/blob/master/oslo/db/sqlalchemy/exc_filters.py</a> you can see an example of just how complex it is to just do the most rudimental task of organizing exceptions into errors that mean the same thing.</div></div><div><br class=""></div><div><br class=""></div><blockquote type="cite" class=""><div class=""><div class=""><p class=""><font size="2" face="sans-serif" class="" style="font-size: 15px;">My reason for asking this question here is that if the community wants to consider #2, then these problems are the place to start crafting that solution - if we solve the conflicts inherent with the  two conncurrent thread scenarios, then I think we will find that we've solved the multi-master problem essentially "for free”.</font><br class="" style="font-size: 20px;"></p></div></div></blockquote><div><br class=""></div><div>Maybe I’m missing something, if we learn how to write out a row such that a concurrent transaction against the same row doesn’t throw us off, where is the part where that data is replicated to databases running concurrently on other IP numbers in a way that is atomic come out of that effort “for free” ?   A home-rolled “multi master” scenario would have to start with a system that has multiple create_engine() calls, since we need to communicate directly to multiple database servers. From there it gets really crazy.  Where’s all that ?</div><div><br class=""></div><div><br class=""></div><div><br class=""></div><br class=""><blockquote type="cite" class=""><div class=""><div class=""><p class="">
<br class="" style="font-size: 20px;">
<font size="2" face="sans-serif" class="" style="font-size: 15px;">Ryan Moats</font><br class="" style="font-size: 20px;">
<br class="">
<tt class=""><font size="2" class="">Mike Bayer <<a href="mailto:mbayer@redhat.com" class="">mbayer@redhat.com</a>> wrote on 11/19/2014 12:05:35 PM:<br class="">
<br class="">
> From: Mike Bayer <<a href="mailto:mbayer@redhat.com" class="">mbayer@redhat.com</a>></font></tt><br class="">
<tt class=""><font size="2" class="">> To: "OpenStack Development Mailing List (not for usage questions)" <br class="">
> <<a href="mailto:openstack-dev@lists.openstack.org" class="">openstack-dev@lists.openstack.org</a>></font></tt><br class="">
<tt class=""><font size="2" class="">> Date: 11/19/2014 12:05 PM</font></tt><br class="">
<tt class=""><font size="2" class="">> Subject: Re: [openstack-dev] [Neutron] DB: transaction isolation and<br class="">
> related questions</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> On Nov 18, 2014, at 1:38 PM, Eugene Nikanorov <<a href="mailto:enikanorov@mirantis.com" class="">enikanorov@mirantis.com</a>> wrote:</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> Hi neutron folks,</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> There is an ongoing effort to refactor some neutron DB logic to be <br class="">
> compatible with galera/mysql which doesn't support locking <br class="">
> (with_lockmode('update')).</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> Some code paths that used locking in the past were rewritten to <br class="">
> retry the operation if they detect that an object was modified concurrently.</font></tt><br class="">
<tt class=""><font size="2" class="">> The problem here is that all DB operations (CRUD) are performed in <br class="">
> the scope of some transaction that makes complex operations to be <br class="">
> executed in atomic manner.</font></tt><br class="">
<tt class=""><font size="2" class="">> For mysql the default transaction isolation level is 'REPEATABLE <br class="">
> READ' which means that once the code issue a query within a <br class="">
> transaction, this query will return the same result while in this <br class="">
> transaction (e.g. the snapshot is taken by the DB during the first <br class="">
> query and then reused for the same query).</font></tt><br class="">
<tt class=""><font size="2" class="">> In other words, the retry logic like the following will not work:</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> def allocate_obj():</font></tt><br class="">
<tt class=""><font size="2" class="">>     with session.begin(subtrans=True):</font></tt><br class="">
<tt class=""><font size="2" class="">>          for i in xrange(n_retries):</font></tt><br class="">
<tt class=""><font size="2" class="">>               obj = session.query(Model).filter_by(filters)</font></tt><br class="">
<tt class=""><font size="2" class="">>               count = session.query(Model).filter_by(id=obj.id<br class="">
> ).update({'allocated': True})</font></tt><br class="">
<tt class=""><font size="2" class="">>               if count:</font></tt><br class="">
<tt class=""><font size="2" class="">>                    return obj</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> since usually methods like allocate_obj() is called from within <br class="">
> another transaction, we can't simply put transaction under 'for' <br class="">
> loop to fix the issue.</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> has this been confirmed?  the point of systems like repeatable read <br class="">
> is not just that you read the “old” data, it’s also to ensure that <br class="">
> updates to that data either proceed or fail explicitly; locking is <br class="">
> also used to prevent concurrent access that can’t be reconciled.  A <br class="">
> lower isolation removes these advantages.  </font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> I ran a simple test in two MySQL sessions as follows:</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> session 1:</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> create table some_table(data integer) engine=innodb;</font></tt><br class="">
<tt class=""><font size="2" class="">> Query OK, 0 rows affected (0.01 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> insert into some_table(data) values (1);</font></tt><br class="">
<tt class=""><font size="2" class="">> Query OK, 1 row affected (0.00 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> begin;</font></tt><br class="">
<tt class=""><font size="2" class="">> Query OK, 0 rows affected (0.00 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> select data from some_table;</font></tt><br class="">
<tt class=""><font size="2" class="">> +------+</font></tt><br class="">
<tt class=""><font size="2" class="">> | data |</font></tt><br class="">
<tt class=""><font size="2" class="">> +------+</font></tt><br class="">
<tt class=""><font size="2" class="">> |    1 |</font></tt><br class="">
<tt class=""><font size="2" class="">> +------+</font></tt><br class="">
<tt class=""><font size="2" class="">> 1 row in set (0.00 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> session 2:</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> begin;</font></tt><br class="">
<tt class=""><font size="2" class="">> Query OK, 0 rows affected (0.00 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> update some_table set data=2 where data=1;</font></tt><br class="">
<tt class=""><font size="2" class="">> Query OK, 1 row affected (0.00 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> Rows matched: 1  Changed: 1  Warnings: 0</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> then back in session 1, I ran:</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> update some_table set data=3 where data=1;</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> this query blocked;  that’s because session 2 has placed a write <br class="">
> lock on the table.  this is the effect of repeatable read isolation.</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> while it blocked, I went to session 2 and committed the in-progress <br class="">
> transaction:</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> commit;</font></tt><br class="">
<tt class=""><font size="2" class="">> Query OK, 0 rows affected (0.00 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> then session 1 unblocked, and it reported, correctly, that zero rows<br class="">
> were affected:</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> Query OK, 0 rows affected (7.29 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> Rows matched: 0  Changed: 0  Warnings: 0</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> the update had not taken place, as was stated by “rows matched":</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> mysql> select * from some_table;</font></tt><br class="">
<tt class=""><font size="2" class="">> +------+</font></tt><br class="">
<tt class=""><font size="2" class="">> | data |</font></tt><br class="">
<tt class=""><font size="2" class="">> +------+</font></tt><br class="">
<tt class=""><font size="2" class="">> |    1 |</font></tt><br class="">
<tt class=""><font size="2" class="">> +------+</font></tt><br class="">
<tt class=""><font size="2" class="">> 1 row in set (0.00 sec)</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> the code in question would do a retry at this point; it is checking <br class="">
> the number of rows matched, and that number is accurate.</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> if our code did *not* block at the point of our UPDATE, then it <br class="">
> would have proceeded, and the other transaction would have <br class="">
> overwritten what we just did, when it committed.   I don’t know that<br class="">
> read committed is necessarily any better here.</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> now perhaps, with Galera, none of this works correctly.  That would <br class="">
> be a different issue in which case sure, we should use whatever <br class="">
> isolation is recommended for Galera.  But I’d want to potentially <br class="">
> peg it to the fact that Galera is in use, or not.</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> would love also to hear from Jay Pipes on this since he literally <br class="">
> wrote the book on MySQL ! :)</font></tt><br class="">
<tt class=""><font size="2" class="">> <br class="">
> _______________________________________________<br class="">
> OpenStack-dev mailing list<br class="">
> <a href="mailto:OpenStack-dev@lists.openstack.org" class="">OpenStack-dev@lists.openstack.org</a><br class="">
> <a href="http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev" class="">http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev</a><br class="">
</font></tt></p></div>_______________________________________________<br class="">OpenStack-dev mailing list<br class=""><a href="mailto:OpenStack-dev@lists.openstack.org" class="">OpenStack-dev@lists.openstack.org</a><br class="">http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev<br class=""></div></blockquote></div><br class=""></body></html>