carrier: restore endpoint-count worker scaling + per-account idle semaphore#146
Merged
Conversation
…aphore v1.6 changed the worker formula from v1.5's `workersPerEndpoint × len(endpoints)` to `(workersPerEndpoint + idleSlots - 1) × bucketCount`, where bucketCount is the number of distinct Google account labels. For configs without account labels — the most common legacy pattern, since most users never edited the deprecated v1.4 example config — every endpoint shares one empty-string bucket, so worker count collapses: config v1.5 v1.6 (incl #143) this PR ---------------------------------------------------------------------- 5 unlabeled endpoints 15 3 15 5 deployments across 5 labeled accounts 15 15 15 9 deployments across 5 accounts (#113) 27 15 27 #143 already shipped the right per-worker behavior (revert workersPerEndpoint to 3, fixes upload throughput at the bench's 1-endpoint config). But it does nothing about the worker count regression for multi-endpoint configs — those users still run with bucketCount-scaled workers. This PR restores v1.5's endpoint-count scaling AND replaces the global idle-slot counter with a per-account semaphore inside the picker. The semaphore keeps the anti-abuse cap (issue #56 — multiple deployments under one account couldn't sustain the v1.5 worker count's concurrency on the same Google account) while letting workers freely rotate across accounts. Two pickers now: - pickRelayEndpoint: blacklist-aware, no cap. Used for active polls carrying TX, which terminate quickly with the drained batch and don't camp an account's concurrency budget — matches v1.5 behavior. - pickIdleEndpoint: blacklist-aware AND requires a free idle slot in the candidate's bucket. Atomically reserves; pollOnce releases on return. Each unlabeled endpoint gets its own implicit bucket (key = "url:"+url) so legacy multi-endpoint configs no longer share one bucket with 1 idle slot — they get one slot per endpoint, like v1.5. Bench on the 1-endpoint config (the only one the harness can drive): throughput_up_8MB_1session 22.26 MB/s (unchanged from main) throughput_up_8MB_4sessions 87.17 MB/s (unchanged) sessions_per_sec 4.39 /s (unchanged, within noise) No regression on the configs the bench covers. The actual benefit lives in the worker-count table above — verifiable from the math, not the bench. A multi-endpoint bench scenario should be a follow-up so this kind of regression can be caught automatically next time.
a01ad0e to
afc02b1
Compare
Kianmhz
added a commit
that referenced
this pull request
May 21, 2026
The default-1 baseline was set conservatively against issue #56 when the worker pool was still per-bucket-scaled. With #146's per-account semaphore enforcing the safety cap, two idle slots per bucket is the better default — it raises download responsiveness without putting more concurrent UrlFetchApp executions on any one account than the recommended multi-deployment setup can sustain. Lower to 1 only if you run a single deployment per account; raise to 3 with 3+. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced May 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
v1.6 changed the worker formula from v1.5's
workersPerEndpoint × len(endpoints)to(workersPerEndpoint + idleSlots − 1) × bucketCount(wherebucketCountis the number of distinct Google account labels). For configs without account labels — the most common legacy pattern, since most users never edited the deprecated v1.4 example config to add account labels — every endpoint shares one empty-string bucket, so worker count collapses:#143 already shipped the right per-worker behavior (revert
workersPerEndpoint4→3, fixed upload throughput at the bench's 1-endpoint config). But it does nothing about the worker-count regression for multi-endpoint configs — those users still run with bucketCount-scaled workers.This PR restores v1.5's endpoint-count scaling AND replaces the global idle-slot counter with a per-account semaphore inside the picker. The semaphore keeps the anti-abuse cap from #56 (multiple deployments under one account couldn't sustain v1.5's worker count's concurrency on the same Google account) while letting workers freely rotate across accounts.
What changes
Two pickers now:
pickRelayEndpoint: blacklist-aware, no cap. Used for active polls carrying TX, which terminate quickly with the drained batch and don't camp an account's concurrency budget — matches v1.5 behavior.pickIdleEndpoint: blacklist-aware AND requires a free idle slot in the candidate's bucket. Atomically reserves;pollOncereleases on return.Each unlabeled endpoint gets its own implicit bucket (key =
"url:"+url) so legacy multi-endpoint configs no longer share one bucket with one idle slot — they get one slot per endpoint, like v1.5.What the bench shows (and doesn't)
The harness only configures one endpoint, so it can't directly measure the multi-endpoint win. The bench confirms no regression on the 1-endpoint config:
throughput_up_8MB_1sessionthroughput_up_8MB_4sessionssessions_per_secThree runs each, all stable.
The real benefit lives in the worker-count table above — verifiable from the math in the diff (
numWorkers = workersPerEndpoint * len(endpoints)line), not from this bench. The most-affected user segment (legacy unlabeled multi-endpoint configs) goes from 3 → 15 workers; #113's specific setup goes from 15 → 27.Follow-up
A multi-endpoint bench scenario should be added so this kind of regression can be caught automatically next time. Out of scope for this PR — would need a harness change to spawn N fake
/tunnelendpoints.Verification
go test -count=1 -timeout 90s ./...— all greengo vet ./...— cleanTestCarrier_PureDownloadIdleCap/TestCarrier_IdleSlotsPerBuckettests pass without modification because the per-bucket cap math happens to give the same result on the configs they exercise.🤖 Generated with Claude Code