프로파일 및 모니터링
프로파일 및 모니터링 (Profile and Monitor, Java/JDBC)
개요 (Overview)
JDBC 드라이버는 실행 중이거나 완료된 쿼리를 관찰하는 여러 방법을 제공해요. 쿼리 취소 및 타임아웃, 쿼리 진행 폴링, 프로파일링 출력 검색, Java Flight Recorder를 통한 메모리 사용 이벤트 발생이 그것이에요. 이 페이지가 각각을 다룬다.
출처: 문서
본문
쿼리 진행 및 취소 (Query Progress and Cancellation)
실행 중인 쿼리는 표준 JDBC Statement.cancel() 메서드로 다른 스레드에서 취소할 수 있어요. 이는 statement의 연결에서 실행 중인 쿼리를 중단해요. Statement.setQueryTimeout(seconds)를 설정하면 주어진 초 후 자동 취소가 준비돼요.
오래 실행되는 쿼리의 경우 DuckDBPreparedStatement.getQueryProgress()가 쿼리 실행 중 QueryProgress 스냅샷을 반환해요. 이는 별도 스레드에서 호출하도록 설계되었으며 getPercentage(), getRowsProcessed(), getTotalRowsToProcess()를 노출해요.
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.CompletableFuture;
import org.duckdb.DuckDBConnection;
import org.duckdb.DuckDBPreparedStatement;
import org.duckdb.QueryProgress;
try (DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
DuckDBPreparedStatement ps =
(DuckDBPreparedStatement) conn.prepareStatement("SELECT count(*) FROM range(1_000_000_000)")) {
// 백그라운드 스레드에서 쿼리를 실행해 실행 중 진행 상황을 폴링할 수 있게 함.
CompletableFuture<Void> query = CompletableFuture.runAsync(() -> {
try {
ps.executeQuery().close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
while (!query.isDone()) {
QueryProgress progress = ps.getQueryProgress();
System.out.printf("%.1f%% (%d / %d rows)%n", progress.getPercentage(),
progress.getRowsProcessed(), progress.getTotalRowsToProcess());
Thread.sleep(100);
}
query.join();
}
프로파일링 (Profiling)
연결을 DuckDBConnection으로 캐스트하고 PRAGMA enable_profiling으로 프로파일링을 활성화한 다음, getProfilingInformation(ProfilerPrintFormat)을 호출해 그 연결의 가장 최근 쿼리에 대한 프로파일링 출력을 검색해요. ProfilerPrintFormat 열거형이 출력 형식을 선택해요: DEFAULT, TEXT, QUERY_TREE, QUERY_TREE_OPTIMIZER, NO_OUTPUT, JSON, HTML, GRAPHVIZ, YAML, 그리고 MERMAID.
try (DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
Statement stmt = conn.createStatement()) {
stmt.execute("PRAGMA enable_profiling = 'json'");
stmt.executeQuery("SELECT count(*) FROM range(1000)").close();
String profile = conn.getProfilingInformation(ProfilerPrintFormat.JSON);
System.out.println(profile);
}
JFR로 메모리 모니터링 (Memory Monitoring with JFR)
JFR 지원 JVM에서 드라이버는 DuckDB의 내부 메모리 사용을 설명하는 Java Flight Recorder 이벤트를 발생시킬 수 있어요. 따라서 JDK Mission Control, jfr 커맨드라인 도구, 연속 프로파일러 등 JFR 인식 도구가 나머지 JVM 신호와 함께 DuckDB 메모리 메트릭을 수집할 수 있어요. 이 기능은 엄격히 옵트인이면서 JFR 지원이 없는 JVM에서는 조용한 no-op이에요. 애플리케이션이 JDBC 프로퍼티를 연결에 설정하고 이벤트를 활성화하는 활성 JFR 녹화를 가질 때만 아무것도 발생되지 않아요.
발생 활성화 (Enabling Emission)
모니터링은 연결별 옵트인이에요. jdbc_jfr_memory_monitor 프로퍼티를 연결의 데이터베이스 인스턴스 식별자로 설정해요. 값은 모든 이벤트에 component 필드로 첨부되는 임의 라벨이므로, 운영자가 메모리를 논리 구성 요소에 귀속시킬 수 있어요. 값이 없거나 비어 있으면 모니터링이 비활성화된 상태로 남아요.
Properties props = new Properties();
props.setProperty("jdbc_jfr_memory_monitor", "analytics-db");
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:/tmp/my_database", props)) {
// JFR 녹화가 활성화된 동안 연결로 작업
}
같은 옵션을 URL에 설정할 수도 있어요:
jdbc:duckdb:/tmp/my_database;jdbc_jfr_memory_monitor=analytics-db
| 프로퍼티 값 | 효과 |
|---|---|
| 없음 | 이 연결에 대해 이벤트가 발생되지 않음. |
| 빈 문자열 | 이벤트가 발생되지 않음, 없음과 동일. |
| 비어 있지 않은 문자열 | 이벤트가 발생되고 주어진 값으로 태그됨. |
JDBC 프로퍼티는 활성화 및 라벨 스위치일 뿐이에요. JFR이 녹화 중인지나 샘플링 주기를 제어하지는 않아요. 이들은 JFR 녹화 설정으로 관리돼요.
주기와 활성화 상태 제어
샘플링 속도와 활성화 상태는 JFR 네이티브 설정이에요. .jfc 프로파일에서, JMC를 통해, 또는 프로그래밍 방식으로 jdk.jfr.Recording API로 구성해요:
try (Recording r = new Recording()) {
r.enable("duckdb.MemoryUsage").withPeriod(Duration.ofSeconds(1));
r.start();
// ... 애플리케이션 작업 ...
r.stop();
r.dump(Path.of("app.jfr"));
}
동등한 .jfc 스니펫:
<event name="duckdb.MemoryUsage">
<setting name="enabled">true</setting>
<setting name="period">1 s</setting>
</event>
녹화가 이벤트를 활성화하지 않으면 드라이버는 아무 작업도 하지 않아요. 기본 duckdb_memory() 쿼리는 절대 발행되지 않아요.
이벤트 스키마 (Event Schema)
녹화가 이벤트를 활성화한 채 활성 상태인 동안 드라이버는 JFR 틱마다 DuckDB 메모리 태그별로 duckdb.MemoryUsage 이벤트 하나를 발생시켜요. 필드는 다음과 같아요:
| 필드 | 타입 | 설명 |
|---|---|---|
component |
String |
애플리케이션이 제공한 식별자, jdbc_jfr_memory_monitor 값. |
tag |
String |
DuckDB 메모리 태그, 예: BASE_TABLE, HASH_TABLE, ALLOCATOR. |
dbAddress |
long |
DuckDB 인스턴스의 네이티브 주소, 인스턴스별 안정 식별자. |
memoryUsageBytes |
long |
이 태그에 현재 할당된 바이트. |
temporaryStorageBytes |
long |
이 태그에 대해 임시 저장소로 스필된 바이트. |
표준 JFR startTime, duration, eventThread 필드도 존재해요. 이 이벤트에는 스택 트레이스가 비활성화돼요.
귀속 (Attribution)
모니터는 JDBC 연결이 아니라 네이티브 DuckDB 인스턴스 주소(dbAddress 필드)를 기준으로 해요. 서로 다른 인스턴스별로 샘플 스트림이 하나 있으므로 공유 메모리는 이중 계산되지 않아요. component 라벨은 인스턴스에 옵트인한 첫 연결에서 캡처되고, 이후 같은 인스턴스에 옵트인한 연결은 그것을 바꾸지 않아요. 모니터는 첫 옵트인 연결이 열릴 때 생성되고 마지막 것이 닫힐 때 해체돼요. 이후 새 옵트인 연결을 열면 새 모니터가 시작돼요.
두 getConnection 호출이 dbAddress를 공유하는지는 [인스턴스 캐싱]({% link docs/current/clients/java/connecting.md %}#database-instances-and-instance-caching)과 같은 규칙을 따라요:
| URL 또는 연산 | 같은 dbAddress? |
|---|---|
jdbc:duckdb: (이름 없는 인메모리) |
아니요 — 호출마다 새 인스턴스. |
jdbc:duckdb:memory:⟨label⟩ |
예, ⟨label⟩이 일치할 때. |
jdbc:duckdb:⟨path⟩ |
예, 경로가 일치할 때. |
conn.duplicate() |
예, 항상. |
데이터베이스 인스턴스를 공유하는 연결은 dbAddress도 공유하므로 component 라벨도 단일해요. 구성 요소별 귀속을 위해 각 구성 요소가 서로 다른 인스턴스로 해석되는 연결을 열어야 해요. 이름 없는 인메모리 jdbc:duckdb: URL은 연결-전용 인스턴스를 만들므로 가장 간단한 선택이에요. 각 연결에 서로 다른 jdbc_jfr_memory_monitor 값을 부여해요.
요구 사항 (Requirements)
이 기능은 JFR 지원 JVM이 필요해요:
- OpenJDK 및 HotSpot 11 이상에는 JFR이 포함돼요.
- Amazon Corretto 8, OpenJDK 8u272 이상, 기타 여러 Java 8 배포판에는 JFR 백포트(
jdk.jfr패키지)가 포함돼요. jdk.jfr가 없는 JVM(일부 축소된 Java 8 빌드)에서는 이 기능이 조용한 no-op이에요.jdbc_jfr_memory_monitor프로퍼티는 무시되고jdk.jfr에 의존하는 클래스는 로드되지 않아요.
추가 JVM 플래그는 필요 없어요.
녹화 검사 (Inspecting a Recording)
캡처된 녹화는 JDK Mission Control 또는 JDK에 번들된 jfr 커맨드라인 도구로 검사해요:
jfr summary app.jfr | grep duckdb.MemoryUsage
jfr print --events duckdb.MemoryUsage app.jfr
jfr metadata app.jfr | sed -n '/class MemoryUsage/,/^}/p'
더 알아보기 (Learn more)
- [Define Connections]({% link docs/current/clients/java/connecting.md %}) —
jdbc_jfr_memory_monitor프로퍼티와 드라이버가 실행하는 쿼리 취소 스케줄러 스레드. - [Profiling]({% link docs/current/dev/profiling.md %}) — DuckDB의 쿼리 프로파일링 출력과
getProfilingInformation()이 반환하는 형식. - [Run Queries]({% link docs/current/clients/java/querying.md %}) — 이 페이지가 진행, 타이밍, 프로파일을 관찰하는 쿼리 실행하기.