Skip to content

fix(db): Honour an explicitly configured min_conns of 0 - #565

Open
Vad1mo wants to merge 2 commits into
mainfrom
fix/db-min-conns-zero
Open

fix(db): Honour an explicitly configured min_conns of 0#565
Vad1mo wants to merge 2 commits into
mainfrom
fix/db-min-conns-zero

Conversation

@Vad1mo

@Vad1mo Vad1mo commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

database.minConns could not be set to 0. The Go config layer treated 0 as "unset" and substituted DefaultMinConns (2), so there was no way to run pgxpool without a warm-connection floor. In dense multi-tenant deployments that floor is multiplied across every tenant sharing one PostgreSQL server.

0 is not a degenerate value — it is pgxpool's own defaultMinConns. The pool keeps no warm connections, drains to empty when idle, and opens on demand at acquire time. MaxConns still governs the ceiling, so burst behaviour is unchanged. The cost is connection setup latency (TCP + TLS + auth + backend fork) on the first query after an idle gap.

Why the default was 2

Worth recording, since it looks deliberate and is not. PR #118 (612a3c5f7) replaced one metadata line with another in the same hunk:

-{Name: common.PostGreSQLMaxIdleConns, ..., EnvKey: "POSTGRESQL_MAX_IDLE_CONNS", DefaultValue: "2", ...}
+{Name: common.PostGreSQLMinConns,     ..., EnvKey: "POSTGRESQL_MIN_CONNS",     DefaultValue: "2", ...}

The database/sql knob was a cap on idle connections (keep at most 2 idle); the pgxpool knob is a floor (keep at least 2 warm). The number crossed an inverted semantic. The if x > 0 { } else { default } guard is the "0 means not set" idiom copied from MaxOpenConns directly above it, where 0 genuinely does mean unset for pgxpool.

The default stays 2. It is now overridable.

Changes

  • models.PostGreSQL.MinConns becomes *int32 — nil is "unset", 0 is a configured value.
  • dbpool.applyPoolConfig honours an explicit 0 and returns an error for a negative value instead of silently falling back. New propagates it, so a bad value fails startup rather than quietly changing pool behaviour.
  • ConfigureValue.GetOptionalInt32 returns nil when the key carries no metadata, so a missing key is not read back as a configured 0 (CfgManager.Get hands back an empty ConfigureValue whose GetInt is 0).
  • The exporter uses viper.IsSet, which distinguishes an unset env var from HARBOR_DATABASE_MIN_CONNS=0; viper.GetInt collapses both to 0.

Related Issues

Closes #564

Chart half is on #56 (fix(chart): Allow database.minConns 0, let exporter.config win over chart env). Both halves are needed end to end: the chart used to coerce 0 to 2 in its templates independently of this.

Type of Change

  • Bug fix (fix:)

Testing

  • Unit tests added/updated

Acceptance criteria from the issue, each asserted directly:

Criterion Test
MinConns: ptr(0)pgxpool.Config.MinConns == 0 TestApplyPoolConfig_ExplicitZeroMinConnsHonored
MinConns: nil → 2 TestApplyPoolConfig_NilMinConnsUsesDefault
negative rejected with a clear error TestApplyPoolConfig_NegativeMinConnsRejected
POSTGRESQL_MIN_CONNS=0 survives the whole core path TestGetDatabaseCfg_MinConns (config store → GetDatabaseCfgmodels.PostGreSQL)
HARBOR_DATABASE_MIN_CONNS unset / "" / 0 / 5 TestGetMinConns
an explicit 0 does not read back as unset TestConfigureValue_GetOptionalInt32
go test ./lib/dbpool/... ./lib/config/... ./pkg/config/... ./cmd/exporter/...
ok  github.com/goharbor/harbor/src/lib/dbpool
ok  github.com/goharbor/harbor/src/lib/config/metadata
ok  github.com/goharbor/harbor/src/pkg/config
ok  github.com/goharbor/harbor/src/pkg/config/inmemory
ok  github.com/goharbor/harbor/src/pkg/config/rest
ok  github.com/goharbor/harbor/src/pkg/config/store
ok  github.com/goharbor/harbor/src/cmd/exporter

Note for reviewers: five db-tagged integration cases set cfg.MinConns = 0 with a // don't pre-create comment, which the old coercion silently turned into 2. They now get what they asked for. go test -tags db ./lib/dbpool/... needs a live PostgreSQL and has not been run locally.

Checklist

  • PR title follows Conventional Commits format
  • Commits are signed off (git commit -s)
  • No new warnings introduced

POSTGRESQL_MIN_CONNS / HARBOR_DATABASE_MIN_CONNS could not be set to 0.
Both the config layer and dbpool treated 0 as "unset" and substituted
DefaultMinConns (2), so there was no way to run pgxpool without a warm
connection floor. That floor is multiplied across every tenant sharing one
PostgreSQL server in dense multi-tenant deployments.

0 is pgxpool's own defaultMinConns, not a degenerate value: the pool keeps
no warm connections, drains to empty when idle, and opens on demand at
acquire time. MaxConns still governs the ceiling, so burst behaviour is
unchanged. The cost is connect latency on the first query after an idle gap.

DefaultMinConns = 2 was itself inherited from the removed
POSTGRESQL_MAX_IDLE_CONNS in 612a3c5 (#118) — but that was a cap on idle
connections, whereas MinConns is a floor. The number crossed an inverted
semantic. The default stays 2; it is now overridable.

- models.PostGreSQL.MinConns becomes *int32: nil = unset, 0 = configured.
- dbpool.applyPoolConfig honours an explicit 0 and returns an error for a
  negative value instead of silently falling back; New propagates it, so a
  bad value fails startup rather than quietly changing pool behaviour.
- ConfigureValue.GetOptionalInt32 returns nil for a key with no metadata, so
  a missing key is not read back as a configured 0 (CfgManager.Get yields an
  empty ConfigureValue whose GetInt is 0).
- The exporter uses viper.IsSet, which distinguishes an unset env var from
  HARBOR_DATABASE_MIN_CONNS=0; viper.GetInt collapses both to 0.

Closes #564

Signed-off-by: Vadim Bauer <vb@container-registry.com>
Copilot AI lite review requested due to automatic review settings August 4, 2026 22:09
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

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

Scanned Files

None

@gitar-bot

gitar-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

@github-actions

github-actions Bot commented Aug 4, 2026

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

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Vad1mo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

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

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 35b10bfc-0a9f-44b7-8c99-56d023a0e091

📥 Commits

Reviewing files that changed from the base of the PR and between 4d8260a and 474b630.

📒 Files selected for processing (4)
  • src/cmd/exporter/main.go
  • src/cmd/exporter/main_test.go
  • src/lib/config/metadata/value.go
  • src/lib/config/metadata/value_test.go
📝 Walkthrough

Walkthrough

The change makes PostgreSQL MinConns optional. It preserves explicit zero values, applies the default only when unset, rejects negative values, propagates pool configuration errors, and adds unit, integration, and configuration-path tests.

Changes

PostgreSQL minimum-connections configuration

Layer / File(s) Summary
Optional configuration contract
src/common/models/database.go, src/lib/config/metadata/value.go, src/lib/config/metadata/value_test.go
PostGreSQL.MinConns now uses *int32. GetOptionalInt32 returns nil for undefined settings and preserves configured zero values.
Configuration entry points
src/lib/config/systemconfig.go, src/pkg/config/manager.go, src/cmd/exporter/main.go, src/cmd/exporter/main_test.go, src/pkg/config/inmemory/manager_test.go
Configuration readers and the exporter pass optional minimum-connection values through to the database model. Tests cover unset, empty, zero, and positive values.
Pool configuration validation
src/lib/dbpool/pool.go, src/lib/dbpool/pool_test.go
The pool applies DefaultMinConns only for nil values, accepts zero, rejects negative values, and returns configuration errors from New.
Pool behavior coverage
src/lib/dbpool/pool_integration_test.go
Integration tests use pointer-based MinConns values across pool capacity, connection, recovery, concurrency, timeout, and eviction scenarios.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The Go requirements are implemented and tested, but issue #564 also requires the Helm chart to preserve minConns: 0. Add the Helm template presence check and tests so database.minConns: 0 renders as "0" instead of "2".
Docstring Coverage ⚠️ Warning Docstring coverage is 42.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes honoring an explicitly configured min_conns value of 0.
Description check ✅ Passed The description includes the summary, related issue, change type, testing details, and checklist, with optional release notes omitted.
Out of Scope Changes check ✅ Passed The code and tests remain focused on preserving explicit zero MinConns values and validating related configuration behavior.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/db-min-conns-zero
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/db-min-conns-zero

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/cmd/exporter/main_test.go`:
- Around line 43-50: Update the subtest setup around viper.Reset and the
HARBOR_DATABASE_MIN_CONNS environment variable to explicitly isolate that
variable before each case. Ensure the set: false path cannot inherit a value
from the test runner, while preserving the existing tt.set behavior for cases
that provide tt.env.

In `@src/lib/config/metadata/value.go`:
- Around line 91-92: Validate the parsed MinConns value against the int32 range
before narrowing in the value conversion logic around GetInt, returning a
startup error for values outside math.MinInt32 through math.MaxInt32; update
both src/lib/config/metadata/value.go:91-92 and src/cmd/exporter/main.go:135-136
as applicable, and add boundary coverage for math.MaxInt32 + 1 and math.MinInt32
- 1.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 00482ced-a870-4436-9822-1bb3b6e2af19

📥 Commits

Reviewing files that changed from the base of the PR and between 5447798 and 4d8260a.

📒 Files selected for processing (11)
  • src/cmd/exporter/main.go
  • src/cmd/exporter/main_test.go
  • src/common/models/database.go
  • src/lib/config/metadata/value.go
  • src/lib/config/metadata/value_test.go
  • src/lib/config/systemconfig.go
  • src/lib/dbpool/pool.go
  • src/lib/dbpool/pool_integration_test.go
  • src/lib/dbpool/pool_test.go
  • src/pkg/config/inmemory/manager_test.go
  • src/pkg/config/manager.go

Comment thread src/cmd/exporter/main_test.go
Comment thread src/lib/config/metadata/value.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes the configuration path so database.minConns can be explicitly set to 0 (pgxpool’s default) instead of being treated as “unset” and coerced to DefaultMinConns (2). This improves correctness for multi-tenant / low-traffic deployments where a warm-connection floor is undesirable, while also making invalid negative values fail fast.

Changes:

  • Represent models.PostGreSQL.MinConns as *int32 so nil means “unset” and 0 is an explicit, valid value.
  • Update dbpool configuration application to honor explicit 0 and reject negative values with a startup error.
  • Add config/exporter handling + tests to preserve explicit 0 through config read/export paths.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/pkg/config/manager.go Uses GetOptionalInt32() so the config layer can preserve an explicit 0 vs “unset”.
src/pkg/config/inmemory/manager_test.go Adds an end-to-end-ish unit test asserting explicit 0 survives config store → GetDatabaseCfg().
src/lib/dbpool/pool.go Makes applyPoolConfig return an error; honors *int32 minConns including explicit 0, rejects negatives.
src/lib/dbpool/pool_test.go Updates tests for pointer-based MinConns and adds explicit coverage for 0, nil, and negative values.
src/lib/dbpool/pool_integration_test.go Updates integration tests for pointer-based MinConns and fixes deref usage.
src/lib/config/systemconfig.go Uses GetOptionalInt32() for system config database min conns.
src/lib/config/metadata/value.go Adds ConfigureValue.GetOptionalInt32() to distinguish missing-metadata from configured 0.
src/lib/config/metadata/value_test.go Adds tests for GetOptionalInt32() behavior (but currently has an order-dependence issue).
src/common/models/database.go Changes MinConns to *int32 with omitempty semantics.
src/cmd/exporter/main.go Introduces getMinConns() to return nil unless the env var is set, preserving explicit 0.
src/cmd/exporter/main_test.go Adds tests asserting unset/empty/0/5 behavior for exporter getMinConns().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/config/metadata/value_test.go

@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.

All reported issues were addressed across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/common/models/database.go
Review follow-ups on #565:

- GetOptionalInt32 and the exporter's getMinConns validated only after
  narrowing to int32, but both parse paths admit wider values (parseInt via
  its float fallback, viper via cast.ToInt). 1<<32 wrapped to an explicit 0 —
  the opposite of the configured intent, and invisible to the negativity
  check. Out-of-range now logs and reads as unset.
- TestGetMinConns isolates HARBOR_DATABASE_MIN_CONNS from the ambient
  environment; viper.Reset does not clear the process env, so a CI runner
  exporting the variable broke the unset case.
- TestConfigureValue_GetOptionalInt32 pins the real metadata ConfigList;
  neighbouring tests swap in testingMetaDataArray without restoring it, which
  lacks postgresql_min_conns, making the test order-dependent.

Signed-off-by: Vadim Bauer <vb@container-registry.com>

@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.

All reported issues were addressed across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/lib/config/metadata/value.go
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Preview images for this PR are available in 8gears.container-registry.com/8gcr-pr with tag pr-565.

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

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-565

Verify 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-565

@Vad1mo
Vad1mo requested a balanced review from Copilot August 7, 2026 13:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment on lines +96 to +98
if v < math.MinInt32 || v > math.MaxInt32 {
log.Errorf("GetOptionalInt32 failed: value %d of %s exceeds int32 range, treating as unset", v, c.Name)
return nil
Comment thread src/cmd/exporter/main.go
Comment on lines +139 to +141
if v < math.MinInt32 || v > math.MaxInt32 {
log.Warningf("database.min_conns %d exceeds int32 range, using the pool default", v)
return nil
@Vad1mo

Vad1mo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

#586 does the same thing but a bit more compact.
duplicate of #586

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow database.minConns: 0 — both the Go config layer and the Helm chart silently coerce it to 2

2 participants