Fetching current_transaction for every GraphQL field results in performance regression
tdg5 opened this issue · 4 comments
I believe I'm hitting the same issue as #605, however, I don't think graphene
is part of the problem. I think the real problem is newrelic
+graphql-core
.
I think that the reason that NewRelic+GraphQL leads to a performance regression is because the NR agent tries to retrieve the current_transaction
for every single field that needs to be resolved. It's not a problem if you have a query that returns a single, shallow object, but any kind of list query that returns complex and/or nested objects is likely to be negatively impacted by the many, many requests to current_transaction
. I think it is incorrect for newrelic
to assume it can call current_transaction
for every field that needs to be resolved.
DISCLAIMER: I'm pretty sure current_transaction
is part of the problem, but it may not be the whole problem. If I override current_transaction
to always return None
, I still see a regression, but a less substantial regression (1.5x vs 2-3x+). That said, a bunch of logic depends on current_transaction
not being None
, so there's a chance that other code that is being circumvented is the true culprit, but based on the output from yappi
, I'm not sure what else to blame.
Here's some example info from yappi
that suggests current_transaction
:
GraphQL Execute
timing when running without New Relic:
full_name: /usr/local/lib/python3.10/site-packages/graphql/execution/execute.py:965 execute
ncall: 999/999
ttot: 28.11397
Time spent in current_transaction
when running with New Relic:
full_name: /usr/local/lib/python3.10/site-packages/newrelic/api/transaction.py:1736 current_transaction
ncall: 3997998/3997998
ttot: 20.82567
Those stats come from different test runs, but those numbers support the notion that calls to current_transaction
could be responsible for almost doubling the time it takes to execute the query.
According to #605, the regression occurred between these two releases, which the referenced code is part of.
Description
A GraphQL request that takes 1 unit of time without the NewRelic agent running takes 2-3 units of time with the NewRelic agent running. The severity of the performance regression is likely API specific and seems likely to depend on the total number of fields that need to be resolved.
Expected Behavior
Using NewRelic agent in combination with GraphQL should not cause a significant performance regression.
Troubleshooting or NR Diag results
I started with a pretty thick stack and was able to reduce the problem to resulting from the combination of graphql-core
and newrelic
python packages.
I made a fork of the newrelic-python-agent
repo and was able to specifically trace the problem to being related to the newrelic hook that wraps graphql with the instrument_graphql_execute
wrapper. I made a draft PR that just shows what I disabled while still reproducing the performance regression: https://github.com/newrelic/newrelic-python-agent/pull/830/files. YMMV, but disabling some of the other hooks/wrappers can help reduce noise to make it easier to figure out where the problem is coming from.
Steps to Reproduce
I made this repo to reproduce the problem with a strawman GraphQL query. The README.md offers some steps for reproducing the problem but there's also a Github workflow that reproduces the problem here: https://github.com/tdg5/newrelic-python-agent-graphql-issue/blob/main/.github/workflows/reproduction.yml
I'm not sure if it's visible to all, but a run of that workflow that demonstrates the significant difference in performance can be found here: https://github.com/tdg5/newrelic-python-agent-graphql-issue/actions/runs/5256745026/jobs/9498499333#step:3:1012. The relevant output is:
***********************************************
* BGN: RUN WITHOUT NEW RELIC AGENT *
***********************************************
71.92
^^^^^^ WITHOUT NEW RELIC AGENT WALL TIME ^^^^^^
***********************************************
* END: RUN WITHOUT NEW RELIC AGENT *
***********************************************
*****************************************
* BGN: RUN WITH NEW RELIC AGENT *
*****************************************
153.98
^^^^^ WITH NEW RELIC AGENT WALL TIME ^^^^
*****************************************
* END: RUN WITH NEW RELIC AGENT *
*****************************************
Your Environment
I've been using the latest release of newrelic and I've only tried ubuntu environments, but it's not obvious to me that this would be an OS specific issue.
Additional context
I've attached the output from a yappi
functional analysis both with and without new relic to try to identify how the app is spending its time differently. The main culprit that sticks out is the calls to current_transaction
, but I'm inclined to believe there is another source of performance regression because I still encounter a non-trivial performance regression (1.5x vs 2-3x) if I override current_transaction
to always return None
, but perhaps that's just the cost of using New Relic.
yappi.get_func_stats()
WITHOUT new relic: no-nr-func-stats.txt
yappi.get_func_stats()
WITH new relic: nr-func-stats.txt
Please let me know if there's anything I can do to help you reproduce or correct this problem. I have GraphQL APIs in prod that I am unable to instrument because of the enormous performance regression that instrumentation introduces. 😿
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Can y'all at least add some configuration to allow me to turn this behavior off? As it stands I can't use NR Agent with any of my GQL apps.
@tdg5 you could turn off the middleware field resolvers via:
[import-hook:graphql.execution.middleware]
enabled = false
We are going to bring this performance issue up with the rest of the agents and decide where to go from there.
Thanks @hmstepanek! I swear I looked around for options to disable the hook, but must've missed something.
As far as this issue is concerned, I think there's an argument for being able to close it. Since I opened this issue I've had to come to terms with many existing graphql implementations having underwhelming performance.
The New Relic hook doesn't help the situation, but isn't completely to blame.
Some other graphql issues for reference:
- graphql-python/graphene#268
- graphql-python/graphql-core#190
- graphql-python/graphql-core#189
- graphql/graphql-js#723 (comment) (this is for the javascript implementation, but since it is the reference implementation, it is kind of to blame)