fix: use LEFT JOIN in projectBasicSQL to include projects without quota records#23520
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug in Harbor's Prometheus exporter (ProjectCollector) where projects were silently dropped from the scraped metrics map when quota_per_project_enable=false. In that mode Harbor creates no quota/quota_usage rows, and the previous INNER JOIN-based projectBasicSQL excluded those projects, which then triggered a flood of project not found error logs from the member/repo/artifact collectors on every scrape (risking Prometheus timeouts). The fix converts the joins to LEFT JOIN, moves join-condition filters into the ON clauses (required for correct outer-join semantics), and adds COALESCE defaults so quota-less projects report empty quota/usage (storage 0) instead of being dropped.
Changes:
- Rewrote
projectBasicSQLto useLEFT JOINonproject_metadata,quota, andquota_usage, withCOALESCEdefaults ('false','{}','{}') and the filter predicates moved into theONclauses. - Changed the quota join to compare
project.project_id::text = reference_idinstead of castingreference_idto integer, which is safer givenreference_idisvarchar. - Added test project
testPro3(created without quota records) and assertions that it appears in the collector map with empty quota/usage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
src/pkg/exporter/project_collector.go |
Converts projectBasicSQL from INNER to LEFT JOINs with COALESCE defaults so projects lacking quota records are still collected. |
src/pkg/exporter/project_collector_test.go |
Adds testPro3 (no quota rows) and asserts it is present in the map with {} quota/usage, plus updates teardown cleanup to include the new project. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ta records When quota_per_project_enable is false, Harbor does not create quota or quota_usage rows for new projects. The previous INNER JOIN on those tables silently excluded every project without a quota record from the collector map, causing the member/repo/artifact update functions to log a "project not found" error for each missing project on every scrape. With ~500+ affected projects this produced hundreds of error log writes per scrape, increasing scrape latency and risking Prometheus timeouts. Affected projects also had no metrics emitted for quota, member, repo, or artifact dimensions. Switch to LEFT JOIN with join-condition filtering (moving name='public' and reference='project' predicates into the ON clause) and COALESCE defaults so every non-deleted project is always present in the map. Projects without quota records emit 0 for storage quota/usage. Also cast project_id to text for the quota join rather than casting reference_id to integer, which is safer when the quota table contains non-integer reference IDs from other reference types. Add a test case that creates a project without quota records and asserts it appears in the collector map with empty quota/usage values. Signed-off-by: cdarninsuang <cdarninsuang+github@bamfunds.com>
e84126a to
0a77d00
Compare
Fixes #23519
What does this PR fix?
When
quota_per_project_enableisfalse, Harbor does not create rows in thequotaorquota_usagetables for new projects. The exporter'sprojectBasicSQLusedINNER JOINon both tables, so any project missing those rows was silently excluded from the in-memory project map built on each scrape.The three subsequent collector functions (
updateProjectMemberInfo,updateProjectRepoInfo,updateProjectArtifactInfo) query those same projects via independent JOINs on theprojecttable directly, and calllog.Errorffor each project ID not found in the map. With a large number of projects missing quota records this produces hundreds of error log writes per scrape, increasing scrape latency and risking Prometheus timeouts that drop all Harbor metrics.Changes
src/pkg/exporter/project_collector.go: ConvertprojectBasicSQLfromINNER JOINtoLEFT JOINonquota,quota_usage, andproject_metadata. Join-condition filtering (e.g.quota.reference='project',project_metadata.name='public') is moved into theONclause as required for correctLEFT JOINsemantics.COALESCEdefaults ensure projects without quota records emit{}(storage = 0) rather than being dropped. The quota join now castsproject_idto text rather than castingreference_idto integer, which is safer if the quota table ever contains non-integer reference IDs.src/pkg/exporter/project_collector_test.go: AddtestPro3, a project created without quota records, and assert it appears in the collector map with empty quota/usage values — covering the previously broken path.Testing
Existing unit tests continue to pass for projects with quota records. The new
testPro3case verifies the fix.Tested against a live Harbor instance where
quota_per_project_enable=falseand 529 of 611 projects had no quota rows — after the fix, all 611 projects appear in the map and theproject not founderror flood is eliminated.Notes
Signed-off-by: cdarninsuang cdarninsuang@bamfunds.com