xk6_duckdb

package module
v1.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2025 License: MIT Imports: 8 Imported by: 0

README

k6 DuckDB Extension

A k6 extension that provides DuckDB database functionality for load testing scenarios involving database operations. This extension leverages the go-duckdb library to provide high-performance analytical database capabilities within k6 tests.

Features

  • In-memory and persistent databases: Support for both temporary and file-based databases
  • Full SQL support: Execute any DuckDB-compatible SQL statements
  • High performance: Built on DuckDB's columnar engine optimized for analytics
  • Easy data loading: Built-in support for CSV loading and batch insertions
  • Go-to-JavaScript bridge: Seamless integration with k6's JavaScript runtime
  • Connection management: Proper resource management with connection pooling

Installation

Prerequisites
  • Go 1.21 or later
  • xk6 tool for building k6 with extensions
  • GCC compiler (required for go-duckdb)
Build the Extension
  1. Install xk6:
go install go.k6.io/xk6/cmd/xk6@latest
  1. Clone or create your extension directory:
mkdir xk6-duckdb && cd xk6-duckdb
  1. Create the files (go.mod, main.go) as provided in this example

  2. Build k6 with the DuckDB extension:

xk6 build --with github.com/k6-extensions-doku/xk6-duckdb=.

This will create a k6 binary in your current directory with the DuckDB extension included.

Usage

Basic Connection and Queries
import duckdb from 'k6/x/duckdb';

export default function() {
    const db = new duckdb.DuckDB();
    
    try {
        // Open database (empty string = in-memory)
        db.open("");
        
        // Create table
        db.createTable("users", {
            id: "INTEGER PRIMARY KEY",
            name: "VARCHAR(100)",
            email: "VARCHAR(100)"
        });
        
        // Insert data
        db.insertData("users", [
            { id: 1, name: "John Doe", email: "john@example.com" },
            { id: 2, name: "Jane Smith", email: "jane@example.com" }
        ]);
        
        // Query data
        const users = db.query("SELECT * FROM users WHERE id > ?", [0]);
        console.log(`Found ${users.count} users`);
        
        // Get single row
        const user = db.querySingle("SELECT * FROM users WHERE id = ?", [1]);
        console.log(`User: ${user.name}`);
        
        // Get scalar value
        const count = db.queryScalar("SELECT COUNT(*) FROM users");
        console.log(`Total users: ${count}`);
        
    } finally {
        db.close();
    }
}
Advanced Features
// Load CSV data
db.loadCSV("sales_data", "/path/to/sales.csv", {
    header: true,
    delimiter: ",",
    quote: "\""
});

// Complex analytics queries
const analytics = db.query(`
    SELECT 
        DATE_TRUNC('month', order_date) as month,
        COUNT(*) as order_count,
        SUM(amount) as total_revenue,
        AVG(amount) as avg_order_value
    FROM orders 
    WHERE order_date >= '2024-01-01'
    GROUP BY DATE_TRUNC('month', order_date)
    ORDER BY month
`);

// Use DuckDB's built-in functions
const stats = db.query(`
    SELECT 
        percentile_cont(0.5) WITHIN GROUP (ORDER BY amount) as median_amount,
        stddev(amount) as amount_stddev
    FROM orders
`);

API Reference

DuckDB Class
Constructor
  • new duckdb.DuckDB() - Creates a new DuckDB connection instance
Methods
Connection Management
  • open(dsn: string): void - Opens database connection

    • dsn: Database path or empty string for in-memory database
    • Example: db.open("/path/to/database.db") or db.open("")
  • close(): void - Closes the database connection

Query Methods
  • query(sql: string, ...args): QueryResult - Executes query and returns all results
  • querySingle(sql: string, ...args): object|null - Returns first row only
  • queryScalar(sql: string, ...args): any - Returns single value
  • execute(sql: string, ...args): void - Executes statement without returning results
Data Management
  • createTable(name: string, columns: object): void - Creates table with specified columns
  • insertData(tableName: string, rows: array): void - Bulk insert data
  • loadCSV(tableName: string, filePath: string, options: object): void - Load CSV file
QueryResult Object
{
    rows: [],        // Array of result rows as objects
    columns: [],     // Array of column names
    count: 0         // Number of rows returned
}
Field Name Conventions

Following k6's Go-to-JavaScript bridge conventions:

  • Go method names (PascalCase) become camelCase in JavaScript: QuerySinglequerySingle
  • Go struct fields become snake_case in JavaScript: RowCountrow_count

Performance Considerations

Best Practices
  1. Connection Management: Always close connections in finally blocks
  2. Batch Operations: Use insertData() for bulk inserts instead of individual INSERT statements
  3. In-Memory vs Persistent: Use in-memory databases for temporary test data, persistent for shared data
  4. Query Optimization: Leverage DuckDB's columnar engine for analytical queries
  5. Resource Cleanup: Use setup/teardown functions for database initialization and cleanup
Memory Usage

DuckDB runs in-process, so all data lives in your k6 process memory:

  • In-memory databases: All data stored in RAM
  • Persistent databases: Uses memory for caching and processing
  • Monitor memory usage with large datasets

Example Use Cases

1. Testing Data Pipeline Performance
export default function() {
    const db = new duckdb.DuckDB();
    db.open("");
    
    // Simulate ETL process
    const start = Date.now();
    db.loadCSV("raw_data", "input.csv", {header: true});
    
    db.execute(`
        CREATE TABLE processed_data AS
        SELECT 
            user_id,
            DATE_TRUNC('day', timestamp) as date,
            SUM(value) as daily_total
        FROM raw_data
        GROUP BY user_id, DATE_TRUNC('day', timestamp)
    `);
    
    const processingTime = Date.now() - start;
    console.log(`ETL completed in ${processingTime}ms`);
}
2. Database Load Testing
export const options = {
    scenarios: {
        read_heavy: {
            executor: 'constant-vus',
            vus: 50,
            duration: '5m',
        },
    },
};

export default function() {
    const db = new duckdb.DuckDB();
    db.open("shared_test.db");
    
    // Simulate read-heavy workload
    const userId = Math.floor(Math.random() * 10000) + 1;
    const orders = db.query(`
        SELECT * FROM orders 
        WHERE user_id = ? AND order_date >= CURRENT_DATE - INTERVAL '30 days'
        ORDER BY order_date DESC
    `, [userId]);
    
    check(orders, {
        'Query executed successfully': (r) => r.count >= 0,
        'Response time acceptable': () => true,
    });
    
    db.close();
}
3. Analytics Workload Testing
export function setup() {
    const db = new duckdb.DuckDB();
    db.open("analytics_test.db");
    
    // Generate test data
    db.execute(`
        CREATE TABLE events AS
        SELECT 
            (random() * 1000000)::INTEGER as user_id,
            ['login', 'purchase', 'view', 'click'][1 + (random() * 3)::INTEGER] as event_type,
            random() * 100 as value,
            CURRENT_TIMESTAMP - INTERVAL (random() * 365) DAY as timestamp
        FROM generate_series(1, 1000000)
    `);
    
    db.close();
    return { dbPath: "analytics_test.db" };
}

export default function(data) {
    const db = new duckdb.DuckDB();
    db.open(data.dbPath);
    
    // Complex analytical query
    const analytics = db.query(`
        WITH daily_stats AS (
            SELECT 
                DATE_TRUNC('day', timestamp) as date,
                event_type,
                COUNT(*) as event_count,
                SUM(value) as total_value
            FROM events
            WHERE timestamp >= CURRENT_DATE - INTERVAL '7 days'
            GROUP BY DATE_TRUNC('day', timestamp), event_type
        ),
        user_segments AS (
            SELECT 
                user_id,
                COUNT(DISTINCT event_type) as event_types,
                SUM(value) as lifetime_value,
                CASE 
                    WHEN SUM(value) > 500 THEN 'high_value'
                    WHEN SUM(value) > 100 THEN 'medium_value'
                    ELSE 'low_value'
                END as segment
            FROM events
            GROUP BY user_id
        )
        SELECT 
            ds.date,
            ds.event_type,
            ds.event_count,
            us.segment,
            COUNT(DISTINCT us.user_id) as unique_users
        FROM daily_stats ds
        JOIN events e ON DATE_TRUNC('day', e.timestamp) = ds.date 
                     AND e.event_type = ds.event_type
        JOIN user_segments us ON e.user_id = us.user_id
        GROUP BY ds.date, ds.event_type, ds.event_count, us.segment
        ORDER BY ds.date, ds.event_type
    `);
    
    console.log(`Processed ${analytics.count} analytical results`);
    db.close();
}

Error Handling

Always wrap database operations in try-catch blocks:

export default function() {
    const db = new duckdb.DuckDB();
    
    try {
        db.open("");
        
        // Your database operations here
        const result = db.query("SELECT * FROM non_existent_table");
        
    } catch (error) {
        console.error('Database error:', error.message);
        
        // Log error for k6 metrics
        check(false, {
            'Database operation successful': () => false,
        });
        
    } finally {
        // Always close connection
        try {
            db.close();
        } catch (closeError) {
            console.error('Error closing database:', closeError);
        }
    }
}

Troubleshooting

Common Issues
Build Errors

Error: undefined: conn

# Install build tools
sudo apt-get update && sudo apt-get install build-essential

# Enable CGO
export CGO_ENABLED=1
xk6 build --with github.com/k6-extensions-doku/xk6-duckdb=.

Windows Build Issues

# Install MSYS2 and GCC
pacman -S mingw-w64-ucrt-x86_64-gcc

# Add to PATH
$env:PATH = "C:\msys64\ucrt64\bin:$env:PATH"
Runtime Errors

Database Connection Issues

  • Ensure proper file permissions for persistent databases
  • Check disk space for large datasets
  • Verify database file paths are accessible

Memory Issues

  • Monitor memory usage with large in-memory databases
  • Consider using persistent storage for large datasets
  • Implement proper connection cleanup
Performance Tuning
DuckDB Configuration
// Set DuckDB configuration options
db.open("test.db?threads=4&memory_limit=2GB");

// Or configure after opening
db.execute("SET memory_limit='2GB'");
db.execute("SET threads=4");
Query Optimization
// Use EXPLAIN to understand query plans
const plan = db.query("EXPLAIN SELECT * FROM large_table WHERE condition = 'value'");
console.log('Query plan:', JSON.stringify(plan.rows, null, 2));

// Create indexes for better performance
db.execute("CREATE INDEX idx_user_id ON orders(user_id)");
db.execute("CREATE INDEX idx_timestamp ON events(timestamp)");

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

  • k6 - Modern load testing tool
  • go-duckdb - DuckDB driver for Go
  • DuckDB - Analytical SQL database engine
  • xk6 - Extension system for k6

Support

Documentation

Overview

Package xk6_duckdb provides comprehensive DuckDB database functionality for k6 tests This extension covers the full go-duckdb API including Connector, Appender, Arrow, and Profiling

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DuckDB

type DuckDB struct{}

DuckDB represents the main module struct

func New

func New() *DuckDB

New creates a new instance of the DuckDB module

func (*DuckDB) XConnector

func (d *DuckDB) XConnector(call sobek.ConstructorCall, rt *sobek.Runtime) *sobek.Object

XConnector creates a JavaScript constructor for DuckDB connectors

func (*DuckDB) XDuckDB

func (d *DuckDB) XDuckDB(call sobek.ConstructorCall, rt *sobek.Runtime) *sobek.Object

XDuckDB creates a JavaScript constructor for DuckDB connections

type DuckDBAppender

type DuckDBAppender struct {
	// contains filtered or unexported fields
}

DuckDBAppender wraps the DuckDB appender for high-performance bulk loading

func (*DuckDBAppender) AppendRow

func (a *DuckDBAppender) AppendRow(values []driver.Value) error

AppendRow appends a single row to the appender Usage: appender.appendRow([1, "John", "john@example.com"])

func (*DuckDBAppender) AppendRows

func (a *DuckDBAppender) AppendRows(rows [][]driver.Value) error

AppendRows appends multiple rows efficiently Usage: appender.appendRows([[1, "John"], [2, "Jane"], [3, "Bob"]])

func (*DuckDBAppender) Close

func (a *DuckDBAppender) Close() error

Close closes the appender

func (*DuckDBAppender) Flush

func (a *DuckDBAppender) Flush() error

Flush flushes the appender buffer

type DuckDBConnection

type DuckDBConnection struct {
	// contains filtered or unexported fields
}

DuckDBConnection wraps a DuckDB database connection

func (*DuckDBConnection) BeginTransaction

func (conn *DuckDBConnection) BeginTransaction() (*DuckDBTransaction, error)

BeginTransaction starts a new transaction

func (*DuckDBConnection) Close

func (conn *DuckDBConnection) Close() error

Close closes the database connection

func (*DuckDBConnection) CreateAppender

func (conn *DuckDBConnection) CreateAppender(schema, table string) (*DuckDBAppender, error)

CreateAppender creates a new DuckDB appender for bulk data loading Usage: const appender = db.createAppender("", "table_name")

func (*DuckDBConnection) CreateTable

func (conn *DuckDBConnection) CreateTable(tableName string, columns map[string]string) error

CreateTable creates a table with the specified structure

func (*DuckDBConnection) DisableProfiling

func (conn *DuckDBConnection) DisableProfiling() error

DisableProfiling disables query profiling

func (*DuckDBConnection) EnableProfiling

func (conn *DuckDBConnection) EnableProfiling(mode, output string) error

EnableProfiling enables query profiling on the connection Usage: db.enableProfiling("detailed", "no_output")

func (*DuckDBConnection) Execute

func (conn *DuckDBConnection) Execute(query string, args ...interface{}) error

Execute runs a SQL statement without returning results

func (*DuckDBConnection) GetConfig

func (conn *DuckDBConnection) GetConfig(option string) (string, error)

GetConfig gets a DuckDB configuration option

func (*DuckDBConnection) GetConnection

func (conn *DuckDBConnection) GetConnection() error

GetConnection returns a dedicated connection from the pool

func (*DuckDBConnection) GetProfilingInfo

func (conn *DuckDBConnection) GetProfilingInfo() (*duckdb.ProfilingInfo, error)

GetProfilingInfo retrieves profiling information for the last query

func (*DuckDBConnection) InsertData

func (conn *DuckDBConnection) InsertData(tableName string, data []map[string]interface{}) error

InsertData inserts data into a table (uses transactions for better performance)

func (*DuckDBConnection) InstallExtension

func (conn *DuckDBConnection) InstallExtension(extension string) error

InstallExtension installs a DuckDB extension

func (*DuckDBConnection) LoadCSV

func (conn *DuckDBConnection) LoadCSV(tableName, filePath string, options map[string]interface{}) error

LoadCSV loads data from a CSV file using DuckDB's native CSV loader

func (*DuckDBConnection) LoadExtension

func (conn *DuckDBConnection) LoadExtension(extension string) error

LoadExtension loads a DuckDB extension Usage: db.loadExtension("json") or db.loadExtension("/path/to/extension.duckdb_extension")

func (*DuckDBConnection) LoadJSON

func (conn *DuckDBConnection) LoadJSON(tableName, filePath string, options map[string]interface{}) error

LoadJSON loads data from a JSON file

func (*DuckDBConnection) LoadParquet

func (conn *DuckDBConnection) LoadParquet(tableName, filePath string) error

LoadParquet loads data from a Parquet file

func (*DuckDBConnection) Open

func (conn *DuckDBConnection) Open(dsn string, config ...map[string]interface{}) error

Open opens a DuckDB database connection with optional configuration Usage: db.open("/path/to/database.db", {threads: 4, memory_limit: "2GB"})

func (*DuckDBConnection) Prepare

func (conn *DuckDBConnection) Prepare(query string) (*DuckDBStatement, error)

Prepare creates a prepared statement

func (*DuckDBConnection) Query

func (conn *DuckDBConnection) Query(query string, args ...interface{}) (*QueryResult, error)

Query executes a SQL query and returns all results

func (*DuckDBConnection) QueryScalar

func (conn *DuckDBConnection) QueryScalar(query string, args ...interface{}) (interface{}, error)

QueryScalar executes a query and returns a single scalar value

func (*DuckDBConnection) QuerySingle

func (conn *DuckDBConnection) QuerySingle(query string, args ...interface{}) (map[string]interface{}, error)

QuerySingle executes a query and returns only the first row

func (*DuckDBConnection) SetConfig

func (conn *DuckDBConnection) SetConfig(option, value string) error

SetConfig sets a DuckDB configuration option Usage: db.setConfig("memory_limit", "4GB")

func (*DuckDBConnection) SetPragma

func (conn *DuckDBConnection) SetPragma(pragma, value string) error

SetPragma sets a DuckDB pragma Usage: db.setPragma("threads", "8")

type DuckDBConnector

type DuckDBConnector struct {
	// contains filtered or unexported fields
}

DuckDBConnector wraps a DuckDB connector for advanced connection management

func (*DuckDBConnector) Close

func (c *DuckDBConnector) Close() error

Close closes the connector

func (*DuckDBConnector) Connect

func (c *DuckDBConnector) Connect() (*DuckDBConnection, error)

Connect creates a new connection from the connector

func (*DuckDBConnector) Create

func (c *DuckDBConnector) Create(dsn string, initQueries []string) error

NewConnector creates a new DuckDB connector with initialization callback Usage: const connector = new duckdb.Connector();

connector.create("test.db", ["SET memory_limit='2GB'", "SET threads=4"])

type DuckDBStatement

type DuckDBStatement struct {
	// contains filtered or unexported fields
}

DuckDBStatement wraps a prepared statement

func (*DuckDBStatement) Close

func (stmt *DuckDBStatement) Close() error

Close closes the prepared statement

func (*DuckDBStatement) Execute

func (stmt *DuckDBStatement) Execute(args ...interface{}) error

Execute executes the prepared statement

func (*DuckDBStatement) Query

func (stmt *DuckDBStatement) Query(args ...interface{}) (*QueryResult, error)

Query executes the prepared statement and returns results

type DuckDBTransaction

type DuckDBTransaction struct {
	// contains filtered or unexported fields
}

DuckDBTransaction wraps a SQL transaction

func (*DuckDBTransaction) Commit

func (tx *DuckDBTransaction) Commit() error

Commit commits the transaction

func (*DuckDBTransaction) Execute

func (tx *DuckDBTransaction) Execute(query string, args ...interface{}) error

Execute runs a statement within the transaction

func (*DuckDBTransaction) Query

func (tx *DuckDBTransaction) Query(query string, args ...interface{}) (*QueryResult, error)

Query runs a query within the transaction

func (*DuckDBTransaction) Rollback

func (tx *DuckDBTransaction) Rollback() error

Rollback rolls back the transaction

type ProfilingInfo

type ProfilingInfo struct {
	Query            string                 `js:"query"`
	ExecutionTime    string                 `js:"execution_time"`
	PlanningTime     string                 `js:"planning_time"`
	OptimizationTime string                 `js:"optimization_time"`
	Metrics          map[string]interface{} `js:"metrics"`
}

ProfilingInfo contains query profiling information

type QueryResult

type QueryResult struct {
	Rows    []map[string]interface{} `js:"rows"`
	Columns []string                 `js:"columns"`
	Count   int                      `js:"count"`
}

QueryResult represents the result of a database query

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL