| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
There was a problem hiding this comment.
It looks really reasonable to check whether the sub_dependency is already solved and cached before solving it!
Related PR, that uses almost the same approach: #4089 (closed)
Sorry, something went wrong.
That PR also has a test! :) I think that other PR, with your suggested changes, is a better change than this PR. Do you think it makes more sense to close this in favour of the other one? |
Sorry, something went wrong.
|
I've rebased and updated the PR with the suggested changes from @YuriiMotov and with the change to dependency_cache empty dict checking from #4089. |
Sorry, something went wrong.
|
@svlandeg Sure! I can spend some time on that later tonight. I would be very happy to get this merged so we can stop running a forked version of fastapi 🙂 |
Sorry, something went wrong.
|
@svlandeg I updated the code to add that test from #4089. I would have liked to create a similar test that didn't use include pydantic, but I wasn't able to figure out how to do that and still test the same thing. I verified that the test fails before this and passes after this. |
Sorry, something went wrong.
| # Use a unique object to compare against in case the cached value is None | ||
| cache_miss = object() | ||
| cached_value = dependency_cache.get(sub_dependant.cache_key, cache_miss) | ||
| # If the sub dependant is already cached, skip doing any more work | ||
| if cached_value is not cache_miss: | ||
| if sub_dependant.name is not None: | ||
| values[sub_dependant.name] = cached_value | ||
| continue |
There was a problem hiding this comment.
Are there any reasons not to simplify it this way?
| # Use a unique object to compare against in case the cached value is None | |
| cache_miss = object() | |
| cached_value = dependency_cache.get(sub_dependant.cache_key, cache_miss) | |
| # If the sub dependant is already cached, skip doing any more work | |
| if cached_value is not cache_miss: | |
| if sub_dependant.name is not None: | |
| values[sub_dependant.name] = cached_value | |
| continue | |
| if sub_dependant.cache_key in dependency_cache: | |
| cached_value = dependency_cache[sub_dependant.cache_key] | |
| if sub_dependant.name is not None: | |
| values[sub_dependant.name] = cached_value | |
| continue |
Sorry, something went wrong.
There was a problem hiding this comment.
dict.get(...) is atomic operation, and thus thread safe, while consequent key in dict; dict[key] is not
Sorry, something went wrong.
There was a problem hiding this comment.
Good point!
But currently dependency solving process is not parallel.
Also, it's highly unlikely (useless?) that somebody decide to delete existing key from dependency_cache.
I would vote for clarity in this case if nobody brings other arguments
Sorry, something went wrong.
There was a problem hiding this comment.
We have been running this fork in our production application for quite a while now, and before we changed to an atomic .get() we got hard to reproduce KeyErrors.
Sorry, something went wrong.
There was a problem hiding this comment.
LGTM
I still believe we can safely simplify this part, but it's not critical
Sorry, something went wrong.
I would have thought it could be simplified too, but we did get KeyErrors before introducing that change to an atomic operation. It only happened a couple of times a week in total, but that's still too many times. 🤷♀️ It might be that we are doing something unusually weird with the dependency injection system in our app, but I couldn't find a clean way to fix the issue without this change in FastAPI. Any idea if this has a chance to get merged soon? I would love to not have an internal fork of FastAPI anymore 😄 |
Sorry, something went wrong.
|
Rebased to resolve the conflicts |
Sorry, something went wrong.
Merging this PR will not alter performance✅ 24 untouched benchmarks Comparing xkabylgSICKAG:master (e2775a9) with master (8e89896)1 Footnotes |
Sorry, something went wrong.
|
Looking forward for this fix since this improves the performance of our apps in production |
Sorry, something went wrong.
| solved = await run_in_threadpool(call, **solved_result.values) | ||
| if sub_dependant.name is not None: | ||
| values[sub_dependant.name] = solved | ||
| if sub_dependant_cache_key not in dependency_cache: |
There was a problem hiding this comment.
| if sub_dependant.use_cache: |
Sorry, something went wrong.
There was a problem hiding this comment.
Maybe makes sense (not sure actually, we need to think if it can break some specific use cases), but I think this is out of scope for this PR
Sorry, something went wrong.
There was a problem hiding this comment.
This PR moves the check for key in dependency_cache into block above with continue, so check in this particular line will always be true.
Also there is no need to update dependency cache if dependency does not require caching in the first place.
Sorry, something went wrong.
There was a problem hiding this comment.
If there is another call of the same dependency that would use the cache, then it wouldn't find the cached value from this call? (I'm not sure if that is what I would expect to happen, but it is the current behavior.)
Imagine the following:
async def needy_dependency(
fresh_value: Annotated[str, Depends(get_value, use_cache=False)],
cached_value: Annotated[str, Depends(get_value, use_cache=True)],
):Previously fresh_value would be resolved and then added to the cache, and cached_value would be grabbed from the cache. With the suggested change it would not add the result from fresh_value to the cache and have to resolve cached_value.
Sorry, something went wrong.
|
Rebased on latest upstream and moved the cache_miss sentinel value outside the function as suggested by @dolfinus |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Hi, we noticed very large performance gains after making this change for our app that heavily uses the dependency injection system. One of our slower endpoints went from taking ~1100ms to ~400ms.