On Fri, May 8, 2015 at 6:43 PM, Marc Aymerich <glicer...@gmail.com> wrote:

>
>
> On Fri, May 8, 2015 at 5:50 PM, Carl Meyer <c...@oddbird.net> wrote:
>
>> Hi Marc,
>>
>> On 05/08/2015 07:15 AM, Marc Aymerich wrote:
>> > I'm using atomic requests, but one of my views needs to save a model
>> > regardless of wheter the current transaction rolls back or not.
>> >
>> > I'm confused about how to proceed with the current autocommit behaviour.
>> > Do I need to use a different db connection? or perhaps there is some way
>> > of telling django to ignore autocommit for some particular block ?
>>
>> You'd need to use a different db connection, since what you're trying to
>> do violates the very nature of a database transaction. If you were just
>> trying to run some raw SQL, you could establish the separate connection
>> yourself manually, but if you're trying to save a Django model, you'll
>> probably need a second connection defined in your DATABASES setting.
>>
>> Possible alternatives:
>>
>> - If you're using a task queuing system (like Celery or rq), queue a
>> task to perform the save; you can do this without making it conditional
>> on the transaction committing, as long as your task queue is using a
>> store other than your primary database (e.g. Redis or RabbitMQ). Usually
>> when people do this it's unintentional and a bug (they really wanted the
>> task to execute only if the current transaction committed), but in your
>> case it could be a feature.
>>
>
> Yep, the thing is that I need to have this instance saved before handling
> it to a worker process, the worker process uses it and if something goes
> wrong on the main thread then the worker has an object that does not exists
> on the database. Now I realize that I can just catch the IntegrityError and
> re-save the object on the worker thread ;)
>
> obj.pk = None
> obj.save()
>

unfortunately this will not work in my case, because I need the object.id
to not change :(


> - Switch from ATOMIC_REQUESTS to explicit use of transaction.atomic() in
>> your views, so that you can place this "no matter what" save in its own
>> transaction.
>
>
> I've been doing something on those lines, but now I have this corner case
> which I have surouding code that needs to be wrapped up in a transaction :(
>
> For the record, after exploring django.db I realize of 2 more alternative
> ways of dealing with this situation:
>
> 1) because the connection pool is a local thread object, you can just
> fireup a separate thread, so it will have fresh connections :)
>
> t = threading.Thread(target=backend.create_log, args=args)
> t.start()
> log = t.join()
>
> 2) because a new connection is created for each settings.DATABASES, it is
> easy to have new connections with a fake database. I've also created a
> context manager for that
>


So my best option so far is to make that query on a separate connection,
and between worker threads (preferable than celery in this case) and moking
the database settings... the later is hacky but much faster :)

>>> from threading import Thread
>>> from django import db
>>> from django.conf import settings as djsettings
>>> class fresh_connection(object):
...     def __init__(self, origin, target):
...         self.origin = origin
...         self.target = target
...
...     def __enter__(self):
...         djsettings.DATABASES[self.target] =
djsettings.DATABASES[self.origin]
...         # Because db.connections.datases is a cached property
...         self.old_connections = db.connections
...         db.connections = db.utils.ConnectionHandler()
...
...     def __exit__(self, type, value, traceback):
...         db.connections[self.target].close()
...         djsettings.DATABASES.pop(self.target)
...         db.connections = self.old_connections
...
>>> def do_query(using='default'):
...     Account.objects.using(using).get(pk=1)
...
>>> from datetime import datetime
>>> now = datetime.now()
>>> for i in range(1, 100):
...     t = Thread(target=do_query)
...     t.start()
...     t.join()
...
>>> print((datetime.now()-now).total_seconds())
1.449795
>>>
>>> now = datetime.now()
>>> for i in range(1, 100):
...     with fresh_connection('default', 'other_default'):
...         do_query(using='other_default')
...
>>> print((datetime.now()-now).total_seconds())
0.106981



-- 
Marc

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To post to this group, send email to django-users@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/CA%2BDCN_vTQqiLBym4-9MLvED2NpfWzm1F%2Bdxh_vkm3TgXEL68KA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to