Skip to content

Fetching current_transaction for every GraphQL field results in performance regression #831

Open
@tdg5

Description

@tdg5

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. 😿

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions