Skip to content

fix: batch bulk subscriber creation to prevent network error (#158)#197

Merged
Alonza0314 merged 12 commits into
free5gc:mainfrom
ALIIQBAL786:fix/bulk-subscriber-network-error
May 14, 2026
Merged

fix: batch bulk subscriber creation to prevent network error (#158)#197
Alonza0314 merged 12 commits into
free5gc:mainfrom
ALIIQBAL786:fix/bulk-subscriber-network-error

Conversation

@ALIIQBAL786

Copy link
Copy Markdown
Contributor

Problem

When creating multiple subscribers with a large `userNumber`, the frontend crashes with a Network Error.

Root cause: The original `onCreate` loop fired all N `axios.post()` calls simultaneously without awaiting. For large counts (e.g. 10,000), this overwhelms the browser's networking stack. Additionally, `navigation("/subscriber")` was called inside every `.then()`, so the component unmounted on the first success while thousands of in-flight requests continued updating dead state.

Fix

  • Process requests in controlled batches of 10 using `Promise.allSettled`
  • Navigate exactly once after all batches complete
  • Show a live progress bar with count (e.g. "Creating 450 / 1000")
  • Show an error summary when any requests fail (instead of per-request alerts)
  • Disable the CREATE button while the operation is in progress

Tested

  • 5 subscribers — completes and navigates correctly
  • 1000 subscribers — progress bar updates, navigates on completion
  • With simulated 20% failure rate — error summary displays correctly
  • Double-click CREATE — button disabled, no duplicate requests

Fixes #158

The original onCreate loop fired all N axios.post() calls simultaneously
without awaiting, overwhelming the browser networking stack and causing
a Network Error for large subscriber counts (e.g. 10,000).

Additionally, navigation("/subscriber") was called inside every .then(),
so the component unmounted on the first success while remaining requests
continued updating dead state.

Fix:
- Process requests in controlled batches of 10 using Promise.allSettled
- Navigate exactly once after all batches complete
- Show a progress bar with live count during creation
- Show an error summary (instead of per-request alerts) when any fail
- Disable the CREATE button while operation is in progress

Fixes free5gc#158
@Alonza0314

Copy link
Copy Markdown
Member

Hi @ALIIQBAL786,

This is a solid improvement to the creation procedure—nice work on that.

I do have a question regarding how you managed to create such a large number of UEs. Did you use the frontend to add them, or did you rely on backend APIs or scripts?

As for the issue itself, my understanding is that the reporter is pointing out that the subscriber page panics when handling around 10K UEs, rather than focusing on the creation procedure.

Happy to discuss further!

Cheers,
Alonza

ALIIQBAL786 and others added 8 commits April 29, 2026 15:30
The subscriber list page panicked when loaded with a large number of UEs
(e.g. 10,000) due to three issues:

1. filteredData.map() rendered ALL rows as DOM nodes simultaneously.
   With 10K subscribers this created 10K table rows at once, overwhelming
   the browser's rendering engine and causing a panic.
   Fix: slice filteredData to the current page before rendering.

2. count() always returned 0, so TablePagination showed incorrect controls
   and navigating pages had no effect.
   Fix: return filteredData.length as the true record count.

3. onDeleteSelected joined all selected items into a single confirm string.
   With 10K items selected this produced a massive string that crashed
   the browser's native confirm dialog.
   Fix: cap the preview to 5 items with a "...and N more" suffix.

4. Search did not reset page to 0, causing a blank view when the filtered
   result set was smaller than the current page offset.
   Fix: reset page to 0 on every search input change.

Fixes free5gc#158
Fixes all 6 points raised in PR review:

1. Virtualization (react-window v2 `List`)
   Replace slice-based pagination with react-window FixedSizeList so only
   visible rows (~10) exist in the DOM regardless of dataset size. Pagination
   was a stopgap — bumping page size or adding "show all" would re-introduce
   the DOM explosion. Virtual scroll bounds DOM nodes permanently.

2. useMemo for filteredData
   Wrap the filter in useMemo([data, searchTerm]) so the full-array scan only
   runs when data or search term changes, not on every checkbox tick or button
   hover. Previously every selection state update re-ran the filter.

3. Threshold-based bulk delete confirmation
   Single confirm for < 100 items (count + 5-item preview).
   Double confirm for >= 100 items: first warning, then final irreversible
   step. Caps the preview string to avoid crashing the native confirm dialog
   with 10K+ selected items joined into one string.

4. Consistent state resets
   Clear selection on search change to avoid operating on subscribers no
   longer visible in filtered results. Virtual scroll eliminates the
   "blank page" edge case that required page-index resets.

5. Selection keyed by stable ueId+plmnID
   Selection is stored globally by identifier, not by row index or current
   visible slice. Selections survive scroll, search, and sort changes.
   Bulk delete operates on the full selected set, not just the visible page.

6. Stress test coverage
   Mock server extended with ?seed=N to generate up to 100K synthetic
   subscribers. Tested: 50K dataset, rapid search typing, select all →
   delete, repeated filter changes.
…eate

setLoading(true) was being called in onCreate, causing the component to
return <div>Loading...</div> early and hide the form and progress bar
entirely. Users saw a blank screen with no feedback until all requests
completed (or indefinitely for large counts like 50,000).

The loading state is only meant for edit mode (fetching an existing
subscriber's data). Batch creation progress is already tracked separately
via createProgress state, which drives the progress bar and button label.
Replace the single-payload DELETE request with batched requests
(BULK_DELETE_BATCH_SIZE=100 items each) so large selections such as
50 K subscribers don't exceed implicit browser/server payload limits
that cause the request to be dropped without surfacing an error.

Changes:
- onDeleteSelected is now async and iterates in batches via await
- deleteProgress state drives a LinearProgress bar and disables the
  button while deletion is in progress (mirrors onCreate pattern)
- Console logs added at each batch for browser-side diagnostics
- ariaAttributes type widened to Record<string, string | number> to
  match react-window v2's aria prop signature
- Unused MUI Table/TableCell/TableHead/TableRow imports removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Raises BATCH_SIZE from 10 to 50 so 5× more requests run concurrently
per batch, reducing total round trips for large subscriber counts
(e.g. 50 K: 5 000 batches → 1 000 batches).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ALIIQBAL786

Copy link
Copy Markdown
Contributor Author

Tested against the real free5GC Docker stack. Summary of all changes:

Bulk create fix (Issue #158)
The original loop fired all requests simultaneously without awaiting, crashing the browser network stack. Fixed by processing
requests in batches of 300 using Promise.allSettled, navigating exactly once after all batches complete, and showing a progress bar
with per-subscriber error collection.

Subscriber list virtualization
react-window inside MUI TableBody rendered either 0 rows or all rows at once. Fixed by moving the virtual list into a plain Box and
using CSS Grid for column alignment. Tested with 50,000 subscribers — only visible rows render and scrolling is smooth.

Reviewer feedback
Added isLoading state to prevent empty-state flash on tab switch/refresh. Moved VirtualRow outside the component for stable
reference. Used useMemo for filtered data and useCallback for row handlers. Selection keyed by ueId+plmnID. Added
double-confirmation dialog for bulk deletes ≥ 100.

Progress bar fix
setLoading(true) in onCreate was triggering the edit-mode loading guard, hiding the form and progress bar. Removed those calls —
loading is only for prefilling the edit form.

Bulk delete fix
Large selections were silently dropped (no error shown, rows unchanged). Fixed by batching DELETE requests at 500 items each with a
progress bar, matching the create pattern.

ALIIQBAL786 and others added 2 commits May 5, 2026 12:57
CI hardened mode requires the lockfile to already contain all
installed packages. yarn.lock was missing the react-window and
@types/react-window entries added by this PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The branch had yarn.lock in yarn v1 classic format while main uses yarn v4
(berry) format with __metadata and checksums. Regenerate using corepack
yarn@4.1.0 so the lockfile format matches main and CI hardened-mode checks
pass. Adds react-window@2.2.7 and @types/react-window@1.8.8 entries that
were missing from main's lockfile.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@solar224

solar224 commented May 9, 2026

Copy link
Copy Markdown
Contributor

@ALIIQBAL786 One thing I'd like to flag — the current approach replaces MUI's semantic <Table> / <TableHead> / <TableBody> / <TableRow> / <TableCell> components with raw <Box> elements styled via inline sx={{}} props and hardcoded color values (e.g. #fafafa, rgba(25,118,210,0.08)). This diverges from the styling patterns used across the rest of the webconsole (such as AnalysisList, ProfileList, TenantList, etc.), where MUI table components handle theming automatically with no hardcoded colors.

To keep things consistent and theme-friendly, could you consider one of the following?

  • Use MUI theme tokens — replace hardcoded values with references like "divider", "action.hover", "background.default", etc., so the styles adapt automatically if the theme changes.
  • Extract to a CSS file — create a dedicated SubscriberList.css (similar to how the project uses App.css / index.css) to keep the styling separate from the component logic.

Also, a minor housekeeping note — there are quite a few comments that read more like commit messages or PR descriptions rather than long-term code documentation. Could you clean these up and keep only comments that explain non-obvious decisions?

Either approach would help keep the codebase consistent and easier to maintain going forward. Let me know what you think!

Replace all hardcoded color literals in SubscriberList with MUI sx theme
token strings so styles adapt automatically to theme changes:
- rgba(224,224,224,1) → "divider"
- #fafafa             → "background.default"
- rgba(25,118,210,…)  → "action.selected" / "action.focus"
- rgba(0,0,0,0.04)    → "action.hover"

Remove comments that described what the code does (readable from the code)
or duplicated PR/commit message content. Retain only the react-window pixel
height note, which explains a non-obvious technical constraint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ALIIQBAL786

ALIIQBAL786 commented May 10, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review!

You're right about the hardcoded colors, I've replaced all of them with MUI theme tokens ("divider", "action.selected", "background.default", etc.) in the latest commit, so they'll adapt automatically with the theme. Comments have been cleaned up too, kept only the one explaining the react-window height constraint since that's genuinely non-obvious.

On the structure, that one's unfortunately a hard constraint from react-window. It renders each row into an absolutely-positioned

, so putting elements inside it produces invalid HTML and breaks the layout. It also prevents react-window from measuring the container height correctly, which causes it to render 0 rows. CSS Grid with a shared column template is the standard workaround for this, it keeps the visual alignment consistent with what would give you, just without the semantic wrapper.

Let me know if anything else needs adjusting!

@Alonza0314 Alonza0314 merged commit 592ad63 into free5gc:main May 14, 2026
4 checks passed
@ALIIQBAL786

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review @solar224, really appreciate you going through it carefully!

Since the PR is already merged, I'll pick these up in a follow-up. The plan:

  • Add a proper guard for userNumber before the loop so undefined surfaces as a clear error rather than silently creating 0 subscribers
  • Build payloads lazily inside the batch loop instead of pre-allocating the full array upfront
  • Narrow the useCallback dependency from [props] to [props.setRefresh]
  • Wrap rowProps in useMemo
  • Import the actual ariaAttributes type from react-window v2 instead of the manual declaration
  • Drop the double confirmation for bulk delete and collect per-batch errors the same way the create flow does
  • Move BATCH_SIZE, ROW_HEIGHT, MAX_LIST_HEIGHT, and the delete thresholds into config.tsx so they're easy to tune
  • Remove @types/react-window and let react-window v2's built-in types take over

Will open a follow-up PR once these are ready!

@solar224

Copy link
Copy Markdown
Contributor

@ALIIQBAL786 Thanks for the quick reply and for outlining this detailed follow-up plan!

Just wanted to give you a quick heads-up that I've actually already started working on these subsequent modifications on my end. So, no need to spend your time opening a new PR for this—I'll take it from here.

Really appreciate your contribution and all your work on the initial PR!

BR,

@ALIIQBAL786

Copy link
Copy Markdown
Contributor Author

Thanks for the heads-up @solar224, and happy to hand it off! Looking forward to seeing your improvements. Appreciate the kind words too!

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.

[Bugs] Create Multiple Subscribers will cause frontend crash (Network Error)

3 participants