-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Persistent database connections with async workers on Django >= 1.6 fails #996
Comments
I'd change the title to something that makes clear the report is about Django >= 1.6 persistent DB connections feature. |
I'm going to try to add some extra info with an excerpt of an internal issue in which we are exploring & testing changing Django handles DB connections like this:
If the web server uses a concurrency model based on greenlets (
Some options are:
[1] This is actually per thread, but in actual web server deployment scenarios with e.g. Linux + apache or gunicorn, multithreading model is rarely used. Sync, multi-process (which means a one-to-one process-thread mapping) or async greenlets-based concurrency models are the usual choices. If the above analysis is correct, I'm not sure it's in gunicorn scope to 'fix' this. Don't think it's in Django scope either (i.e., [try to] detect what concurrency model does the web server it has been deployed to uses and adapt its handling of these persistent DB connections accordingly.) |
@ramiro I agree with your analysis. |
I think, given the analysis, that this will not be fixed. If multiple, concurrent connections are made by an async worker the application needs to use a thread safe connection pool to get safe, correct behavior. |
Re: "Now, if we set CONN_MAX_AGE != 0 then what happens is that connections aren't closed at the end of requests. So they start to accumulate." Why is that an issue with async workers? As soon as an async worker completes a request it still closes the connection? Why would connections accumulate then? By accumulate you mean connections are getting more and more (and finally exceeding the DB's connection limit)? I can understand that async workers produce more connections (because a single worker can handle more than 1 request at a time, hence opening more db connections) but once a certain number of connections is reached, it should converge. Additionally can someone explain why/how https://github.com/jneight/django-db-geventpool would help in this case? |
Could somebody (perhaps @ramiro or @tilgovi) please confirm that https://github.com/jneight/django-db-geventpool should actually help in this case? I think I ran into this problem but it kept occurring after installing django-db-geventpool (with the import from eventlet included, and the import from gevent not included). I was getting a log of "FATAL: sorry, too many clients already" which indicates that I opened up more than the allowed limit of connections to the database. CPU was also maxing out so perhaps that's why I kept getting the same error after installing django-db-geventpool...? |
Following up from my last post, it seems possible that the CPU max out was (at least partially) caused by the issue @Starefossen raised above. http://stackoverflow.com/a/43799635/805141 indicates this is a possibility. Also, shouldn't this issue be documented somewhere? |
The lack of issue resolution that I experienced may have been caused by jneight/django-db-geventpool#21 |
The default gunicorn worker class is 'gevent', which is not compatible with django's CONN_MAX_AGE because it leaks database connections. See: benoitc/gunicorn#996 (comment) This changes the default from 60 to 0.
The default gunicorn worker class is 'gevent', which is not compatible with django's CONN_MAX_AGE because it leaks database connections. See: benoitc/gunicorn#996 (comment) This changes the default from 60 to 0.
I think it is because the established connections will only be closed when requests starts or finishes if As for the Correct me if I'm wrong. :D |
In current design, connections will be kept alive to handle HTTP keepalive before releasing. New coming requests are handled in a new greenlet. The Django documentation states:
I guess the way django persistent connections work is to associate them to the thread id. If threads are patched in the gevent or eventlet workers, it is probably creating a new connection / greenlets which is limited by the number of maximum connections which may explains what you observe. If it' that then maybe django could be patched to actually use the real thread on which it run? Or by limiting the number of concurrent connections. |
* setting is not respected by celery < v4.2 * celery/celery#4292 * not compatible with 'gevent' gunicorn worker class * benoitc/gunicorn#996 (comment)
I made a patch to allow db pooling with async workers. |
Read this thread but I am lost. Does anyone know how to get DB connections properly reused when running with uvicorn?
I have the exact same issue of each request resulting in a new DB connection that never clears if I set CONN_MAX_AGE != 0 |
@patrik7 did you figure this out ? |
Nope, I resigned - I am still opening a new connection for every single HTTP request |
I noticed something when switching from
sync
to asynceventlet
workers in Gunicorn; Postgres which had been working perfectly so far begun complaining that maximum connection limit was reached, and it was refusing new connections. When enabling monitoring I could clearly see that new connections to the database was established for each HTTP request. Even thoughCONN_MAX_AGE
was set to60
database connections were not reused.After some digging around I found this post on serverfault which explains the a very similar situation which concludes that persistent database connections are indeed set up – but they are never reused in any sequential HTTP requests and they are eventually closed when they reach the max age.
Is this a known issues? I can not see it has been mentioned in this issue tracker, nor on eventlet.
The text was updated successfully, but these errors were encountered: