perf(cli): parallelize deploy metadata fetch and function uploads#1793
perf(cli): parallelize deploy metadata fetch and function uploads#1793dqn wants to merge 6 commits into
Conversation
Deploy issued platform RPCs one at a time in three hot paths: the sdk-version change detection fetched metadata for every candidate TRN serially, and the function-registry and TailorDB type applies looped over creates/updates sequentially. Run each batch with Promise.all; total in-flight RPCs stay bounded by the operator client's shared concurrency limiter. Measured on the example app (37 metadata TRNs, 19 functions, 12 types): a no-change deploy drops from 8.8s to 4.1s, an update deploy from 20.5s to 10.3s.
Re-serialize TailorDB type creates/updates: concurrent DDL within a namespace races platform-side schema mutation (the invariant fc0c469 established), so only the metadata fetch and function-upload paths stay parallel. Pin the invariant with a sequential-apply test. Function upload streams bypass the operator client's unary concurrency interceptor, so cap them explicitly with the shared apply-concurrency limiter (TAILOR_APPLY_CONCURRENCY, default 16).
Run each function's upload and setMetadata inside a single limiter slot so effective RPC concurrency stays within TAILOR_APPLY_CONCURRENCY, and let a detected sdk-version mismatch win over an unrelated metadata fetch failure instead of aborting the deploy.
🦋 Changeset detectedLatest commit: 2bef878 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
📖 Docs Quality & Consistency Check
✅ Docs are consistent with the implementation and contain no user-facing internal-detail leaks.
Checked areas:
- CLI environment variable documentation (
TAILOR_APPLY_CONCURRENCY) - Implementation of concurrent metadata fetching in
deploy.ts - Implementation of concurrent function uploads in
function-registry.ts - Test coverage for new concurrent behavior
- JSDoc comments on modified functions
Key findings:
- The
TAILOR_APPLY_CONCURRENCYdocumentation change accurately reflects the implementation change: streaming function uploads are now gated by the same concurrency budget (viacreateApplyLimiter()) instead of running unbounded - The simplified description ("Max concurrent platform RPCs" vs "Max concurrent unary platform RPCs... (streaming uploads are not gated)") improves clarity by removing technical implementation details that users don't need to understand
- All code changes are internal to the CLI implementation and not part of the public SDK API
- The
shouldForceApplyAllfunction, now exported within its module for testing purposes, is not part of the package's public API exports
Re-run this check by adding the
docs-checklabel to the PR.
| return true; | ||
| } | ||
| } | ||
| const results = await Promise.allSettled( |
There was a problem hiding this comment.
The concurrent getMetadata fetch has no concurrency cap.
function-registry.ts bounds its uploads with createApplyLimiter() (TAILOR_APPLY_CONCURRENCY), but this one fires getMetadata for every candidate TRN at once.
On projects with many resources this risks overloading the platform. Suggest limiting it with the same createApplyLimiter().
There was a problem hiding this comment.
getMetadata is a unary RPC, so these calls already pass through the concurrencyLimitInterceptor() installed by initOperatorClient(). That interceptor shares a single createApplyLimiter() budget across all unary calls on the client, so adding another call-site limiter here would only duplicate the existing cap.
| await uploadFunctionScript(client, workspaceId, create.entry, true); | ||
| await client.setMetadata(create.metaRequest); | ||
| } | ||
| await Promise.all( |
There was a problem hiding this comment.
Creates and updates run in two separate Promise.all waves, so updates never overlap with the tail of the creates wave.
Since both share the same limitFunction, merging both map calls into one array passed to a single Promise.all would let them overlap naturally.
Fixed in 2bef878. |
Code Metrics Report (packages/sdk)
Details | | main (fdd6254) | #1793 (a0f50d9) | +/- |
|--------------------|----------------|-----------------|-------|
+ | Coverage | 74.5% | 74.5% | +0.0% |
| Files | 457 | 458 | +1 |
| Lines | 16951 | 16965 | +14 |
+ | Covered | 12634 | 12650 | +16 |
+ | Code to Test Ratio | 1:0.4 | 1:0.4 | +0.0 |
| Code | 113290 | 113591 | +301 |
+ | Test | 52562 | 52832 | +270 |Code coverage of files in pull request scope (90.0% → 90.5%)
SDK Configure Bundle Size
Runtime Performance
Type Performance (instantiations)
Reported by octocov |
Summary
Deploy fetched sdk-version metadata for every candidate resource one at a time
and uploaded function scripts serially; both now run concurrently, bounded by
the shared apply-concurrency budget (
TAILOR_APPLY_CONCURRENCY, default 16).Measured on example/ (37 metadata TRNs, 15 functions, 12 types)
Notes
platform-side schema mutation (the invariant fc0c469 established); a test
now pins the sequential order.
metadata fetch failure instead of aborting the deploy.
their counts are small and can be parallelized later if profiling justifies it.