-
Notifications
You must be signed in to change notification settings - Fork 20
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
Don't use fastapi Depends for database sessions #136
Comments
Thanks for the report. This comes straight from the FastAPI tutorial on how to use SQL databases https://github.com/tiangolo/fastapi/blob/1c25e2d8dc99331290e99e17814ec4d83adce725/docs_src/sql_databases/sql_app_py39/main.py#L12-L18 |
I did a deep dive. The dependencies handling happens here and the actual call here.
So FastAPI does wrap the dependencies into a context manager and thus should terminate the session correctly. |
@pmeier I haven't looked at what ragna does. But here's context from conda-store:
|
Thanks a ton for the links @nkaretnikov! I think the relevant write-up is in tiangolo/fastapi#3205 (comment). TL;DR the deadlock happens because certain SQLAlchemy operations like As reported their, using a context manager inside the endpoint will solve the issue and is the easiest to implement. I'll do that in the short term. However, since are past Line 40 in d4ee0ee
we should probably want to use the async session anyway. And with that, we should be able to circumvent the issue as well. |
@pmeier Note: I haven't tried using async with our application, only with some testing code, so I'm not sure whether it'll help. But it might also require you switching to an async protocol for the DB and wrapping all your DB calls into async/await, which is why I didn't want to do it. E.g., we also use celery in some places and I didn't want that running in the async loop. |
All our endpoints are async anyway so making the DB calls async should not be an issue: Lines 102 to 112 in d4ee0ee
It should just be
In Ragna, the DB is completely detached from the task queue (although that is also running async) so this is not an issue. |
According to tiangolo/fastapi#3205 (comment) this was solved in tiangolo/fastapi#5122. The commit was included starting in Removing the bug label for now, since after the PR was merged, there were no more comments on the same thread indicating that the fix didn't work. The PR itself has a repro, so I'm going to try that to see if there is actually a problem here. |
@pmeier I cannot provide the version information because it's not pinned and I recreated my env recently. So I won't be able to tell whether I'm using the same version I used while fixing this issue in conda-store. |
I've ran the following script against from threading import Thread
import httpx
def main(n=1_000):
client = get_client()
threads = [
Thread(target=get_document, args=(client, f"document_{idx}.txt"))
for idx in range(n)
]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
def get_document(client, name):
return client.get("/document", params={"name": name})
def get_client():
client = httpx.Client(base_url="http://127.0.0.1:31476")
client.get("/").raise_for_status()
username = password = "user"
token = (
(
client.post(
"/token",
data={"username": username, "password": password},
)
)
.raise_for_status()
.json()
)
client.headers["Authorization"] = f"Bearer {token}"
return client
if __name__ == "__main__":
main() Meaning, roughly 1000 concurrent requests seems to be fine. That being said, @costrouc noted that sqlite might be special cased in SQLAlchemy (conda-incubator/conda-store#622 (review)). Thus, I need to rerun this against PostgreSQL or the like. |
My 2c:
|
we faced that along with serveral problems and had moved away to much better maintained Litestar. give a try. |
@v3ss0n Apart from this (potential!) wrinkle, FastAPI has been working well for us. I've never used Litestar so I can't really tell if it is "better" (in whatever sense) for our use case. If you feel strongly about this, please open a dedicated issue. However, until we have better test coverage, we won't swap a major dependency. |
Closing this as we no longer doing it anyway. Since a few of our endpoints take a relatively long time to process, i.e. answer and prepare, we don't want to keep the database connection open for the whole time. Meaning, we use context manager twice: first to retrieve something from the database, and later again to store the results of the computation: ragna/ragna/deploy/_api/core.py Lines 265 to 270 in c6bdb0f
ragna/ragna/deploy/_api/core.py Lines 313 to 315 in c6bdb0f
|
Bug description
I just saw https://github.com/Quansight/ragna/blob/main/ragna/_api/core.py#L104. This is the exact issue that we had in conda-store that Nikita found. Depends does not reliably terminate the session when the request ends. FastAPI doesn't guarantee when the depends statement will be terminated. In conda-store the fix was to create the session explicitly with a context manager or middleware. Deep dive in this PR conda-incubator/conda-store#622
How to reproduce the bug?
Open a bunch of api requests concurently and database connection pool will become exhausted (I don't have a test for this) just can report this issue happended in conda-store.
Versions and dependencies used
No response
Anything else?
No response
The text was updated successfully, but these errors were encountered: