Add duckdb.MemoryUsage JFR periodic event for non-blocking per-instance memory sampling#678
Open
arouel wants to merge 6 commits intoduckdb:mainfrom
Open
Add duckdb.MemoryUsage JFR periodic event for non-blocking per-instance memory sampling#678arouel wants to merge 6 commits intoduckdb:mainfrom
duckdb.MemoryUsage JFR periodic event for non-blocking per-instance memory sampling#678arouel wants to merge 6 commits intoduckdb:mainfrom
Conversation
Collaborator
|
Hi, thanks for the PR! To pass the formatter please run: python -m pip install --user clang_format==11.0.1
make formatOr just apply the following diff: diff --git a/src/main/java/org/duckdb/DuckDBConnection.java b/src/main/java/org/duckdb/DuckDBConnection.java
index 24f0105d..fd48e531 100644
--- a/src/main/java/org/duckdb/DuckDBConnection.java
+++ b/src/main/java/org/duckdb/DuckDBConnection.java
@@ -72,8 +72,8 @@ public final class DuckDBConnection implements java.sql.Connection {
return newConnection(url, readOnly, sessionInitSQL, properties, null);
}
- public static DuckDBConnection newConnection(String url, boolean readOnly, String sessionInitSQL, Properties properties,
- String monitorName) throws SQLException {
+ public static DuckDBConnection newConnection(String url, boolean readOnly, String sessionInitSQL,
+ Properties properties, String monitorName) throws SQLException {
if (null == properties) {
properties = new Properties();
}
diff --git a/src/main/java/org/duckdb/DuckDBDriver.java b/src/main/java/org/duckdb/DuckDBDriver.java
index 1883b19b..771cfcf2 100644
--- a/src/main/java/org/duckdb/DuckDBDriver.java
+++ b/src/main/java/org/duckdb/DuckDBDriver.java
@@ -140,7 +140,8 @@ public class DuckDBDriver implements java.sql.Driver {
String monitorName = removeOption(props, JDBC_JFR_MEMORY_MONITOR);
// Create connection
- DuckDBConnection conn = DuckDBConnection.newConnection(pp.shortUrl, readOnly, sf.origFileText, props, monitorName);
+ DuckDBConnection conn =
+ DuckDBConnection.newConnection(pp.shortUrl, readOnly, sf.origFileText, props, monitorName);
// Run post-init
try {
@@ -177,8 +178,9 @@ public class DuckDBDriver implements java.sql.Driver {
"Do not close the DB instance after all connections to it are closed"));
list.add(createDriverPropInfo(JDBC_IGNORE_UNSUPPORTED_OPTIONS, "",
"Silently discard unsupported connection options"));
- list.add(createDriverPropInfo(JDBC_JFR_MEMORY_MONITOR, "",
- "User-assigned identifier under which this connection's DuckDB instance is tracked in the duckdb.MemoryUsage JFR event. Leave empty to disable monitoring. JFR controls the event's enabled state and period via recording settings. Requires a JFR-capable JVM."));
+ list.add(createDriverPropInfo(
+ JDBC_JFR_MEMORY_MONITOR, "",
+ "User-assigned identifier under which this connection's DuckDB instance is tracked in the duckdb.MemoryUsage JFR event. Leave empty to disable monitoring. JFR controls the event's enabled state and period via recording settings. Requires a JFR-capable JVM."));
list.sort((o1, o2) -> o1.name.compareToIgnoreCase(o2.name));
return list.toArray(new DriverPropertyInfo[0]);
}
diff --git a/src/main/java/org/duckdb/DuckDBMemoryEvent.java b/src/main/java/org/duckdb/DuckDBMemoryEvent.java
index a2d00383..7577b560 100644
--- a/src/main/java/org/duckdb/DuckDBMemoryEvent.java
+++ b/src/main/java/org/duckdb/DuckDBMemoryEvent.java
@@ -39,7 +39,8 @@ import jdk.jfr.StackTrace;
public class DuckDBMemoryEvent extends Event {
@Label("Name")
- @Description("User-assigned identifier of the DuckDB instance (value of the jdbc_jfr_memory_monitor connection property)")
+ @Description(
+ "User-assigned identifier of the DuckDB instance (value of the jdbc_jfr_memory_monitor connection property)")
String name;
@Label("Tag")
@@ -54,9 +55,7 @@ public class DuckDBMemoryEvent extends Event {
@Description("Native address of the underlying DuckDB instance; disambiguates databases when names collide")
long dbAddress;
- @Label("Memory Usage")
- @Description("Bytes currently allocated for this tag")
- long memoryUsageBytes;
+ @Label("Memory Usage") @Description("Bytes currently allocated for this tag") long memoryUsageBytes;
@Label("Temporary Storage Usage")
@Description("Bytes spilled to the temporary storage for this tag")
diff --git a/src/main/java/org/duckdb/DuckDBMemoryMonitor.java b/src/main/java/org/duckdb/DuckDBMemoryMonitor.java
index 325013d5..b64b23d2 100644
--- a/src/main/java/org/duckdb/DuckDBMemoryMonitor.java
+++ b/src/main/java/org/duckdb/DuckDBMemoryMonitor.java
@@ -51,7 +51,8 @@ final class DuckDBMemoryMonitor {
private static boolean initialized;
// Non-instantiable
- private DuckDBMemoryMonitor() {}
+ private DuckDBMemoryMonitor() {
+ }
/**
* Registers the periodic JFR hook for {@link DuckDBMemoryEvent}. Idempotent
@@ -152,7 +153,8 @@ final class DuckDBMemoryMonitor {
// Publish monitorConn last so readers see fully-populated state.
monitorConn = mc;
} catch (SQLException e) {
- logger.log(Level.WARNING, "Failed to open JFR memory-monitor connection; will retry on next open()", e);
+ logger.log(Level.WARNING, "Failed to open JFR memory-monitor connection; will retry on next open()",
+ e);
}
}
openConnections++;
@@ -196,8 +198,7 @@ final class DuckDBMemoryMonitor {
String nameSnap = name;
String url = dbUrl;
long addr = dbAddress;
- try (Statement stmt = mc.createStatement();
- ResultSet rs = stmt.executeQuery(QUERY)) {
+ try (Statement stmt = mc.createStatement(); ResultSet rs = stmt.executeQuery(QUERY)) {
while (rs.next()) {
String tag = rs.getString(1);
long memoryUsageBytes = rs.getLong(2);
diff --git a/src/main/java/org/duckdb/JfrMemoryMonitor.java b/src/main/java/org/duckdb/JfrMemoryMonitor.java
index 600f864d..727825ee 100644
--- a/src/main/java/org/duckdb/JfrMemoryMonitor.java
+++ b/src/main/java/org/duckdb/JfrMemoryMonitor.java
@@ -45,7 +45,8 @@ final class JfrMemoryMonitor {
CONNECTION_CLOSED = closed;
}
- private JfrMemoryMonitor() {}
+ private JfrMemoryMonitor() {
+ }
static void init() {
if (INIT == null) { |
duckdb.MemoryUsage JFR periodic event for non-blocking per-instance memory sampling
Operating DuckDB-backed Java applications at scale currently offers no in-process, low-overhead way to observe DuckDB's internal memory usage over time. Users either sit inside the JVM's heap metrics (which don't capture native allocations) or run ad-hoc SELECT * FROM duckdb_memory() queries by hand. JFR is the standard, always-available observability sink on modern JVMs, so emitting DuckDB memory usage as a periodic JFR event lets operators diagnose memory growth, per-tag breakdowns, and temporary-storage spill with the same tooling already used for the rest of the JVM — enabled or disabled via JFR recording settings, sampled at the period the consumer chooses, and opt-in per database via a single JDBC connection property.
5bd424b to
5458f5f
Compare
The last row is the real trade-off: we now depend on `BufferManager::GetMemoryUsageInfo()` and the `MemoryTag` enum layout. Both have been stable for a long time and the `MEMORY_TAGS` length-min guard in sample() lets an older JAR cope with a DuckDB that adds new tags.
Collaborator
Sorry, we do not want to introduce any new usage of DuckDB C++ API. Currently only a few things are missing in C API to move the whole JDBC driver onto it. Can we continue using |
…b_memory()`" This reverts commit 4ac7686.
Contributor
Author
@staticlibs I made the changes to use |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Sampling
duckdb_memory()on an active connection blocks for the duration of any in-flight query, so getting a memory snapshot at an arbitrary moment requires either waiting for the query to finish or holding a spare connection open just for diagnostics. In a process running multiple DuckDB instances there is a second problem:duckdb_memory()returns a global per-tag snapshot with no way to attribute usage to a specific instance. A periodic JFR event backed by a dedicated monitor connection solves both: sampling never interferes with application queries, and each event carriesdbAddress(the native instance pointer) as a stable attribution key.What
Adds a
duckdb.MemoryUsageJFR periodic event, opt-in per connection via thejdbc_jfr_memory_monitorJDBC property, that samplesduckdb_memory()on a dedicated side-channel connection per database instance and emits one event per memory tag per JFR tick.