Tracked branch capacity estimator

Will tracking more branches with Vulnerabilities Across Contexts (VAC) fit your GitLab Self-Managed database? For admins, CSMs and solutions architects.

This is an estimate. GitLab's performance and stability vary a lot with workload and deployment, so your results may differ. Leave room for a margin of error.

The calculator needs JavaScript and must be served over HTTP. If you opened this file from disk, run python3 -m http.server in the public folder and open http://localhost:8000.

In short

VAC is generally available from GitLab 19.5. Tracking a branch stores another copy of every finding on that branch. If each project tracks one more branch with the same findings as its default branch, the per-finding vulnerability tables roughly double. If only some projects track branches, they grow by those projects' share of your vulnerabilities. Each pipeline on a tracked branch writes about as much as one on the default branch.

To size it for your instance, fill in your plan below, then run the SQL script on your database for the rest. It only reads data.

Your numbers

These are example values. Replace them with yours.

Your plan

Only projects with at least one tracked branch besides the default branch.

Besides the default branch, tracked at the same time. By default a project can track its default branch plus 3 more branches or tags.

Per project, over the planning period. Untracked branches keep their data, and tracking the same branch again stores it again.

Pipelines with security reports. Include merge request pipelines whose source branch is tracked. See Build > Pipelines, filtered by branch.

Your instance today

Get these from the SQL script. It only reads data. On a Linux package install, run sudo gitlab-rails dbconsole --database main < measure-your-db.sql on the primary. On a read replica it skips queries 4 and 5.

The database that holds vulnerability data, usually the main database. Query 2 shows it as database_size_gib.

Average on the default branch. Query 3b lists the 50 projects with the most vulnerabilities. Average its not_resolved column over the projects that will track branches, or its vulnerabilities column to be cautious. If they aren't all listed, divide vulnerabilities_on_default_branches by projects_with_vulnerabilities from query 3.

Average SBOM components, from the dependencies column of query 3b. Each tracked branch gets its own reference to each dependency.

WAL (write-ahead log) is PostgreSQL's record of every change, and backups and replicas copy it. Query 4 gives today's rate as wal_gib_per_day_average. Leave it empty and the write load isn't checked.

Advanced settings

The share of default-branch vulnerabilities also found on each tracked branch. 100% is the most cautious choice (every branch has the same findings as the default branch). On GitLab.com a tracked branch carries about 11.5% to 18.5%.

Compact is a lean test report in freshly packed tables. Real reports carry more links per finding and bigger payloads, and rows rewritten every pipeline add bloat, so the verdict uses the GitLab.com size. Custom uses your own numbers from the SQL script.

Estimate

Enter your numbers to see an estimate.

Treat this as a starting point

These numbers come from measurements on GitLab.com and on a test install. GitLab's performance and stability vary a lot with workload and deployment, so your results may differ. Use the estimate to get an idea of the size of the change, and leave room for a margin of error. The "Plan for" figures add some headroom, but they can't cover every setup. How we measured.

Formula

For each tracked branch, VAC stores its own copy of every finding on that branch, plus its own reference to each dependency. GitLab calls each tracked branch a tracked context. A finding is one vulnerability on one branch, so the same vulnerability on two tracked branches is two findings. Bytes per vulnerability means the bytes for one finding. Shared tables (identifiers, scanners, SBOM components and occurrences, statistics) don't get new rows.

branches now      = projects x tracked branches per project
branches stored   = projects x (tracked branches + branches tracked and later untracked)
findings/branch   = vulnerabilities per project x share
added findings    = branches stored x findings/branch
dependency refs   = branches now x dependencies per project x 1.1

added storage     = added findings x bytes per vulnerability
                  + dependency refs x bytes per dependency ref

pipelines/day     = branches now x pipelines per day per branch
row updates/day   = pipelines/day x (findings/branch x 5.5 + dependencies per project x 2)
WAL/day           = pipelines/day x findings/branch x 14 to 17 KiB
first ingestion   = added findings x 25 to 29 KiB of WAL      (one time)

plan for storage  = added storage (high end) x 1.5
plan for WAL/day  = WAL/day (high end) x 2

1.1 is how many dependency refs a tracked branch has compared to the default branch on GitLab.com. 5.5 and 2 are row updates per finding and per dependency, measured on a GDK (see Method). Untracking removes a branch's dependency refs but keeps its findings, so branches you later untrack still count toward findings storage.

"Plan for" adds headroom on top of the high end. The two measured profiles differ by 1.83x, child rows per finding were 2.6x higher on GitLab.com than on the GDK (mostly finding links, which vary a lot by scanner), and bloat depends on your autovacuum settings, so storage gets 1.5x. WAL was only measured on lean payloads, without Sidekiq follow-up writes or continuous vulnerability scanning, so it gets 2x. If the "Plan for" figure lands in the next band, the verdict says so but keeps the lower band's colour.

