feat: introduce SNYK_REQUEST_CONCURRENCY for dependency request parallelism#6756
feat: introduce SNYK_REQUEST_CONCURRENCY for dependency request parallelism#6756
Conversation
…lelism Add a tunable concurrency knob for in-flight dependency-test/dependency-monitor HTTP requests, default 10 (raised from the prior hard-coded 5), clamped to [1, 50]. Override via the SNYK_REQUEST_CONCURRENCY environment variable. Apply the new helper at the existing pMap call site in sendAndParseResults (run-test.ts). The default bump is effectively a no-op for non-container test workloads (single-project tests produce one payload; --all-projects rarely produces more than the prior 5-payload ceiling), but materially improves wall-clock for container tests that produce one ScanResult per directory containing dependencies. A follow-up PR will adopt the same helper in the container monitor path, which is currently fully sequential.
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
Benchmark —
|
| Configuration | Mean wall-clock | vs baseline |
|---|---|---|
baseline (c=5) |
68.04 s ± 1.72 s | 1.00× |
PR A default (c=10) |
40.96 s ± 0.15 s | 1.66× faster (−40%) |
PR A override (c=20) |
25.13 s ± 0.48 s | 2.71× faster (−63%) |
Each config: 3 runs after 1 warmup; image pre-pulled. Min/max within ±2% of mean across all configs — variance is tight.
Standard deviation drops with higher concurrency (1.72s → 0.15s → 0.48s) because the wait-time component shrinks and the scan becomes more CPU-bound on the (much steadier) JAR-extraction work in snyk-docker-plugin.
PR Reviewer Guide 🔍
|
| * SNYK_REQUEST_CONCURRENCY environment variable; values are clamped to | ||
| * [MIN_REQUEST_CONCURRENCY, MAX_REQUEST_CONCURRENCY]. | ||
| */ | ||
| export function getRequestConcurrency(): number { |
There was a problem hiding this comment.
Suggestion: There is already a similar configuration value in the Golang CLI. While this is currently not publicly documented, I think we should inject it in the Legacy CLI and have the Golang configuration a single source of truth for this. We can also expose the config value but would probably want to frame it around SNYK_MAX_CONCURRENCY rather then focussed on requests.
| export const RETRY_ATTEMPTS = 3; | ||
| export const RETRY_DELAY = 500; | ||
|
|
||
| const DEFAULT_REQUEST_CONCURRENCY = 10; |
There was a problem hiding this comment.
Thinking: Generally changing a default without visibility in the behaviour, we currently don't have any metrics on CPU consumption, makes me wonder if we should be more careful. The impact is quite huge as it affects all SCA and Container scans.
What does this PR do?
Adds a tunable concurrency knob for in-flight dependency-test / dependency-monitor HTTP requests, and bumps the default from 5 to 10.
getRequestConcurrency()insrc/lib/snyk-test/common.ts— readsSNYK_REQUEST_CONCURRENCY, defaults to 10, clamps to[1, 50].MAX_CONCURRENCY = 5constant at the existingpMapcall site insendAndParseResults(src/lib/snyk-test/run-test.ts).A follow-up PR (#6757) adopts the same helper in
src/lib/ecosystems/monitor.ts:monitorDependencies, which is currently fully sequential.Why?
snyk container testproduces oneScanResultper directory of dependencies in the image (lib/analyzer/applications/java.ts:groupJarFingerprintsByPathinsnyk-docker-plugin). For Java-heavy images this can be hundreds of ScanResults, each becoming a separatePOST /test-dependenciesrequest. With the prior concurrency cap of 5, the request fan-out is the dominant wall-clock cost.Benchmark —
quay.io/wildfly/wildfly:34.0.1.Final-jdk21(512 ScanResults)hyperfine --warmup 1 --runs 3against the same locally-built PR binary, varying onlySNYK_REQUEST_CONCURRENCYto isolate the API concurrency change from any other build/runtime differences. Thec=5row reproduces the prior hard-coded value, so it's an apples-to-apples baseline.c=5)c=10)c=20)Min/max within ±2% of mean across all configs — variance is tight. Standard deviation drops with higher concurrency (1.72s → 0.15s → 0.48s) because the wait-time component shrinks and the scan becomes more CPU-bound on the (much steadier) JAR-extraction work in
snyk-docker-plugin.For non-container test workloads the default bump is essentially a no-op:
--all-projectsrarely produces more than the prior 5-payload ceiling.Where should the reviewer start?
src/lib/snyk-test/common.ts— new helper.src/lib/snyk-test/run-test.ts— single call-site swap.test/jest/unit/lib/snyk-test/common.spec.ts— 9 new unit tests for the helper covering default, override, clamping, and invalid-input cases.How should this be manually tested?
npx jest --selectProjects coreCli --testPathPattern 'test/jest/unit/lib/snyk-test/common.spec'Risk assessment
Low. The change is scoped to a single constant + a small env-var helper. Default behavior change (5 → 10) only affects test invocations that produce more than 5 payloads, which today means container tests and large
--all-projectsruns. The override is bounded to[1, 50]to prevent footguns.Background
Supersedes #6747 (which targeted the wrong code path —
src/lib/ecosystems/test.ts:testDependenciesis unreachable fromsnyk container test;getEcosystemForTestreturns null for docker, so container test goes throughrunTest/pMapinstead).