(Remember, I still know next to nothing about Tornado, so this is pure assumption on my part)
By default, SQLAlchemy scoped sessions use thread-local variables to ensure that each thread gets its own session. Tornado's asynchronous features process multiple requests "simultaneously" on the same thread. So consider this set of steps 1. Request 1 comes in for your "special" asynchronous action. Since this is the first request, a new thread-local session gets created. 2. Your special action does whatever it is that causes it to pause and yield control back to Tornado 3. Request 2 comes in for a normal synchronous action. Tornado handles it on the same thread that processed Request 1. Since the session is still open from Request 1, it gets re-used, including whatever state was set up in Request 1. 4. At the end of Request 2, you call session.commit/rollback/close 5. Tornado hands control back to the Request 1 handler, which now checks out a new session (because its previous one has been closed). Any state that Request 1 had previously put in the session will have been committed or rolled back at the end of Request 2 Putting a "session.close" at the beginning of every request won't really fix the problem. It just means that in the example above, Request 2 would throw away whatever Request 1 was doing. I don't think you can use threadlocal scoped sessions with asynchronous tornado code. You could use a different scoping mechanism (see the scope_func parameter to scoped_session at http://docs.sqlalchemy.org/en/rel_0_8/orm/session.html#sqlalchemy.orm.scoping.scoped_session), or you could get rid of scoped sessions altogether, and instead create a session at the beginning of a request and pass it to your handler functions (or attach it to the Request object). Simon On Thu, Sep 5, 2013 at 8:08 AM, herzaso <herz...@gmail.com> wrote: > Thanks Simon, > I have one action that is a bit "different" and might be a culprit. All > other actions are normal with no special asynchronous stuff, > but before I try to track down this condition, do you think a simple > session.close at the beginning of each request will do the job? > > Thanks, > Ofir > > > On Tuesday, September 3, 2013 3:54:16 PM UTC+3, Simon King wrote: >> >> I don't honestly know, but if this were my project I would be trying >> very hard to ensure that both (session.commit or session.rollback) >> followed by session.close were being called at the end of every single >> request. >> >> Are you using any of Tornado's asynchronous capabilities (eg. >> >> http://www.tornadoweb.org/en/stable/overview.html#non-blocking-asynchronous-requests)? >> If so, I don't imagine they would interact very well with a scoped >> session. >> >> On Tue, Sep 3, 2013 at 1:40 PM, Ofir Herzas <her...@gmail.com> wrote: >> > I have a gateway class (RequestHandler) that all requests get through. >> > In the handler, I have a try ... finally block, and the session.close >> > call >> > is in the finally clause. >> > Let's say that I have a situation where I don't close the session ... >> > could >> > that lead to this issue assuming that I commit changes at the end of >> > each >> > request? >> > >> > Thanks, >> > Ofir >> > >> > -----Original Message----- >> > From: sqlal...@googlegroups.com [mailto:sqlal...@googlegroups.com] On >> > Behalf Of Simon King >> > Sent: Tuesday, September 03, 2013 3:28 PM >> > To: sqlal...@googlegroups.com >> > Subject: Re: [sqlalchemy] Occasional IntegrityError when identifying >> > model >> > not by its ID >> > >> > OK, I agree that doesn't sound like a race as such. It sounds more like >> > some >> > connection state is being shared between requests. >> > >> > I haven't used Tornado before and don't know much about it. Its main >> > feature >> > is that it uses asynchronous IO, unlike most other python web servers >> > that >> > use threads for request handling. How do you make sure that a single >> > request >> > uses a single SQLAlchemy session, and that the session gets closed >> > properly >> > at the end of the request? >> > >> > On Tue, Sep 3, 2013 at 1:08 PM, Ofir Herzas <her...@gmail.com> wrote: >> >> I Agree, but when there is very little load on the system (e.g. 5 >> >> logged-in >> >> users) what are the odds that the same user will get the same error >> >> 10 times? >> >> >> >> On 3 Sep, 2013 3:05 PM, "Simon King" <si...@simonking.org.uk> wrote: >> >>> >> >>> Race conditions can happen at any time, not just when the system is >> >>> under heavy load. You only need 2 requests to arrive at approximately >> >>> the same time to trigger a race condition. >> >>> >> >>> On Tue, Sep 3, 2013 at 12:49 PM, herzaso <her...@gmail.com> wrote: >> >>> > Another important thing - I don't think it's a race condition - the >> >>> > load on the system isn't that high and users getting this error >> >>> > continue to get it all the time >> >>> > >> >>> > >> >>> > On Tuesday, September 3, 2013 2:47:26 PM UTC+3, herzaso wrote: >> >>> >> >> >>> >> I've added session.close in my get method just to see if it solves >> >>> >> the issue and it doesn't! >> >>> >> I'm so frustrated with this issue... not only do I feel helpless, >> >>> >> I don't have any clue on how to get around it ... >> >>> >> >> >>> >> What if I make the change without the session? Would the session >> >>> >> pick up the changes on its first query? >> >>> >> >> >>> >> On Monday, September 2, 2013 3:58:02 PM UTC+3, Simon King wrote: >> >>> >>> >> >>> >>> I'm no expert on isolation levels - I was just trying to help you >> >>> >>> understand what the problem was :-) >> >>> >>> >> >>> >>> Fixing it really depends on how your application is supposed to >> >>> >>> work in the face of concurrent requests. For this specific part >> >>> >>> of the application, you probably want to be able to see the Foo >> >>> >>> object that was created by the other transaction. Reducing the >> >>> >>> transaction isolation is probably the easiest way to do that, but >> >>> >>> might have knock-on effects in your application, so you ought to >> >>> >>> think carefully before doing it. >> >>> >>> >> >>> >>> The alternative is to discard the existing session state when you >> >>> >>> get into this situation (via session.close) and start a new >> > transaction. >> >>> >>> However, it wouldn't be appropriate to do this inside your "get" >> >>> >>> method - "session lifecycle" operations like this really belong >> >>> >>> at an outer scope, so making a change like this may require a >> >>> >>> certain amount of restructuring. >> >>> >>> >> >>> >>> Basically, dealing with concurrent operations is hard, and >> >>> >>> SQLAlchemy isn't going to magically make it any easier I'm afraid. >> >>> >>> >> >>> >>> Simon >> >>> >>> >> >>> >>> On Mon, Sep 2, 2013 at 1:40 PM, herzaso <her...@gmail.com> wrote: >> >>> >>> > I'm sorry, it was a misunderstanding on my part regarding the >> >>> >>> > transactions. >> >>> >>> > So what are you saying? that I should replace the transaction >> >>> >>> > isolation level? >> >>> >>> > >> >>> >>> > >> >>> >>> > On Monday, September 2, 2013 3:29:25 PM UTC+3, Simon King wrote: >> >>> >>> >> >> >>> >>> >> What exactly do you mean by not using transactions? The >> >>> >>> >> Session always works within a transaction: >> >>> >>> >> >> >>> >>> >> >> >>> >>> >> >> >>> >>> >> >> >>> >>> >> http://docs.sqlalchemy.org/en/rel_0_8/orm/session.html#managin >> >>> >>> >> g-transactions >> >>> >>> >> >> >>> >>> >> I assume you are also using InnoDB tables. >> >>> >>> >> >> >>> >>> >> On Mon, Sep 2, 2013 at 1:19 PM, herzaso <her...@gmail.com> >> >>> >>> >> wrote: >> >>> >>> >> > I do have it set as REPEATABLE READ. >> >>> >>> >> > However, I don't use transactions in sqlalchemy >> >>> >>> >> > >> >>> >>> >> > >> >>> >>> >> > On Monday, September 2, 2013 3:08:58 PM UTC+3, Simon King >> >>> >>> >> > wrote: >> >>> >>> >> >> >> >>> >>> >> >> Do you know what transaction isolation level you are running >> > at? >> >>> >>> >> >> The >> >>> >>> >> >> default apparently is "REPEATABLE READ": >> >>> >>> >> >> >> >>> >>> >> >> >> >>> >>> >> >> >> >>> >>> >> >> >> >>> >>> >> >> >> >>> >>> >> >> http://dev.mysql.com/doc/refman/5.6/en/set-transaction.html >> >>> >>> >> >> #isolevel_repeatable-read >> >>> >>> >> >> >> >>> >>> >> >> The important sentence in that link is: >> >>> >>> >> >> >> >>> >>> >> >> All consistent reads within the same transaction read the >> >>> >>> >> >> snapshot established by the first read >> >>> >>> >> >> >> >>> >>> >> >> When you query the database for the first time, to see if >> >>> >>> >> >> the entity already exists, you are setting that initial >> >>> >>> >> >> snapshot. If you run the same query again (such as in your >> >>> >>> >> >> exception handler), you will get the same results, whether >> >>> >>> >> >> or not another connection has inserted a matching row in >> >>> >>> >> >> the meantime. >> >>> >>> >> >> >> >>> >>> >> >> Simon >> >>> >>> >> >> >> >>> >>> >> >> On Mon, Sep 2, 2013 at 12:54 PM, herzaso <her...@gmail.com> >> >>> >>> >> >> wrote: >> >>> >>> >> >> > I'm not sure what to make of the results: >> >>> >>> >> >> > On the first connection, I ran BEGIN and INSERT and both >> >>> >>> >> >> > were successful, but when I tried the INSERT statement on >> >>> >>> >> >> > the second connection, I got "ERROR >> >>> >>> >> >> > 1205 (HY000): Lock wait timeout exceeded; try restarting >> >>> >>> >> >> > transaction". >> >>> >>> >> >> > Running the same query on the first connection produced >> >>> >>> >> >> > the required result which is "ERROR 1062 (23000): >> >>> >>> >> >> > Duplicate entry" >> >>> >>> >> >> > After the ROLLBACK on the first connection, the INSERT >> >>> >>> >> >> > statement worked well on the second connection >> >>> >>> >> >> > >> >>> >>> >> >> > Regarding your second remark, the answer is yes, the >> >>> >>> >> >> > error was due to the unique constraint on those columns >> >>> >>> >> >> > >> >>> >>> >> >> > BTW: I'm working on MySQL >> >>> >>> >> >> > >> >>> >>> >> >> > On Monday, September 2, 2013 1:31:12 PM UTC+3, Simon King >> >>> >>> >> >> > wrote: >> >>> >>> >> >> >> >> >>> >>> >> >> >> I don't really know the answer, but I'd be interested in >> >>> >>> >> >> >> the results of this experiment: >> >>> >>> >> >> >> >> >>> >>> >> >> >> Forget about SQLAlchemy for the moment, and start 2 >> >>> >>> >> >> >> plain SQL connections to your database. In the first, >> >>> >>> >> >> >> type something like the >> >>> >>> >> >> >> following: >> >>> >>> >> >> >> >> >>> >>> >> >> >> BEGIN; >> >>> >>> >> >> >> INSERT foo(bar, baz, qux) VALUES(1, 1, 1); >> >>> >>> >> >> >> >> >>> >>> >> >> >> Now in the second connection do the same. I assume it'll >> >>> >>> >> >> >> fail because of the duplicate values. >> >>> >>> >> >> >> >> >>> >>> >> >> >> Now in the first connection issue a "ROLLBACK". You >> >>> >>> >> >> >> should now be in a state where no matching row exists in >> >>> >>> >> >> >> the database, even though you received an error about >> >>> >>> >> >> >> constraint violations. >> >>> >>> >> >> >> >> >>> >>> >> >> >> The results you see may be different, depending on your >> >>> >>> >> >> >> transaction isolation level. (It may be that you don't >> >>> >>> >> >> >> get the constraint violation at all until you try to >> >>> >>> >> >> >> commit the second connection). >> >>> >>> >> >> >> >> >>> >>> >> >> >> Another thing you could look at: are you sure that the >> >>> >>> >> >> >> error you are getting is due to the unique constraint on >> >>> >>> >> >> >> bar/baz/qux, and not some other constraint in the >> >>> >>> >> >> >> database? >> >>> >>> >> >> >> >> >>> >>> >> >> >> Simon >> >>> >>> >> >> >> >> >>> >>> >> >> >> On Mon, Sep 2, 2013 at 8:45 AM, herzaso >> >>> >>> >> >> >> <her...@gmail.com> >> >>> >>> >> >> >> wrote: >> >>> >>> >> >> >> > I'm afraid it didn't solve my problem. >> >>> >>> >> >> >> > >> >>> >>> >> >> >> > Here is my updated method: >> >>> >>> >> >> >> > @classmethod >> >>> >>> >> >> >> > def get(cls, bar=None, baz=None, qux=None, >> >>> >>> >> >> >> > **kwargs): >> >>> >>> >> >> >> > query = session.query(cls).\ >> >>> >>> >> >> >> > filter(cls.bar == bar).\ >> >>> >>> >> >> >> > filter(cls.baz == baz).\ >> >>> >>> >> >> >> > filter(cls.qux == qux) >> >>> >>> >> >> >> > >> >>> >>> >> >> >> > item = query.first() >> >>> >>> >> >> >> > updated = False >> >>> >>> >> >> >> > >> >>> >>> >> >> >> > if not item: >> >>> >>> >> >> >> > try: >> >>> >>> >> >> >> > with session.begin_nested(): # run >> > inside >> >>> >>> >> >> >> > a >> >>> >>> >> >> >> > SAVEPOINT >> >>> >>> >> >> >> > updated = True >> >>> >>> >> >> >> > item = cls(bar=bar, baz=baz, >> >>> >>> >> >> >> > qux=qux, >> >>> >>> >> >> >> > **kwargs) >> >>> >>> >> >> >> > session.add(item) >> >>> >>> >> >> >> > session.flush() >> >>> >>> >> >> >> > except sa.exc.IntegrityError: >> >>> >>> >> >> >> > item = query.first() >> >>> >>> >> >> >> > if not item: >> >>> >>> >> >> >> > raise >> >>> >>> >> >> >> > Exception("invalidIntegrityError") >> >>> >>> >> >> >> > except: >> >>> >>> >> >> >> > raise >> >>> >>> >> >> >> > >> >>> >>> >> >> >> > if not updated: >> >>> >>> >> >> >> > for k, v in kwargs.iteritems(): >> >>> >>> >> >> >> > if getattr(item, k) != v: >> >>> >>> >> >> >> > setattr(item, k, v) >> >>> >>> >> >> >> > >> >>> >>> >> >> >> > return item >> >>> >>> >> >> >> > >> >>> >>> >> >> >> > With this code, i'm getting invalidIntegrityError. How >> >>> >>> >> >> >> > is it possible? >> >>> >>> >> >> >> > (it's also worth pointing out that this solution >> >>> >>> >> >> >> > requires SA >> >>> >>> >> >> >> > 0.8.2 >> >>> >>> >> >> >> > (otherwise, there is a problem with >> >>> >>> >> >> >> > session.begin_nested) >> >>> >>> >> >> >> > >> >>> >>> >> >> >> > >> >>> >>> >> >> >> > On Tuesday, August 27, 2013 6:40:03 PM UTC+3, Michael >> >>> >>> >> >> >> > Bayer >> >>> >>> >> >> >> > wrote: >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> I'm not a fan of catching integrity errors, i prefer >> >>> >>> >> >> >> >> to try to make sure they aren't going to happen, or >> >>> >>> >> >> >> >> if they are, they aren't a normal occurrence and the >> >>> >>> >> >> >> >> system is such that the particular operation can just >> >>> >>> >> >> >> >> fail (of >> >>> >>> >> >> >> >> course it depends on what it is). A problem with >> >>> >>> >> >> >> >> catching >> >>> >>> >> >> >> >> the >> >>> >>> >> >> >> >> integrity >> >>> >>> >> >> >> >> error due to concurrent, conflicting operations is >> >>> >>> >> >> >> >> that depending on backend and isolation level, you >> >>> >>> >> >> >> >> can't be totally sure when the error is going to get >> >>> >>> >> >> >> >> raised (e.g. serializable isolation vs. non). Also >> >>> >>> >> >> >> >> on a backend like Postgresql, the database can't >> >>> >>> >> >> >> >> recover the transaction after an integrity error >> >>> >>> >> >> >> >> unless you used a savepoint. >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> But here you're doing the "concurrent transactions >> >>> >>> >> >> >> >> need row identity X", so maybe it is appropriate >> >>> >>> >> >> >> >> here. Here is a rough idea of a transactional >> >>> >>> >> >> >> >> pattern for that, noting this isn't tested: >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> try: >> >>> >>> >> >> >> >> my_object = >> >>> >>> >> >> >> >> Session.query(MyClass).filter(....).one() >> >>> >>> >> >> >> >> except NoResultFound: >> >>> >>> >> >> >> >> try: >> >>> >>> >> >> >> >> with Session.begin_nested(): # run inside a >> >>> >>> >> >> >> >> SAVEPOINT >> >>> >>> >> >> >> >> my_object = MyClass(...) >> >>> >>> >> >> >> >> Session.add(my_object) >> >>> >>> >> >> >> >> Session.flush() >> >>> >>> >> >> >> >> except IntegrityError: >> >>> >>> >> >> >> >> my_object = >> >>> >>> >> >> >> >> Session.query(MyClass).filter(....).one() >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> On Aug 27, 2013, at 11:13 AM, herzaso >> >>> >>> >> >> >> >> <her...@gmail.com> >> >>> >>> >> >> >> >> wrote: >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> Suppose we are looking at a race condition, do you >> >>> >>> >> >> >> >> also think this should be handled by catching the >> >>> >>> >> >> >> >> IntegrityError? >> >>> >>> >> >> >> >> If so, what should I do? only flush and do the >> >>> >>> >> >> >> >> operation again? >> >>> >>> >> >> >> >> >> >>> >>> >> >> >> >> On Tuesday, August 27, 2013 5:42:23 PM UTC+3, Michael >> >>> >>> >> >> >> >> Bayer >> >>> >>> >> >> >> >> wrote: >> >>> >>> >> >> >> >>> >> >>> >>> >> >> >> >>> the word "occasional" is very meaningful. It >> >>> >>> >> >> >> >>> usually suggests race >> >>> >>> >> >> >> >>> conditions. Then with the word "tornado", the >> >>> >>> >> >> >> >>> baysean >> >>> >>> >> >> >> >>> filters >> >>> >>> >> >> >> >>> are >> >>> >>> >> >> >> >>> strongly leaning towards "race condition" at that >> >>> >>> >> >> >> >>> point :). >> >>> >>> >> >> >> >>> >> >>> >>> >> >> >> >>> if an error is occurring only under volume then you >> >>> >>> >> >> >> >>> have to revisit where race conditions can occur. >> >>> >>> >> >> >> >>> >> >>> >>> >> >> >> >>> On Aug 27, 2013, at 10:32 AM, herzaso >> >>> >>> >> >> >> >>> <her...@gmail.com> >> >>> >>> >> >> >> >>> wrote: >> >>> >>> >> >> >> >>> >> >>> >>> >> >> >> >>> I'm running a Tornado server without redundancy >> >>> >>> >> >> >> >>> (only one process, requests can arrive at the same >> >>> >>> >> >> >> >>> time but will be handled one at a >> >>> >>> >> >> >> >>> time) >> >>> >>> >> >> >> >>> I do agree that for large volumes, catching the >> >>> >>> >> >> >> >>> IntegrityError would be better, but currently I am >> >>> >>> >> >> >> >>> handling a single request at a time and I want to >> >>> >>> >> >> >> >>> fix this problem before I move on ... >> >>> >>> >> >> >> >>> >> >>> >>> >> >> >> >>> >> >>> >>> >> >> >> >>> On Tuesday, August 27, 2013 5:24:07 PM UTC+3, Simon >> >>> >>> >> >> >> >>> King >> >>> >>> >> >> >> >>> wrote: >> >>> >>> >> >> >> >>>> >> >>> >>> >> >> >> >>>> On Tue, Aug 27, 2013 at 2:31 PM, herzaso >> >>> >>> >> >> >> >>>> <her...@gmail.com> >> >>> >>> >> >> >> >>>> wrote: >> >>> >>> >> >> >> >>>> > On Tuesday, August 27, 2013 3:55:50 PM UTC+3, >> >>> >>> >> >> >> >>>> > Simon King >> >>> >>> >> >> >> >>>> > wrote: >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> On Tue, Aug 27, 2013 at 1:40 PM, herzaso >> >>> >>> >> >> >> >>>> >> <her...@gmail.com> >> >>> >>> >> >> >> >>>> >> wrote: >> >>> >>> >> >> >> >>>> >> > I have a model with an ID column set as the >> >>> >>> >> >> >> >>>> >> > primary key, though i'd like to be able to >> >>> >>> >> >> >> >>>> >> > identify records by 3 other columns. >> >>> >>> >> >> >> >>>> >> > For this situation, I've added a classmethod >> >>> >>> >> >> >> >>>> >> > that will fetch the record if found or a new >> >>> >>> >> >> >> >>>> >> > record if not. >> >>> >>> >> >> >> >>>> >> > The problem i'm having is that every once in a >> >>> >>> >> >> >> >>>> >> > while, I get IntegrityError trying to flush a >> >>> >>> >> >> >> >>>> >> > change >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> > class Foo(Base): >> >>> >>> >> >> >> >>>> >> > __table_args__ = >> >>> >>> >> >> >> >>>> >> > (sa.UniqueConstraint('bar', 'baz', >> >>> >>> >> >> >> >>>> >> > 'qux'),) >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> > id = sa.Column(Identifier, >> >>> >>> >> >> >> >>>> >> > sa.Sequence('%s_id_seq' >> >>> >>> >> >> >> >>>> >> > % >> >>> >>> >> >> >> >>>> >> > __tablename__), nullable=False, >> >>> >>> >> >> >> >>>> >> > primary_key=True) >> >>> >>> >> >> >> >>>> >> > bar = sa.Column(sa.BigInteger) >> >>> >>> >> >> >> >>>> >> > baz = sa.Column(sa.BigInteger) >> >>> >>> >> >> >> >>>> >> > qux = sa.Column(sa.BigInteger) >> >>> >>> >> >> >> >>>> >> > a1 = sa.Column(sa.BigInteger) >> >>> >>> >> >> >> >>>> >> > a2 = sa.Column(sa.BigInteger) >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> > @classmethod >> >>> >>> >> >> >> >>>> >> > def get(cls, bar=None, baz=None, qux=None, >> >>> >>> >> >> >> >>>> >> > **kwargs): >> >>> >>> >> >> >> >>>> >> > item = session.query(cls).\ >> >>> >>> >> >> >> >>>> >> > filter(cls.bar== bar).\ >> >>> >>> >> >> >> >>>> >> > filter(cls.baz == baz).\ >> >>> >>> >> >> >> >>>> >> > filter(cls.qux == qux).\ >> >>> >>> >> >> >> >>>> >> > first() >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> > if item: >> >>> >>> >> >> >> >>>> >> > for k, v in kwargs.iteritems(): >> >>> >>> >> >> >> >>>> >> > if getattr(item, k) != v: >> >>> >>> >> >> >> >>>> >> > setattr(item, k, v) >> >>> >>> >> >> >> >>>> >> > else: >> >>> >>> >> >> >> >>>> >> > item = cls(bar=bar, baz=baz, >> >>> >>> >> >> >> >>>> >> > qux=qux, >> >>> >>> >> >> >> >>>> >> > **kwargs) >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> > return item >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> > This is the code I use to add/update records: >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> > foo = Foo.get(**item) >> >>> >>> >> >> >> >>>> >> > session.merge(foo) >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> > I'm struggling with this problem for some time >> >>> >>> >> >> >> >>>> >> > now, and would appreciate any help ... >> >>> >>> >> >> >> >>>> >> > >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> I'm not sure of the exact problem, but there are >> >>> >>> >> >> >> >>>> >> a couple of things that you could investigate. >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> Firstly, session.merge returns a copy of the >> >>> >>> >> >> >> >>>> >> object, rather than adding the object that you >> >>> >>> >> >> >> >>>> >> supplied into the session. >> >>> >>> >> >> >> >>>> >> See >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> http://docs.sqlalchemy.org/en/rel_0_8/orm/sessio >> >>> >>> >> >> >> >>>> >> n.html#merging >> >>> >>> >> >> >> >>>> >> for >> >>> >>> >> >> >> >>>> >> details. >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> Secondly, your "get" method sometimes returns >> >>> >>> >> >> >> >>>> >> objects that are already part of the session (if >> >>> >>> >> >> >> >>>> >> they were in the database), and sometimes >> >>> >>> >> >> >> >>>> >> objects that are not in the session. It would >> >>> >>> >> >> >> >>>> >> probably be more consistent to always return >> >>> >>> >> >> >> >>>> >> objects that are part of the session, by putting >> >>> >>> >> >> >> >>>> >> "session.add(item)" in your "else" clause. >> >>> >>> >> >> >> >>>> >> This >> >>> >>> >> >> >> >>>> >> would >> >>> >>> >> >> >> >>>> >> get >> >>> >>> >> >> >> >>>> >> rid >> >>> >>> >> >> >> >>>> >> of the need for session.merge(). (If you want to >> >>> >>> >> >> >> >>>> >> be able to use the "get" with non-global >> >>> >>> >> >> >> >>>> >> sessions, pass the session as a >> >>> >>> >> >> >> >>>> >> parameter.) >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> Finally, if your session isn't auto-flushing, it >> >>> >>> >> >> >> >>>> >> would be possible for you to call "get" twice >> >>> >>> >> >> >> >>>> >> with the same parameters and get >> >>> >>> >> >> >> >>>> >> 2 >> >>> >>> >> >> >> >>>> >> different >> >>> >>> >> >> >> >>>> >> objects back. >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> You may want to look at the UniqueObject recipe >> >>> >>> >> >> >> >>>> >> in the >> >>> >>> >> >> >> >>>> >> wiki: >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> >> http://www.sqlalchemy.org/trac/wiki/UsageRecipes >> >>> >>> >> >> >> >>>> >> /UniqueObject >> >>> >>> >> >> >> >>>> >> >> >>> >>> >> >> >> >>>> > Hi Simon, >> >>> >>> >> >> >> >>>> > Thanks for the fast reply. >> >>> >>> >> >> >> >>>> > >> >>> >>> >> >> >> >>>> > I tried adding session.add(item) and >> >>> >>> >> >> >> >>>> > session.flush() in the else clause in the past >> >>> >>> >> >> >> >>>> > but that didn't solve my problem. >> >>> >>> >> >> >> >>>> > I didn't however remove the merge, do you think >> >>> >>> >> >> >> >>>> > that might be the problem? >> >>> >>> >> >> >> >>>> > >> >>> >>> >> >> >> >>>> > Regarding the flush, this code is part of an API >> >>> >>> >> >> >> >>>> > server where a scoped_session is committed after >> >>> >>> >> >> >> >>>> > each change. I haven't changed the autoflush >> >>> >>> >> >> >> >>>> > parameter, and as I understand the default value >> >>> >>> >> >> >> >>>> > is True making a flush before each commit or >> >>> >>> >> >> >> >>>> > query. >> >>> >>> >> >> >> >>>> > >> >>> >>> >> >> >> >>>> > As for the UniqueObject recipe, thanks! Amazing >> >>> >>> >> >> >> >>>> > that I never found it searching for a cure. As I >> >>> >>> >> >> >> >>>> > see it basically does the same ... >> >>> >>> >> >> >> >>>> > >> >>> >>> >> >> >> >>>> > I never managed to reproduce this bug on my >> >>> >>> >> >> >> >>>> > development environment. >> >>> >>> >> >> >> >>>> > It only >> >>> >>> >> >> >> >>>> > happens in my production environment. >> >>> >>> >> >> >> >>>> > Do you suppose adding a session.add and removing >> >>> >>> >> >> >> >>>> > the merge will solve this issue? >> >>> >>> >> >> >> >>>> > >> >>> >>> >> >> >> >>>> > Thanks, >> >>> >>> >> >> >> >>>> > Ofir >> >>> >>> >> >> >> >>>> >> >>> >>> >> >> >> >>>> It's difficult to say without knowing more about >> >>> >>> >> >> >> >>>> your system. >> >>> >>> >> >> >> >>>> For >> >>> >>> >> >> >> >>>> example, does your production system get multiple >> >>> >>> >> >> >> >>>> concurrent API requests, or are they serialised? If >> >>> >>> >> >> >> >>>> 2 requests can come in at approximately the same >> >>> >>> >> >> >> >>>> time and are handled by 2 different threads (or >> >>> >>> >> >> >> >>>> processes), then it is easy to imagine that the >> >>> >>> >> >> >> >>>> first handler will check the database, find that an >> >>> >>> >> >> >> >>>> entry doesn't exist, and create it. >> >>> >>> >> >> >> >>>> But before it flushes the change to the database >> >>> >>> >> >> >> >>>> (or even after it flushes, but before it commits, >> >>> >>> >> >> >> >>>> depending on your transaction isolation), the >> >>> >>> >> >> >> >>>> second handler will check for the same object, find >> >>> >>> >> >> >> >>>> it missing, and so create it. >> >>> >>> >> >> >> >>>> >> >>> >>> >> >> >> >>>> To track down problems like this, you could ensure >> >>> >>> >> >> >> >>>> that your development environment has the same >> >>> >>> >> >> >> >>>> thread/process behaviour as the production >> >>> >>> >> >> >> >>>> environment, then try submitting multiple >> >>> >>> >> >> >> >>>> concurrent requests to it. If you add "time.sleep" >> >>> >>> >> >> >> >>>> statements somewhere between the creation of the >> >>> >>> >> >> >> >>>> object and the commit of the transaction you will >> >>> >>> >> >> >> >>>> probably find it easier to trigger. >> >>> >>> >> >> >> >>>> >> >>> >>> >> >> >> >>>> To actually fix the problem, you could choose to >> >>> >>> >> >> >> >>>> only handle a single request at a time (fine if you >> >>> >>> >> >> >> >>>> don't expect a high volume of requests). If that's >> >>> >>> >> >> >> >>>> not acceptable, you could catch the IntegrityError >> >>> >>> >> >> >> >>>> and then re-process the request. >> >>> >>> >> >> >> >>>> >> >>> >>> >> >> >> >>>> Hope that helps, >> >>> >>> >> >> >> >>>> >> >>> >>> >> >> >> >>>> Simon >> >>> >>> >> >> >> >>> > > -- > 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 sqlalchemy+unsubscr...@googlegroups.com. > To post to this group, send email to sqlalchemy@googlegroups.com. > Visit this group at http://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/groups/opt_out. -- 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 sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at http://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/groups/opt_out.