Skip to content

Limit table detail requests in controller UI#18615

Open
wolfkill wants to merge 1 commit into
apache:masterfrom
wolfkill:fix/table-size-request-limit
Open

Limit table detail requests in controller UI#18615
wolfkill wants to merge 1 commit into
apache:masterfrom
wolfkill:fix/table-size-request-limit

Conversation

@wolfkill
Copy link
Copy Markdown
Contributor

Summary

  • Add a small concurrency-limited task runner for controller UI requests.
  • Use it when loading table detail rows so large clusters do not fire all table size/status requests at once.
  • Keep the existing incremental table row updates and loading placeholders unchanged.

Root cause

The table listing loads all tables first, then starts detail requests for every table immediately. On clusters with hundreds of tables, that can overwhelm browsers with many concurrent /tables/{table}/size and segment status requests, matching the behavior reported in #11370.

Addresses #11370.

Testing

  • rm -rf /tmp/pinot-ui-request-limiter-test && ./node_modules/.bin/tsc app/utils/requestLimiter.ts app/utils/requestLimiter.test.ts --outDir /tmp/pinot-ui-request-limiter-test --module commonjs --target es2020 --types node --skipLibCheck --esModuleInterop && node --test /tmp/pinot-ui-request-limiter-test/requestLimiter.test.js
  • npm run build
  • git diff --check

Notes

  • ./node_modules/.bin/eslint app/utils/requestLimiter.ts app/utils/requestLimiter.test.ts --quiet could not run cleanly because the current ESLint config references missing rules (react/jsx-key, import/no-extraneous-dependencies, react/jsx-filename-extension, import/extensions).

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.36%. Comparing base (6bff229) to head (e6da68a).

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #18615      +/-   ##
============================================
- Coverage     64.39%   64.36%   -0.04%     
  Complexity     1137     1137              
============================================
  Files          3336     3336              
  Lines        206096   206096              
  Branches      32145    32145              
============================================
- Hits         132721   132656      -65     
- Misses        62714    62778      +64     
- Partials      10661    10662       +1     
Flag Coverage Δ
custom-integration1 100.00% <ø> (ø)
integration 100.00% <ø> (ø)
integration1 100.00% <ø> (ø)
integration2 0.00% <ø> (ø)
java-21 64.36% <ø> (-0.04%) ⬇️
temurin 64.36% <ø> (-0.04%) ⬇️
unittests 64.36% <ø> (-0.04%) ⬇️
unittests1 56.76% <ø> (-0.03%) ⬇️
unittests2 36.93% <ø> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@yashmayya yashmayya left a comment

Choose a reason for hiding this comment

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

Nice idea throttling these — on big clusters the all-at-once fan-out is a real problem. One concern on the error path though (inline).

const worker = async () => {
while (nextIndex < tasks.length) {
const currentIndex = nextIndex++;
results[currentIndex] = await tasks[currentIndex]();
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.

If a task rejects, this await throws and the worker's while-loop exits — so that worker stops pulling tasks. After a handful of failures the whole pool is dead and the remaining tables never get their requests fired (rows stuck on 'Loading'). The old per-table forEach isolated failures. Maybe wrap the task call in try/catch here so one bad request doesn't starve the rest?

}
);

await Promise.all([segmentDetails, tableSizes]);
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.

getTableSizes/getSegmentCountAndStatus reject when there's no response (e.g. a timeout — result.data is undefined), so this Promise.all rejects and bubbles all the way up to runWithConcurrencyLimit uncaught. That's exactly the large-cluster case this PR is meant to help. Probably want a .catch per request so a failing table just keeps its placeholder instead of taking down the rest.


const nextTick = () => new Promise((resolve) => setTimeout(resolve, 0));

test('runWithConcurrencyLimit caps in-flight tasks', async () => {
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.

This only covers the all-resolve path — since a rejection kills the worker, a test where one task rejects is the valuable one to add. Also worth flagging: npm test is still the no-op stub, so this spec doesn't actually run in CI yet.

@yashmayya yashmayya added ui UI related issue enhancement Improvement to existing functionality labels Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to existing functionality ui UI related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants