DuckDB とは
OLAP(オンライン分析処理)ワークロードに特化した組み込み SQLite といったイメージです。
- Apache Parquet ファイルをストレージとして使用
- ベクトル化されたクエリ処理エンジン(vectorized query processing engine)を使用し、大規模データセット分析に使われる
- クエリにはSQLを使用
- 外部依存関係がなく、C++11コンパイラだけでビルド可能
- 従来のクライアントサーバーモデルではなく、ホストプロセス内で組み込み実行される
DuckDB には C/C++API に加え、各種言語向けのクライアントAPIが提供されています。 ここでは Java から DuckDB を利用してみます。
DuckDB JDBC
DuckDB を Java から利用する場合、JDBC が提供されているので、これを利用するだけです。
Kotlin DSL の場合、以下の依存を追加します。
dependencies {
implementation("org.duckdb:duckdb_jdbc:1.1.3")
}
duckdb_jdbc
には、各種の環境別の共有ライブラリが同梱されており、環境に応じた共有ライブラリをJNIでロードします。
ネイティブライブラリを JDBC 経由で操作する流れになります。
DuckDB 操作
他のデータベースと同様に、JDBC を操作するだけです。
接続文字列として jdbc:duckdb:
とすると、インメモリ動作となります(終了時にデータは無くなります)。
jdbc:duckdb:/tmp/my_database
のようにデータベースファイルのパスを付与することで、データは永続化されます。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class App { public static void main(String[] args) throws Exception { Connection conn = DriverManager.getConnection("jdbc:duckdb:"); Statement stmt = conn.createStatement(); stmt.execute("CREATE TABLE items (item VARCHAR, value DECIMAL(10, 2), count INTEGER)"); stmt.execute("INSERT INTO items VALUES ('jeans', 20.0, 1), ('hammer', 42.2, 2)"); try (ResultSet rs = stmt.executeQuery("SELECT * FROM items")) { while (rs.next()) { System.out.println(rs.getString(1) + " " + rs.getInt(3)); } } stmt.close(); } }
単なる JDBC 操作なので、特に注目すべき点もありませんが、実行すると以下のような出力が得られます。
jeans 1 hammer 2
DuckDB 特有の機能を利用する場合は、DuckDBConnection
にキャストして利用することになります。
DuckDBConnection conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:");
Batch Insert
大量データのインサートにはバッチインサート機能を使うことができます。
以下のようになります。
private void batchInsert(DuckDBConnection conn) throws Exception { try (PreparedStatement stmt = conn.prepareStatement("INSERT INTO items VALUES (?, ?, ?);")) { stmt.setObject(1, 1); stmt.setObject(2, 2); stmt.setObject(3, 3); stmt.addBatch(); stmt.setObject(1, 4); stmt.setObject(2, 5); stmt.setObject(3, 6); stmt.addBatch(); stmt.executeBatch(); } }
Appender
バルク挿入には、Appender を利用することで、より高いパフォーマンスを得ることができます。
private void append(DuckDBConnection conn) throws Exception { try (var appender = conn.createAppender(DuckDBConnection.DEFAULT_SCHEMA, "items")) { appender.beginRow(); appender.append(1); appender.append(2); appender.append(3); appender.endRow(); appender.beginRow(); appender.append(4); appender.append(5); appender.append(6); appender.endRow(); } }
データのインポート
以下のように CSV ファイルからテーブルにコピーすることができます。
COPY tbl FROM 'test.csv.gz' (HEADER false);
ロードされた内容から直接テーブルを作成することもできます。
CREATE TABLE test AS SELECT * FROM 'test.csv';
Parquet ファイルや JSON ファイルについても同様に操作できます。
COPY tbl FROM 'test.parquet'; COPY tbl FROM 'test.json';