Skip to content

feat(commons): ConnectionPool.setProperty / setProperties for the DuckDB instance, the memory reserve included - #524

Closed
tanejagagan wants to merge 2 commits into
dazzleduck-web:mainfrom
tanejagagan:feat/connection-pool-properties
Closed

tanejagagan wants to merge 2 commits into
dazzleduck-web:mainfrom
tanejagagan:feat/connection-pool-properties

Conversation

@tanejagagan

Copy link
Copy Markdown
Collaborator

Summary

Builds on #523, which computes DuckDB's memory_limit as container − heap − reserve. In #523 the reserve was fixed at 512 MiB unless a -D system property said otherwise, so a service couldn't size it from its own configuration. This PR adds an API to set it, and any other DuckDB property, before the instance is created.

This PR contains #523's commit. A PR from another fork can't use #523's branch as its base. Merge #523 first and this one shows only its own commit, or merge this one in place of #523.

ConnectionPool.setProperty("dazzleduck.duckdb.memory_reserve_mib", "1024");
ConnectionPool.setProperties(Map.of(
        "threads", "8",
        "dazzleduck.duckdb.auto_memory_limit", "false"));
  • Two variants: setProperty sets one property and setProperties sets several. setProperties checks every entry before applying any, so one bad entry leaves none set.
  • One set of keys everywhere: DuckDB options (memory_limit, threads, ...) are passed through. DazzleDuck's own dazzleduck.duckdb.memory_reserve_mib and dazzleduck.duckdb.auto_memory_limit are used to compute the limit and then removed, because DuckDB rejects options it doesn't know. The same keys also work in duckdb.properties. System properties with those names still work if neither sets them.
  • Precedence: setProperty/setProperties win over duckdb.properties, which wins over the computed memory_limit. A startup script's SET memory_limit still overrides all of them.
  • The instance is created on the first connection, in a lazy holder instead of the enum constructor. Touching any static member of the enum used to create it, so a static setProperty would always have come too late. No callers change.
  • Fails loudly: a property set after the instance exists throws IllegalStateException instead of being silently ignored. A negative or non-numeric reserve throws IllegalArgumentException; feat(commons): default DuckDB memory_limit to container minus heap minus reserve #523 ignored it and used 512.

Not in this PR

No service calls the API from its own config yet. In Runtime, the call would go just before executeOnSingleton(startupScript); the otel collector and compactor would need the same.

Tests

  • DuckDBMemoryLimitTest gains three tests: properties win over system properties and aren't passed to DuckDB; a bad reserve is rejected; setting properties after the instance exists is refused, for both variants.
  • Full dazzleduck-sql-commons suite: 784 run, 0 failures, 8 skipped (JDK 25). Javadoc builds.

🤖 Generated with Claude Code

gtaneja-sentrinox and others added 2 commits October 29, 2026 12:01
…nus reserve

Left unset, DuckDB sizes memory_limit from the memory it can see -- the
cgroup limit in a container -- as if the process were DuckDB alone. It
shares the process with the JVM: heap, Metaspace, threads, agents and
Arrow's off-heap buffers sit on top of DuckDB's buffer pool under the same
limit, so DuckDB's default plus the JVM can exceed the container's limit,
and the kernel OOM-kills the process before DuckDB spills. Each service
has had to hand-size memory_limit against its container limit and heap,
and keep the two in step.

ConnectionPool now sets, when duckdb.properties has no memory_limit:

  memory_limit = total - max heap - reserve   (floor 128 MiB)

total is the JVM's view of the machine (OperatingSystemMXBean
getTotalMemorySize: the cgroup limit under UseContainerSupport), max heap
is Runtime.maxMemory(), reserve defaults to 512 MiB for everything outside
the heap. E.g. a 2 GiB container whose JVM has a 512 MiB max heap gets
1024 MiB. The computed value is logged at startup.

Precedence: memory_limit in duckdb.properties wins; a startup script's
SET memory_limit overrides later. -Ddazzleduck.duckdb.auto_memory_limit=false
keeps DuckDB's own default; -Ddazzleduck.duckdb.memory_reserve_mib=N
changes the reserve. Unknown total or unbounded heap leaves DuckDB's
default, with a warning; a budget under 128 MiB is floored, with a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kDB instance's properties, the memory reserve included

The reserve was fixed at 512 MiB unless a -D system property said otherwise, so a
service could not size it from its own configuration. ConnectionPool.setProperty
(one) and setProperties (several, all checked before any is applied) take
properties before the first connection: a DuckDB option (overriding
duckdb.properties), or one of DazzleDuck's own keys,
dazzleduck.duckdb.memory_reserve_mib and dazzleduck.duckdb.auto_memory_limit, which
are removed before the properties reach DuckDB. The same keys work in
duckdb.properties, with the system properties as the fallback.

The instance is now created on the first connection instead of with the enum,
since any static call initializes the enum and would arrive too late. A property
set after that throws instead of being ignored, and a negative or non-numeric
reserve is rejected rather than ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tanejagagan

Copy link
Copy Markdown
Collaborator Author

Superseded by #525: only the ConnectionPool change, without the computed memory_limit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants