UPSTREAM FIX

magento2-40788: Concurrent GraphQL GET requests queue up on Redis session locks

Community fix magento2-40788 merged into magento/magento2 on 2026-10-07, not in a release yet; applies cleanly to 7 releases from 2.4.8 to 2.4.9.

Concurrent GraphQL GET requests queue up on Redis session locks edited

Pull request title
Unnecessary Redis Session Locking On All HTTP GET Requests - Affecting PWA Studio Concurrent GraphQL Requests
Pull request
magento/magento2#40788
Issues
#34758 human
Author
@VitaliyBoyko
Merged
2026-10-07
Fixed in
no release yet
Reported on
—
Categories
GraphQL
Components
magento/module-customer-graph-ql, magento/module-directory-graph-ql, magento/module-graph-ql, magento/module-page-cache

Labels

Area
APIs
Component
Framework/Session, GraphQL
Priority
P1
Severity
S0
Reported on (labels)
—

Issue

Title and steps come from the upstream issue and pull request.

Description

With Redis session locking enabled which is the default and recommended safe behavior for redis session configs, the concurrent requests queue up, each waiting in sequence for a redis session lock to clear before the next request is able to complete.

Steps to reproduce

Configure Magento
1. Ensure that default configuration of Redis session locking is enabled by setting disable_locking to 0:
cat app/etc/env.php | grep disable_locking
bin/magento -n setup:config:set --session-save-redis-disable-locking=0
Load the PWA storefront home page
1. Load home page (example: https://syseng-seldon.cldev.io/) which will make multiple graphql calls to the MAGENTO_BACKED_URL
2. In Chrome Developer Tools on the Network tab you can use a filter to only display requests for the specific domain that are graphql calls domain:syseng-seldon.cldev.io -media -static graphql
3. Multiple (15) /graphql requests are all sent concurrently to the configured MAGENTO_BACKEND_URL of the PWA app around the same time

Simulate the PWA storefront requests on ANY Magento site
1. Create a static html file (example: graphqlrepeat.html) in /pub/ of the magento root containing fetch
<!DOCTYPE html>
<html lang="en">
<head><title>JavaScript HTTP Requests</title></head>
<body><script>
 
var domain = "syseng-seldon.cldev.io";
 
fetch("https://" + domain + "/graphql?query=query+GetStoreConfigForCarouselEE%7BstoreConfig%7Bid+product_url_suffix+magento_wishlist_general_is_enabled+enable_multiple_wishlists+__typename%7D%7D&operationName=GetStoreConfigForCarouselEE&variables=%7B%7D", {
  "headers": {
    "accept": "*/*",
    "accept-language": "en-US,en;q=0.9",
    "authorization": "",
    "cache-control": "no-cache",
    "content-type": "application/json",
    "pragma": "no-cache",
    "sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"96\", \"Google Chrome\";v=\"96\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"macOS\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin",
    "store": "default",
    "x-magento-cache-id": "null"
  },
  "referrer": "https://" + domain + "/",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": null,
  "method": "GET",
  "mode": "cors",
  "credentials": "include"
});
 
</script></body>
</html>
2. Replace the domain variable with the domain of the magento site you're testing
3. Load the HTML page containing js that will fetch multiple graphql requests concurrently
4. You can open Chrome Developer Tools on the Network tab and where you see one of the GraphQL calls, you can right click on the request line and "Copy as fetch" to get the javascript fetch statement for making that same request in an html page js inline script.
![image](https://user-images.githubusercontent.com/50849/144677577-f911e868-d190-43a2-ab07-6f22885a89d1.png)

5. You can do this to re-create all the requests for a specific page by adding each fetch() call to the html file, or duplicate the exact same fetch() graphql request (40x) to reproduce the session locking behavior being seen here.

Simulate SOME of the expected behavior by globally disabling redis session locking on ALL requests
1. Simulate SOME of the expected behavior by globally disabling redis session locking on ALL requests
cat app/etc/env.php | grep disable_locking
bin/magento -n setup:config:set --session-save-redis-disable-locking=1

Expected result

When concurrent GraphQL GET requests are made from a visitor, requests should be able to complete in parallel to keep page load time minimal, while still allowing requests that require session locking (where important session data is being written) in order to prevent some other request from overwriting data in the session. Important data getting overwritten in a session can negatively affect critical application behavior.

![image](https://user-images.githubusercontent.com/50849/144677897-742bffe4-5836-436c-b8a8-2a0e14116cb9.png)
![image](https://user-images.githubusercontent.com/50849/144677907-b9a9f722-587d-4547-b335-9151af839f32.png)
![image](https://user-images.githubusercontent.com/50849/144677916-db252082-0fac-46f5-9740-7801db6b95be.png)

In simulating some of the correct behavior with Redis session locking disabled, I was able to load the home page and all 15 graphql requests within a window of 600 ms. There are other pages that may contain many more GraphQL requests where this can be even more important to have concurrent requests complete in parallel.
Home Page GraphQL Measurements - with Session Locking Disabled
  First started at: 393ms
  Last started at: 586ms
  Last duration was: 401ms
  Last ended at: 586ms + 401ms = 987ms
  Total window of execution: 586ms + 401ms - 393ms = 594ms
  GraphQL Requests Total: 15
  Page Finished 1020ms
![image](https://user-images.githubusercontent.com/50849/144677949-5d012ef8-0459-484c-b459-b83803e173f3.png)
![image](https://user-images.githubusercontent.com/50849/144677966-cb6c501e-2221-4adc-8fde-579b75a94731.png)

Looking at a waterfall of how the concurrent GraphQL requests complete, can reveal that multiple requests are completing at or within close to the same time.

![image](https://user-images.githubusercontent.com/50849/144677992-7afdb257-c485-4155-bc9d-6e6e4113d25f.png)

Actual result

With Redis session locking enabled which is the default and recommended safe behavior for redis session configs, the concurrent requests queue up, each waiting in sequence for a redis session lock to clear before the next request is able to complete.

![image](https://user-images.githubusercontent.com/50849/144678048-a3fe999a-2e46-4531-9f84-1758c289cc42.png)
![image](https://user-images.githubusercontent.com/50849/144678068-626b005e-0386-461b-8035-6528be2838cb.png)
![image](https://user-images.githubusercontent.com/50849/144678079-61a02424-1b16-494c-b62e-b6d944d287c5.png)
Home Page GraphQL Measurements - with Session Locking Disabled
  First started at: 313ms
  Last started at: 323ms
  Last duration was: 2800ms
  Last ended at: 323ms + 2800ms = 3123ms
  Total window of execution: 323ms + 2800ms - 313ms = 2810ms
  GraphQL Requests Total: 15
  Page Finished 3120ms
![image](https://user-images.githubusercontent.com/50849/144678100-e386180d-8e99-40fc-a6fb-0bba764abf86.png)
![image](https://user-images.githubusercontent.com/50849/144678121-86ccca87-b5fd-4e1d-9ef7-1fb98f2f1b16.png)
![image](https://user-images.githubusercontent.com/50849/144678130-548a9ddd-1995-467e-9099-f1102f1537bf.png)

This makes it look like several of the graphql requests are taking an excessive amount of time to complete, while others completed in less time, but while these requests started close to the same time, they spent a lot of time waiting for session locking to clear, resulting in requests being completed in sequence rather than in parallel.

![image](https://user-images.githubusercontent.com/50849/144678160-fd8aa39b-7199-4173-8711-2111944f4cd2.png)
![image](https://user-images.githubusercontent.com/50849/144678175-80ea40c4-2683-41d2-89b7-c1bc78963d03.png)


### Cause of Behavior
With the PWA Studio (Client Side React App) running as the frontend "storefront" of Magento and sending GraphQL calls to the backend of Magento there are different behavioral patterns in how requests are sent to and processed by the web server from how we have been seeing interactions when using the Magento "theme" as the frontend.

The PWA sends multiple concurrent AJAX calls to /graphql end points on the web server, and these requests are all processed by the Magento backend PHP application. It turns out that Magento architecture has it creating a session and locking that session regardless of the type of request or response being issued.

Some /graphql requests are able to be cached by Varnish and because cached requests in Varnish do not execute code and therefore do not interact with redis sessions. This can mask the fact that each request that hits the backend will lock the session for the request and cause requests to be completed in sequence. Many of the graphql requests are not able to be cached in Varnish at this time.

We have seen this behavior in the Magento "theme" frontend also, but there are typically only a few requests that typically happen concurrently, and we see these results show up in New Relic on transaction traces a lot for AJAX calls that are likely to be running concurrently with other requests with the same session.

![image](https://user-images.githubusercontent.com/50849/144678278-31683534-439d-405b-9e8a-490c88390242.png)

The problem is not just isolated to AJAX calls like customer/section/load on the Magento "theme" frontend, it also tends to occasionally interfere with other requests on product pages, or AJAX calls in the checkout. In the example below the unlucky visitor ended up waiting 8.8s to get an initial response for loading the page instead of what should have taken 300ms because they had some other request that was locking their session. It doesn't happen often, but it's not pleasant when it does.

The big behavioral difference is that most of the requests in the Magento "theme" frontend do NOT happen concurrently, so session locking on a couple concurrent requests doesn't affect things overall that much, and it tends to be more of an exception and most requests (even customer/section/load AJAX requests) are not delayed waiting for session locks to release on average, so this known deficiency is only causing mild problems with the Magento "theme" frontend overall, and limiting concurrent AJAX calls is a way of working around it.

![image](https://user-images.githubusercontent.com/50849/144678436-2facbb99-d74b-4b6d-a413-5475ced304aa.png)

This session locking causes lots of problems with a PWA by delaying concurrent AJAX calls causing them to wait in line and finish sequentially as the locks they are waiting on clear. This ends up making requests randomly appear like they are taking a really long time to complete while under the hood they are primarily just waiting in line for the session to be available so it can lock the session and complete it's request. This makes it very hard to identify from the client side where the problem is as it relates to concurrent requests for the same session and running the requests independently results in a very fast response.

While it's possible to disable session locking, this can cause serious issues with requests that make changes to session data and overwrite each other, and that can result in serious problems on checkout where payments are captured but orders fail to be saved in Magento resulting sometimes in a customer re-submitting the order and getting charged multiple times if they are able to get the order to complete successfully.

The [library used by Magento (v2.3.4)](https://github.com/magento/magento2/blob/2.4.3-p1/composer.lock#L321) is the [latest available (colinmollenhour/php-redis-session-abstract v1.4.4)](https://github.com/colinmollenhour/php-redis-session-abstract/releases) and it [does support the ability to process read only requests without locking a session](https://github.com/colinmollenhour/php-redis-session-abstract/blob/v1.4.4/src/Cm/RedisSession/Handler.php#L434) when the global config is set to utilize session locking... it just so happens that [Magento does not utilize this functionality](https://github.com/magento/magento2/blob/2.4.3-p1/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php#L63) and [opens all session connections without specifying](https://github.com/colinmollenhour/php-redis-session-abstract/blob/v1.4.4/src/Cm/RedisSession/Handler.php#L263) if it needs to write to the session or not, thus defaulting to write mode and session locking.

1. https://github.com/magento/magento2/blob/2.4.3-p1/composer.lock#L321
2. https://github.com/colinmollenhour/php-redis-session-abstract/releases
3. https://github.com/colinmollenhour/php-redis-session-abstract/blob/v1.4.4/src/Cm/RedisSession/Handler.php#L434
4. https://github.com/magento/magento2/blob/2.4.3-p1/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php#L63
5. https://github.com/colinmollenhour/php-redis-session-abstract/blob/v1.4.4/src/Cm/RedisSession/Handler.php#L263

### Possible Solution
Requests that come into Magento as GET requests are typically expected to return generic publicly identical response that can be cached by Varnish, while POST requests are explicitly not allowed to be cached by Varnish as they are expected to contain visitor/session/customer private data in the responses. Important write operations should typically happen in POST requests, and those types of requests would be expected to need and utilize session locking, while GET requests would generally be for returning generic data that is not visitor/session/customer specific and the same response would be returned to all requests and not involve any kind of write to the session. If any session write activity were to occur on GET requests, it would likely be to update the timestamp of the most recent request to indicate the session is still active and has not expired yet (this is an assumption that should be verified).

---

- [ ] Severity: S2 _- Affects non-critical data or functionality and forces users to employ a workaround._

Taken from the upstream issue.

Code match per tag

Each tag was checked with git apply --check against that tag's files. A clean match means the change applies; it is not a test result. Tags that already contain the fix are marked.

LineCode match per tagTests
2.4.6
2.4.6 conflict 2.4.6-p1 conflict 2.4.6-p2 conflict 2.4.6-p3 conflict 2.4.6-p4 conflict 2.4.6-p5 conflict 2.4.6-p6 conflict 2.4.6-p7 conflict 2.4.6-p8 conflict 2.4.6-p9 conflict 2.4.6-p10 conflict 2.4.6-p11 conflict 2.4.6-p12 conflict 2.4.6-p13 conflict 2.4.6-p14 conflict 2.4.6-p15 conflict
2.4.6: no test data 2.4.6-p1: no test data 2.4.6-p2: no test data 2.4.6-p3: no test data 2.4.6-p4: no test data 2.4.6-p5: no test data 2.4.6-p6: no test data 2.4.6-p7: no test data 2.4.6-p8: no test data 2.4.6-p9: no test data 2.4.6-p10: no test data 2.4.6-p11: no test data 2.4.6-p12: no test data 2.4.6-p13: no test data 2.4.6-p14: no test data 2.4.6-p15: no test data
2.4.7
2.4.7 conflict 2.4.7-p1 conflict 2.4.7-p2 conflict 2.4.7-p3 conflict 2.4.7-p4 conflict 2.4.7-p5 conflict 2.4.7-p6 conflict 2.4.7-p7 conflict 2.4.7-p8 conflict 2.4.7-p9 conflict 2.4.7-p10 conflict
2.4.7: no test data 2.4.7-p1: no test data 2.4.7-p2: no test data 2.4.7-p3: no test data 2.4.7-p4: no test data 2.4.7-p5: no test data 2.4.7-p6: no test data 2.4.7-p7: no test data 2.4.7-p8: no test data 2.4.7-p9: no test data 2.4.7-p10: no test data
2.4.8
2.4.8 clean 2.4.8-p1 clean 2.4.8-p2 clean 2.4.8-p3 clean 2.4.8-p4 clean 2.4.8-p5 clean
2.4.8: no test data 2.4.8-p1: no test data 2.4.8-p2: no test data 2.4.8-p3: no test data 2.4.8-p4: no test data 2.4.8-p5: test files do not apply to this releaseunit: could not run before, could not run after · integration: could not run before, could not run after · api-functional: could not run before, could not run after
2.4.9
2.4.9 clean
2.4.9: fails before, passes afterunit: could not run before, passes after · integration: passes before and after · api-functional: fails before, passes after

Triage

Model @cf/cloudflare/clef. Probability this is a bug fix: 96.6%. Probability it is security relevant: 93.4%.

Show the model's answers and probabilities
QuestionAnswerProbabilitiesConfidence
Change kindbugfixbugfix 93.3%, refactor 2.5%, cleanup 1.7%, feature 1.1%, tests_only 0.7%, dependency 0.4%, docs_only 0.3%85.1%
Areagraphql_apigraphql_api 77.2%, framework 14.2%, customer 3.8%56.4%
Reported versionunspecifiedunspecified 13.0%, 2.4.3-p1 7.8%, 2.4.3 3.5%2.1%
Scope1.35 of 22 48.5%, 1 37.9%, 0 13.6%9.6%
Risk1.03 of 21 38.0%, 2 32.5%, 0 29.4%0.6%
Worth backporting1.38 of 22 51.0%, 1 35.8%, 0 13.2%10.9%

Download

For cweagans/composer-patches, choose a version below and download the bundle. Copy its magento2-40788/ folder into patches/composer/, merge composer.patches.json into composer.json, then run composer install. Test files are always removed; paths are relative to each package root, using the default -p1 level.

Packages (4): magento/module-customer-graph-ql, magento/module-directory-graph-ql, magento/module-graph-ql, magento/module-page-cache
Bundle README (what the ZIP ships)
# magento2-40788

Community fix merged upstream into magento/magento2, adapted by magento.watch.
This is not a patch published by Adobe.

Pull request: https://github.com/magento/magento2/pull/40788
Issue: https://github.com/magento/magento2/issues/34758
Author: @VitaliyBoyko
Source commit: 7f71205f751c71ac1ee164dfa288d3f1241e8835
Modifications: test files and documentation removed, paths rewritten relative to each Composer package.
Licence: OSL-3.0 / AFL-3.0, as the original Magento Open Source code.
Maintainer: Łukasz Bajsarowicz (@lbajsarowicz)

Licence: Magento Open Source code under OSL-3.0 and AFL-3.0. The bundle carries the original author, source commit and the list of modifications.

Sources

Łukasz Bajsarowicz
Built by

Łukasz Bajsarowicz, e-commerce architect

Magento and Adobe Commerce architecture, upgrades, performance and audits for merchants and agencies since 2015; magento.watch is the tooling I use on those projects.

Open source, maintained on weekends.