Units: KiB = 1,024 bytes. GB on this page means 1,0243 bytes, the same as pg_size_pretty shows. The page rounds numbers to 2 significant figures.

Rule-of-thumb thresholds

We picked these as rules of thumb. The product doesn't enforce them, and your own hardware and backup setup matter more.

CheckLikely finePlan aheadTest first
Added storage, % of database
WAL added, % of your current WAL

WAL (write-ahead log) is PostgreSQL's record of every change it writes. Backups, WAL archiving and replicas all have to copy it. For the presets, storage uses the GitLab.com size (the high end), and Custom uses your own number. WAL uses the high end and is checked only when you enter your current WAL per day from query 4. Until then it shows as Not checked. WAL archive space needed is WAL per day x days of retention.

Method and sources

GitLab.com production (2026-10-07)

Read-only measurements on postgres.ai thin clones of the GitLab.com production sec database, taken 2026-10-07 at about 11:00 UTC. Sizes are on disk and include table data (heap), large values stored out of line (TOAST), indexes and bloat. Row counts are planner estimates. Duplication counts are exact.

  • 9,783 B per vulnerability per tracked branch across the per-branch tables (about 9.6 KiB, or 9,640 B without temporary backfill indexes).
  • About 250 B per sbom_occurrence_refs row.
  • Tracked branches carry 11.5% to 18.5% of the default branch's findings, and about 1.1x its dependency refs.
Biggest contributors per finding on GitLab.com. Smaller tables (signatures, evidences, flags, remediation links) add about 64 B.
TableRows per findingBytes per finding
vulnerability_occurrences1.003,922
vulnerability_finding_links11.072,965
vulnerability_reads1.001,429
vulnerabilities1.00549
vulnerability_occurrence_identifiers1.94418
security_finding_enrichments0.70222
vulnerability_finding_risk_scores1.00144
sbom_occurrences_vulnerabilities0.3273

Controlled measurement on a GDK (2026-10-07)

GitLab master 03043cd4f1ee (committed 2026-10-07 11:00 UTC), PostgreSQL 17.8, separate sec database. One project, 2,000 findings (1,000 SAST, 1,000 dependency scanning) and 500 SBOM components, ingested on the default branch and on one tracked branch.

  • About 5.2 KiB (5,336 B) per vulnerability per tracked branch, heap plus indexes, in freshly packed tables.
  • About 244 B per dependency ref.
  • Steady-state pipeline: 12,028 row updates and no inserts, about 6.0 per finding. 2.7 of those are HOT updates, the cheaper in-place kind that skips index writes. The estimator splits this into 5.5 per finding plus 2 per dependency.
  • WAL: 14 to 17 KiB per finding per steady-state pipeline, typical for lean payloads. Real scanner payloads write more (not measured). First ingestion writes 25 to 29 KiB per finding.
  • Each pipeline rewrites every tracked finding's rows, so autovacuum has more to clean on these tables. PostgreSQL keeps the old copy of each rewritten row (a dead tuple) until autovacuum removes it. Query 5 shows whether it keeps up today. More links per finding, bigger payloads and steady-state bloat are why the GitLab.com profile is the high end.
  • A tracked-branch pipeline costs the same as a default-branch pipeline.
  • Not measured: Sidekiq follow-up jobs (for example search indexing and statistics), so real write load is higher.

Reading the code

Data model and limits come from reading GitLab master on 2026-10-07: 54e51fb3 for the data model and the branch limit, and 05dd34b6f45 for what untracking leaves behind.

Child row counts

"Other per-finding rows" uses 5.4 per finding (GDK) to 14.2 per finding (GitLab.com). Finding links vary the most by scanner.

Things to know

  • Untracking doesn't remove data. When you stop tracking a branch, its findings, vulnerabilities and vulnerability report rows stay in the database. Only the dependency refs are removed, and tracking the same branch again stores its findings again. Plan storage as if tracked branches are permanent.
  • You can't get that storage back, so start with one tracked branch on a few projects, then re-run the script and compare with the estimate.
  • With continuous vulnerability scanning on, new advisories create findings on every tracked branch between pipelines. This isn't in the estimate.
  • If you use advanced search (Elasticsearch or OpenSearch) for the vulnerability report, each tracked branch's findings are indexed too, so plan index storage in proportion. Filtering by branch on Self-Managed needs advanced search with advanced vulnerability management.
  • A project ingests one pipeline at a time. If several finish while one is being ingested, only the latest is ingested next, so fewer pipelines may be ingested than you enter.
  • By default a project can track its default branch plus 3 more branches or tags.
  • These estimates come from GitLab as of October 2026 (see Method), and later versions may differ. Even with the same version, results vary with workload and deployment. For the best number, run the SQL script and use Custom, then start with a few projects and compare.