Skip to content

fix: dedupe concurrent getNodeAjaxOptions requests (#10155)#10159

Open
hiteshjambhale wants to merge 1 commit into
pgadmin-org:masterfrom
hiteshjambhale:fix/node-ajax-inflight-dedup
Open

fix: dedupe concurrent getNodeAjaxOptions requests (#10155)#10159
hiteshjambhale wants to merge 1 commit into
pgadmin-org:masterfrom
hiteshjambhale:fix/node-ajax-inflight-dedup

Conversation

@hiteshjambhale

@hiteshjambhale hiteshjambhale commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #10155.

getNodeAjaxOptions() in web/pgadmin/browser/static/js/node_ajax.js — the shared helper behind every AJAX-backed dropdown — cached responses only after they resolved. So when many components requested the same URL before the first response landed (e.g. every column row's Data Type dropdown mounting at once on a wide table's Columns tab), each one saw an empty cache and fired its own identical GET. A 150-column table produced ~150 concurrent duplicate get_types requests.

Fix

Added in-flight request sharing:

  • A module-level Map tracks requests currently in progress, keyed by the resolved full URL + query params.
  • On a cache miss, a caller reuses an existing in-flight request if one matches; otherwise it starts one and stores it.
  • The first request to resolve populates the cache exactly as before.
  • The map entry is removed on settle (.finally()), success or failure, so it doesn't leak.

Result: one shared get_types request for all concurrent callers instead of one per row. Cached behavior and response-shape handling are unchanged.

Notes

Could not run the JS linter locally — the repo's .eslintrc.js (legacy format) is incompatible with the installed ESLint v9 (flat-config only), a pre-existing toolchain issue unrelated to this change. The file was verified to parse as valid ES module syntax.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Prevented duplicate concurrent data requests when multiple parts of the application request the same information.
    • Improved request handling by sharing an in-progress request and properly clearing it after completion or failure.

getNodeAjaxOptions cached responses only after they resolved, so concurrent
callers for the same URL (e.g. every column row's Data Type dropdown mounting
at once) all saw an empty cache and each fired its own identical HTTP GET.

Track in-flight requests in a module-level Map keyed by the resolved URL and
query params. Concurrent callers now await the same underlying request; the
first to resolve populates the cache as before, and the entry is cleaned up on
settle so the map does not leak.

Fixes pgadmin-org#10155

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Changes

AJAX request deduplication

Layer / File(s) Summary
In-flight request sharing
web/pgadmin/browser/static/js/node_ajax.js
Tracks pending GET requests by resolved URL and query parameters, reuses matching Promises, transforms shared responses, and removes entries after completion.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant getNodeAjaxOptions
  participant InflightMap
  participant api.get
  Caller->>getNodeAjaxOptions: Request node options
  getNodeAjaxOptions->>InflightMap: Check URL and params key
  alt No pending request
    getNodeAjaxOptions->>api.get: Fetch fullUrl with params
    api.get-->>getNodeAjaxOptions: Return response
    getNodeAjaxOptions->>InflightMap: Remove completed request
  else Pending request exists
    InflightMap-->>getNodeAjaxOptions: Return shared Promise
  end
  getNodeAjaxOptions-->>Caller: Resolve transformed options
Loading

Suggested reviewers: asheshv

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: deduplicating concurrent getNodeAjaxOptions requests.
Linked Issues check ✅ Passed The implementation matches #10155 by sharing in-flight requests, keying them by URL+params, and cleaning up after completion.
Out of Scope Changes check ✅ Passed The PR stays scoped to node_ajax.js and only adds the requested in-flight request deduplication.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/pgadmin/browser/static/js/node_ajax.js`:
- Around line 137-145: Move the cacheNode.cache invocation out of the shared
api.get promise callback and into the per-caller request.then block before
transform(resData). Use each caller’s own otherParams, cacheNode, nodeObj, url,
treeNodeInfo, and cacheLevel values so concurrent requests apply independent
cache logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 27d542f2-1ebc-4cbb-881c-b8a2f93d5bc4

📥 Commits

Reviewing files that changed from the base of the PR and between b15c745 and eae3f97.

📒 Files selected for processing (1)
  • web/pgadmin/browser/static/js/node_ajax.js

Comment on lines +137 to +145
otherParams.useCache && cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel, resData);
return resData;
}).finally(()=>{
_inflightAjaxRequests.delete(inflightKey);
});
_inflightAjaxRequests.set(inflightKey, request);
}
request.then((resData)=>{
resolve(transform(resData));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Move cache population outside the shared promise to avoid closure capture of the first caller's arguments.

The shared api.get(...).then(...) block captures the closure variables (otherParams, cacheNode, nodeObj, url, treeNodeInfo, cacheLevel) of the first caller. If concurrent callers request the same URL but differ in parameters (e.g., different nodeObj.type or useCache settings), the cache will only be populated for the first caller's key.

Moving the cacheNode.cache call into the individual request.then block ensures that every concurrent caller correctly executes its own caching logic and populates its respective cache entry before applying its transformation.

🐛 Proposed fix
-            otherParams.useCache && cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel, resData);
             return resData;
           }).finally(()=>{
             _inflightAjaxRequests.delete(inflightKey);
           });
           _inflightAjaxRequests.set(inflightKey, request);
         }
         request.then((resData)=>{
+          if (otherParams.useCache) {
+            cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel, resData);
+          }
           resolve(transform(resData));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
otherParams.useCache && cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel, resData);
return resData;
}).finally(()=>{
_inflightAjaxRequests.delete(inflightKey);
});
_inflightAjaxRequests.set(inflightKey, request);
}
request.then((resData)=>{
resolve(transform(resData));
return resData;
}).finally(()=>{
_inflightAjaxRequests.delete(inflightKey);
});
_inflightAjaxRequests.set(inflightKey, request);
}
request.then((resData)=>{
if (otherParams.useCache) {
cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel, resData);
}
resolve(transform(resData));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/pgadmin/browser/static/js/node_ajax.js` around lines 137 - 145, Move the
cacheNode.cache invocation out of the shared api.get promise callback and into
the per-caller request.then block before transform(resData). Use each caller’s
own otherParams, cacheNode, nodeObj, url, treeNodeInfo, and cacheLevel values so
concurrent requests apply independent cache logic.

// Share a single in-flight request among all concurrent callers asking
// for the same URL + params, so we don't fire duplicate GETs before the
// first response lands and populates the cache.
let inflightKey = fullUrl + '#' + JSON.stringify(otherParams.urlParams || {});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some observations -

  1. Not the best way to create a unique key
{
   a:1,
   b:2
}

vs

{
   b:2,
   a:1
}

And a nested object will make it worse.

  1. A better place to put such logic is inside the Axios wrapper, so that it will be used by the entire app

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate concurrent get_types (and other dropdown) requests when many rows mount at once

2 participants