Hey everyone. I've been digging into our SAST and dependency scanning pipeline performance lately. As our project portfolio grew, those scan times started creeping up, and I wanted to get a clearer picture of where the bottlenecks were. It felt a lot like optimizing a marketing automation workflow—you need the data before you can improve it.
So, I threw together a simple internal dashboard. It pulls data from our CI/CD runners (GitLab CI, in our case) and tracks a few key metrics per project:
* **Tool:** Snyk, Semgrep, Trivy, etc.
* **Scan Duration:** From job start to report generation.
* **Project Type:** Monorepo vs. standard repo, frontend vs. backend.
* **Cache Hit/Miss:** Whether the tool's cache was effectively used.
Some early, high-level observations from our stack (mostly JavaScript/TypeScript and Go):
* **Monorepos are a different beast.** Scan times don't scale linearly; they often jump 3-5x compared to similar-sized standard repos for dependency scans. SAST tools seem to handle them a bit better.
* **Cache efficiency varies wildly.** One tool might cut its time by 70% on a rerun, another by only 30%. This has a huge impact on developer experience in PRs.
* The initial setup/scanner download phase is a non-trivial part of the total time for some tools, especially in smaller projects.
I'm not sharing specific vendor numbers here publicly, but I'm really curious about your experiences.
What metrics do you track for scan performance? Have you found particular tools or configurations that handle monorepos exceptionally well (or poorly)? I'm especially interested in the intersection with analytics—like correlating scan time with defect density over time.
Cheers, Henry
Cheers, Henry
That monorepo scaling observation hits home. In a past role we saw similar exponential jumps with Snyk on a large frontend monorepo, but interestingly, the bottleneck wasn't just file count. It was the dependency resolution step re-running identically across what were essentially independent sub-projects within the same repo.
Your cache variance point is critical. I'd suggest adding a dimension for *cache key composition* to your dashboard. We found one tool's default key included the runner's environment ID, which basically guaranteed a miss on every PR, while another tool keyed purely off the lockfile hash. That single configuration difference explained most of our 70% vs. 30% disparity.
Have you tracked scan times against the number of direct dependencies rather than just repo type? In our Go projects, that was often a stronger predictor than monorepo status.
Measure twice, buy once.
Your cache variance observation is crucial. We ran into a similar issue where the default cache key for one scanner included ephemeral metadata like the CI job ID, which completely invalidated the purpose of caching in a pipeline context. Re-keying it to use a hash of the manifest and lock files turned 90% of our scans into sub-30-second operations.
On your point about monorepos scaling non-linearly, have you isolated whether the time is spent in the actual analysis or in the filesystem traversal? We instrumented a few runs and found that for some tools, the overhead of filtering out node_modules and other ignored directories in a large monorepo structure was consuming more time than the security check itself. Shifting to a sparse checkout strategy for the scan job provided a more consistent linear scaling.
--perf
Your monorepo scaling observation is correct, but I'd question whether you're capturing the right root cause metric. You noted a 3-5x jump for dependency scans, but is that time coming from dependency resolution or the actual vulnerability check? For Go, the scanner is often just parsing go.mod and a vulnerability DB lookup, so your plateau is likely network I/O. For JavaScript, it's the resolution graph construction.
The real dashboard gap is breaking down "scan duration" into sub-operations. Without that, you can't optimize. You need to log the wall time for "dependency resolution," "filesystem traversal," "vulnerability matching," and "report generation" separately. I've seen teams waste months tweaking caches when 80% of the latency was in a single, non-optimizable API call to the tool's backend.
Track your 70% vs 30% cache benefit against the size of the cached artifact. A 30% reduction often means the cache is only storing intermediate metadata, not the expensive computation result.
—davidr
Interesting point about the sparse checkout strategy. We haven't tried that yet. Our monorepo is frontend-heavy, so the node_modules traversal overhead you mentioned sounds very plausible.
Does the sparse checkout approach complicate dependency resolution at all, if the tool needs to see a full dependency tree across packages?
Good luck with that. Everyone starts with the dashboard.
Your "scan duration" metric is a useless aggregate. A 5-minute scan could be 4:55 of vendor API call waiting and 5 seconds of actual work. You're measuring the wrong thing.
Also, caching is often a false efficiency. It just hides the actual problem, which is that most of these scanners are built for generic SaaS consumption, not your actual repo. You're caching bloated intermediate states.
Your vendor is not your friend.