Skip to content

PE-9103: Parallelize upload preparation pipeline#2161

Open
vilenarios wants to merge 4 commits into
devfrom
PE-9103-upload-prep-optimization
Open

PE-9103: Parallelize upload preparation pipeline#2161
vilenarios wants to merge 4 commits into
devfrom
PE-9103-upload-prep-optimization

Conversation

@vilenarios

@vilenarios vilenarios commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

Optimizes the upload preparation phase by parallelizing sequential operations that were previously blocking on each other unnecessarily.

Changes:

  • Parallel conflict detection: All file conflict DB queries run concurrently via Future.wait instead of one sequential query per file. For 100 files: 100 sequential queries → 1 parallel batch.
  • Pre-computed file lengths: All file lengths read in parallel at the start of startUploadPreparation() and cached in _fileLengthCache. Eliminates 2-3x redundant sequential reads per file across warning check, plan creation, and total size calculation.
  • Parallel cost estimation: Turbo balance + AR bundle/file sizes fetched concurrently. AR and Turbo cost calculations run in parallel after sizes are computed.
  • Parallel upload plan creation: AR and Turbo upload plans created simultaneously via Future.wait instead of sequentially.
  • Remove redundant operations: Duplicate wallet mismatch check removed, ANT records not re-fetched if already populated, 100ms artificial UI delay reduced to Duration.zero.

Impact

For multi-file uploads (50+ files), preparation time reduced from sequential O(n) DB queries to parallel O(1). Network-bound cost estimation parallelized (saves ~1-2s).

Test plan

  • Upload single file — verify preparation completes normally
  • Upload 10+ files — verify faster preparation
  • Upload folder with nested files — verify conflict detection works
  • Upload with file name conflicts — verify conflict modal appears correctly
  • Upload to private drive — verify encryption/key derivation works
  • Turbo vs AR cost display — verify both costs shown correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Performance Improvements
    • Upload preparation now runs key planning, size, conflict, and cost calculations concurrently to reduce waiting time.
    • File length values are cached during setup so later warning, conflict, and plan steps reuse them instead of re-reading.
    • Upload conflict detection now aggregates results faster for all selected files.
    • Upload cost/plan estimation waits on AR and Turbo work in parallel, improving how quickly the upload screen updates.
    • Reduces redundant network calls when mounting upload parameters.

…ation, plans PE-9103

- parallelize conflict detection: check all files concurrently via
  Future.wait instead of sequential DB query per file
- pre-compute all file lengths in parallel at start of preparation,
  cache in _fileLengthCache to avoid redundant sequential reads
- parallelize cost estimation: turbo balance + AR sizes fetched in
  parallel, then AR and Turbo cost calculations run concurrently
- parallelize upload plan creation: AR and Turbo plans created
  simultaneously via Future.wait instead of sequentially
- remove duplicate wallet mismatch check (called twice in
  prepareUploadPlanAndCostEstimates)
- skip ANT records re-fetch if already populated from initial
  fire-and-forget fetch
- reduce 100ms artificial UI delay to Duration.zero (just yields
  to event loop for state update)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@vilenarios, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d6707a3-70e6-44d5-a35a-5d5612b0a0bb

📥 Commits

Reviewing files that changed from the base of the PR and between 4d28c18 and 73d8619.

📒 Files selected for processing (2)
  • lib/blocs/upload/upload_cubit.dart
  • lib/core/upload/uploader.dart
📝 Walkthrough

Walkthrough

Upload preparation logic now caches file lengths, reduces sequential conflict and guard checks, and parallelizes upload plan mounting and payment estimation.

Changes

Upload preparation concurrency

Layer / File(s) Summary
File length caching in UploadCubit
lib/blocs/upload/upload_cubit.dart
Adds a _fileLengthCache field, computes file lengths concurrently during startUploadPreparation, and reuses cached values in _getTotalSize() with a fallback.
Parallel conflict detection and upload guards
lib/blocs/upload/upload_cubit.dart
Runs per-file conflict queries concurrently in checkConflictingFiles(), changes the warning-limit yield to Duration.zero, moves the wallet-mismatch guard before the try block, and tightens the ArNS ant-record load condition.
Parallel upload plan mounting and cost estimation
lib/core/upload/uploader.dart
Mounts AR and Turbo upload plans concurrently in prepareFileUpload, runs balance and size retrieval in parallel in getUploadPaymentInfoForUploadPlans, and computes AR and Turbo cost estimates with concurrent futures and separate error handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
    participant UploadCubit
    participant FileSystem
    participant FileRepository
    participant UploadPreparer
    participant UploadPaymentEvaluator

    UploadCubit->>FileSystem: compute selected file lengths concurrently
    UploadCubit->>FileRepository: query conflicting names concurrently
    UploadCubit->>UploadPreparer: startUploadPreparation
    UploadPreparer->>UploadPreparer: mount AR and Turbo plans with Future.wait
    UploadPreparer->>UploadPaymentEvaluator: getUploadPaymentInfoForUploadPlans
    UploadPaymentEvaluator->>UploadPaymentEvaluator: fetch balance and sizes in parallel
    UploadPaymentEvaluator->>UploadPaymentEvaluator: estimate AR and Turbo costs concurrently
Loading
🚥 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 matches the main change: parallelizing the upload preparation pipeline.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch PE-9103-upload-prep-optimization

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: 2

🧹 Nitpick comments (1)
lib/core/upload/uploader.dart (1)

440-451: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reuse the Turbo estimate instead of recalculating it.

For non-free Turbo uploads, turboCostFuture calculates the Turbo cost, then _determineUploadMethod() calls _turboUploadCostCalculator.calculateCost() again. Reuse turboCostEstimate to avoid the duplicate network-bound cost estimate and inconsistent fallback behavior.

Proposed refactor
     uploadMethod = isFreeUploadPossibleUsingTurbo
         ? UploadMethod.turbo
-        : await _determineUploadMethod(
-            turboBalance.balance,
-            turboBundleSizes,
-            _appConfig.allowedDataItemSizeForTurbo,
-            _isTurboAvailableToUploadAllFiles,
-          );
+        : _isTurboAvailableToUploadAllFiles &&
+                turboBalance.balance >= turboCostEstimate.totalCost
+            ? UploadMethod.turbo
+            : UploadMethod.ar;

Also applies to: 473-480

🤖 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 `@lib/core/upload/uploader.dart` around lines 440 - 451, Reuse the existing
Turbo cost result instead of calling the cost calculator twice: the
`turboCostFuture` in `uploader.dart` already produces `turboCostEstimate`, so
update `_determineUploadMethod()` (and the related non-free Turbo path in the
surrounding upload selection flow) to consume that cached estimate rather than
invoking `_turboUploadCostCalculator.calculateCost()` again. Keep the fallback
behavior centralized in the `catchError` handling for `turboCostFuture`, and
ensure the upload method decision uses the already awaited `turboCostEstimate`
value consistently.
🤖 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 `@lib/blocs/upload/upload_cubit.dart`:
- Around line 105-107: The file length cache in UploadCubit is using a derived
string key from getIdentifier(), which can collide for different files and
overwrite cached lengths. Update _fileLengthCache and its read/write sites in
UploadCubit, especially around _getTotalSize() and the upload preparation logic,
to key by the file object or IOFile identity instead of a string identifier.
Make sure the cache lookup and population use the same identity-based key so
distinct files cannot share entries.
- Around line 1078-1080: The manifest reload guard in the upload cubit is
checking for a null `_ants` value even though `_ants` is initialized as an empty
list, so the reload path never runs. Update the condition in the upload flow
within the `UploadCubit` manifest handling logic to use the empty-list fallback
by checking `_ants.isEmpty` instead of `_ants == null`, keeping the existing
reload behavior intact when manifest entries are present.

---

Nitpick comments:
In `@lib/core/upload/uploader.dart`:
- Around line 440-451: Reuse the existing Turbo cost result instead of calling
the cost calculator twice: the `turboCostFuture` in `uploader.dart` already
produces `turboCostEstimate`, so update `_determineUploadMethod()` (and the
related non-free Turbo path in the surrounding upload selection flow) to consume
that cached estimate rather than invoking
`_turboUploadCostCalculator.calculateCost()` again. Keep the fallback behavior
centralized in the `catchError` handling for `turboCostFuture`, and ensure the
upload method decision uses the already awaited `turboCostEstimate` value
consistently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 092002cb-3c6c-4110-ad14-4031aa496e51

📥 Commits

Reviewing files that changed from the base of the PR and between 14623fa and 5607cf0.

📒 Files selected for processing (2)
  • lib/blocs/upload/upload_cubit.dart
  • lib/core/upload/uploader.dart

Comment on lines +105 to +107
/// Pre-computed file lengths cache. Populated in parallel at the start of
/// upload preparation to avoid redundant sequential file.ioFile.length reads.
Map<String, int> _fileLengthCache = {};

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 | 🟡 Minor | ⚡ Quick win

Avoid key collisions in the file length cache.

getIdentifier() can collapse distinct files to the same key, especially for empty/blob paths where it returns only ioFile.name. That can overwrite one file’s cached length and make _getTotalSize() report the wrong total. Key the cache by the file object/IOFile identity instead of a derived string.

Proposed fix
-  Map<String, int> _fileLengthCache = {};
+  Map<IOFile, int> _fileLengthCache = {};
@@
-      size += _fileLengthCache[file.getIdentifier()] ??
+      size += _fileLengthCache[file.ioFile] ??
           await file.ioFile.length;
@@
-        _files[i].getIdentifier(): lengths[i],
+        _files[i].ioFile: lengths[i],

Also applies to: 557-558, 941-945

🤖 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 `@lib/blocs/upload/upload_cubit.dart` around lines 105 - 107, The file length
cache in UploadCubit is using a derived string key from getIdentifier(), which
can collide for different files and overwrite cached lengths. Update
_fileLengthCache and its read/write sites in UploadCubit, especially around
_getTotalSize() and the upload preparation logic, to key by the file object or
IOFile identity instead of a string identifier. Make sure the cache lookup and
population use the same identity-based key so distinct files cannot share
entries.

Comment thread lib/blocs/upload/upload_cubit.dart Outdated
vilenarios and others added 3 commits July 5, 2026 15:12
IOFile.length returns FutureOr<int>, not Future<int>. Future.wait
requires Future<T>, so wrap in async closure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mocktail's thenThrow throws synchronously before returning a Future,
so .catchError never gets attached. Wrapping in async IIFE with
try/catch handles both sync and async throws correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit 73d8619):

https://ardrive-web--pr2161-pe-9103-upload-prep-yfmy7su9.web.app

(expires Sun, 12 Jul 2026 21:24:40 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: a224ebaee2f0939e7665e7630e7d3d6cd7d0f8b0

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.

1 participant