Description
I'm using Django ORM + Mysql with fastapi
all works fine
but at some point when application have no requests for some period of time I'm getting
2006 mysql server gone away
Most likely Django does some connection checks or closing somehere in the request flow..
(I tried to play with CONN_MAX_AGE and close_old_connections in middleware - no luck)
any idea how to do it with fastapi ?
Try use asynchronous alternatives of Django ORM. For example, try it: https://github.com/tortoise/tortoise-orm
@prostomarkeloff thank you
but there is a requirement to re-use a lot of Django code.
I definitely think getting fastapi to work with the Django ORM is a reasonable goal, and I've heard of multiple people doing this.
I think there was a similar issue that came up recently related to connection staleness using asyncpg directly. (@prostomarkeloff note this implies that using an async ORM won't necessarily prevent this issue!)
If you want to prevent connection staleness issues, the easiest way is probably to do a "pre-ping" where you try to run an extremely cheap query (typically "select 1") using the ORM's db connection inside a middleware at the beginning of every request (or at least every request that makes use of the ORM). If it fails, you would then get a new connection (presumably the ORM would handle this for you, and you would just handle the exception in the middleware, but the right way to handle this may require some special handling specific to the Django ORM).
Note: despite the additional overhead, when I first saw this I was surprised to learn this is not considered a bad practice; it is discussed for sqlalchemy here: https://docs.sqlalchemy.org/en/13/core/pooling.html#dealing-with-disconnects (under the "pessimistic" handling section).
Given the amount of usage implied by this statement:
at some point when application have no requests for some period of time
(i.e., presumably you aren't pushing the limits of what your hardware can handle) my guess is that wasting the handful of milliseconds it would take to get the response to select 1 is worth it to prevent the occasional stale connection. (But that's obviously dependent on the priorities for your application.)
I am not familiar enough with django to be able to advise how to accomplish this, but I think it should be possible.
I suspect there is also a way to get the Django ORM to gracefully handle connection issues in an optimistic way, but again, I don't know enough about the Django ORM to advise.
Thanks for the discussion here!
@vitalik , does @dmontagu 's answer solve your problem? Did you solve it in any other way?
@tiangolo
Unfortunately - no
the goal is that connection should be checked / pinged before using ORM and closed if not responding
BUT the problem is that Django persist connection object to mysql inside threading.local
on the other hand starlette/fastapi run each operation in threadpool executor
meaning that middleware can run in one thread, but actual operation with ORM calls in other thread
which makes it impossible to check connection on some high level like middleware :(
@vitalik For what it's worth, you should be able to perform middleware-like operations inside the threadpool thread via a custom route handler: https://fastapi.tiangolo.com/advanced/custom-request-and-route/#custom-apiroute-class-in-a-router
Though admittedly this is less ergonomic than one would hope for.
Another approach might be to use the middleware to put a Django connection into a contextvar, which is passed into the threadpool executor's call. But I suspect that threadlocals are likely to cause problems if used in a concurrent context like this.
Hopefully as Django becomes more ASGI compatible they refactor into using contextvars rather than thread locals anyway. I'm not sure if there has been any discussion on this topic from the django devs but that might be a good topic to search for for ideas about how to work around this.
I use this decorator to close stale connections, don't know if it helps in the async world.
def ensure_fresh_db_connection(f):
@wraps(f)
def wrapper(*args, **kwargs):
django.db.close_old_connections()
result = f(*args, **kwargs)
return result
return wrapper
I'm not currently using Django, but some people have been doing experiments mixing Django and FastAPI, e.g. https://www.stavros.io/posts/fastapi-with-django/
Maybe that can help you with your use case.
Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.
This can help somebody https://www.stavros.io/posts/fastapi-with-django/
Hey guys, I also wrote an article about how to integrate Django ORM into FastAPI.
You might also what to check this and see how I did it:
https://kigawas.me/posts/integrate-fastapi-and-django-orm/
Corresponding github repo:
Most helpful comment
Try use
asynchronousalternatives of Django ORM. For example, try it: https://github.com/tortoise/tortoise-orm