Skip to content

fix(scan): Parse the vulnerability report once during conversion - #885

Open
bupd wants to merge 1 commit into
mainfrom
fix/scan-report-single-parse
Open

bupd wants to merge 1 commit into
mainfrom
fix/scan-report-single-parse

Conversation

@bupd

@bupd bupd commented Sep 14, 2026

Copy link
Copy Markdown
Member

Refs #478.

ToRelationalSchema parses the raw report into a vuln.Report, then hands the
same raw string to toSchema, which parses the whole document again into a
second complete tree:

rawReport := new(vuln.Report)
json.Unmarshal([]byte(reportData), &rawReport)     // report_converters.go:68

c.toSchema(ctx, reportUUID, registrationUUID, digest, reportData)
    └─ json.Unmarshal([]byte(rawReportData), &vulnReport)   // :103, same bytes again

Both trees stay reachable through the heaviest stretch of the conversion, where
the CVE id set, the existing records loaded from the database, the item index and
the new ORM records all pile up on top of them. Parsed structs run several times
the size of the JSON they came from, so on a report of a few thousand
vulnerabilities the second tree is tens of MB that nothing ever reads.

This passes the parsed report to toSchema instead.

Effect

Peak live heap across one conversion, database faked out, measured at GOGC=1 so
HeapAlloc tracks reachable bytes:

5,000-vuln report (4.1 MiB raw)
before 12.6 MiB — 3.1x the raw report
after 5.1 MiB — 1.2x

For scale: one artifact on a production tenant returned 5,180 vulnerability
records, and the report in #478 was 10-40 MB.

toSchema also builds its item index locally rather than through
Report.GetVulnerabilityItemList, which caches the index on the report and would
otherwise keep every item reachable after the caller clears Vulnerabilities.

Test

TestToRelationalSchemaDoesNotParseTheReportTwice measures peak live heap around
a conversion and fails on the unpatched code (3.1x against a 2.0x bound), so
it guards the regression rather than just passing. The fakes are hand-rolled
rather than mockery, whose argument capture would be most of what the numbers
measured. Runs in the pure lane — no database needed.

Rejected along the way

Decoding with json.NewDecoder over a strings.Reader, to avoid the
[]byte(reportData) copy, allocates more rather than less: on a single
multi-MB document the decoder's internal buffer grows by reallocation and costs
more than the one copy it saves (27 MB to 31 MB total allocation, measured). Not
done, and the reason is in a code comment so it does not get retried.

Scope

This is item 2 of the five ranked in #478 and does not close it on its own. Items
1, 4 and 5 stand: GOMEMLIMIT at the chart level, batching the per-record
inserts, and streaming decode. Item 3, bounding concurrent conversions, is better
handled as part of #862, where the same MaxCurrency() == 0 is the root; see the
assessment posted there so the two issues do not grow separate concurrency knobs.

ToRelationalSchema parsed the raw report into a vuln.Report, then handed the
raw string to toSchema, which parsed the whole document a second time. Both
trees stayed reachable through the heaviest stretch of the conversion, where
the CVE id set, the existing records loaded from the database, the item index
and the new ORM records all pile up on top of them. Parsed structs run a few
times the size of the JSON they came from, so on a report of a few thousand
vulnerabilities the second tree is tens of MB that nothing needs.

Pass the parsed report to toSchema instead. Peak live heap for a 5000
vulnerability report falls from 3.1x the raw report to 1.2x.

toSchema builds its item index locally rather than through
Report.GetVulnerabilityItemList, which caches the index on the report and
would keep every item reachable after the caller drops Vulnerabilities.

The new test measures peak live heap around a conversion with the database
faked out, and fails on the two-parse version.

Refs #478

Signed-off-by: Prasanth Baskar <prasanth@8gears.com>
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 45 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 5a526c7c-510d-4bd3-b9dc-ccb7b3b9a876

📥 Commits

Reviewing files that changed from the base of the PR and between 58adb87 and b0f63e1.

📒 Files selected for processing (2)
  • src/pkg/scan/postprocessors/report_converters.go
  • src/pkg/scan/postprocessors/report_converters_memory_test.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the tests label Sep 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@github-actions

Copy link
Copy Markdown
Contributor

This change may need patch-release backports. Comment with one of these commands to open a cherry-pick PR:

/backport v2.15

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Re-trigger cubic

@github-actions

Copy link
Copy Markdown
Contributor

Preview images for this PR are available in 8gears.container-registry.com/8gcr-pr with tag pr-885, built from b0f63e1:

  • 8gears.container-registry.com/8gcr-pr/harbor-core:pr-885
  • 8gears.container-registry.com/8gcr-pr/harbor-jobservice:pr-885
  • 8gears.container-registry.com/8gcr-pr/harbor-registryctl:pr-885
  • 8gears.container-registry.com/8gcr-pr/harbor-exporter:pr-885
  • 8gears.container-registry.com/8gcr-pr/harbor-portal:pr-885
  • 8gears.container-registry.com/8gcr-pr/harbor-registry:pr-885
  • 8gears.container-registry.com/8gcr-pr/trivy-adapter:pr-885

Verify a preview image:

cosign verify \
  --certificate-identity-regexp="https://github.com/container-registry/harbor-next/.github/workflows/pr-ci.yml@.*" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  8gears.container-registry.com/8gcr-pr/harbor-core:pr-885

Verify the SBOM attestation:

cosign verify-attestation \
  --certificate-identity-regexp="https://github.com/container-registry/harbor-next/.github/workflows/pr-ci.yml@.*" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
  --type spdxjson \
  8gears.container-registry.com/8gcr-pr/harbor-core:pr-885

@bupd bupd added prio/P1 Wanted this cycle; schedule it into a merge wave state/needs-review Green and rebased; waiting on a human labels Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prio/P1 Wanted this cycle; schedule it into a merge wave state/needs-review Green and rebased; waiting on a human tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants