Skip to content

Repository files navigation

CouchBase k6 extension

K6 extension to perform tests on couchbase.

Currently Supported Commands

  • Supports inserting a document.
  • Supports Batch insertion.
  • Supports findOne (Fetch by primary key)
  • Supports findMany (Batch fetch by primary keys)
  • Supports deleting a document by id
  • Supports upserts
  • Testing query performance
  • Prepared statement query performance

Build Instructions

For Development

git clone git@github.com:thotasrinath/xk6-couchbase.git
cd xk6-couchbase
make build

This produces a ./xk6-couchbase binary from the local source. No global xk6 is required — the Makefile invokes it via go run. See the Development section for the full set of make targets (tests, lint, example validation).

For Use

Install xk6, then build a k6 binary with the published extension:

go install go.k6.io/xk6/cmd/xk6@latest
xk6 build --with github.com/thotasrinath/xk6-couchbase@latest

Development

A Makefile wraps the common tasks (run make with no target to list them):

make build      # build a k6 binary with this extension from local source
make test       # run unit tests
make check      # gofmt + go vet + test (pre-commit gate)
make lint       # golangci-lint (see https://golangci-lint.run to install)
make validate   # build + ensure local Couchbase + run every example end-to-end

Unit tests cover the module lifecycle, the shared-connection singleton, and the per-VU connection behavior. They use k6's modulestest runtime and do not require a live Couchbase cluster (gocb.Connect is lazy), so make test runs anywhere.

make validate runs the example scripts against a local Couchbase (starting a Docker container if one isn't already reachable). See examples/README.md for details.

Examples

Document Insertion Test

import xk6_couchbase from 'k6/x/couchbase';


const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default () => {

    let doc = {
        correlationId: `test--couchbase`,
        title: 'Perf test experiment',
        url: 'example.com',
        locale: 'en',
        time: `${new Date(Date.now()).toISOString()}`
    };
    client.insert("test", "_default", "_default", makeId(15), doc);
}

function makeId(length) {
    let result = '';
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    const charactersLength = characters.length;
    let counter = 0;
    while (counter < length) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
        counter += 1;
    }
    return result;
}
Below is the example commands to test the script
# Update examples/test-insert.js with cluster credentials and bucket name
 ./xk6-couchbase run --vus 10 --duration 30s examples/test-insert.js

Batch Insert Documents

import xk6_couchbase from 'k6/x/couchbase';


const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');

const batchsize = 50;

export default () => {

    var docobjs = {}

    for (var i = 0; i < batchsize; i++) {
        docobjs[makeId(15)] = getRecord();
    }

    client.insertBatch("test", "_default", "_default", docobjs);
}

function getRecord() {
    return {
        correlationId: `test--couchbase`,
        title: 'Perf test experiment',
        url: 'example.com',
        locale: 'en',
        time: `${new Date(Date.now()).toISOString()}`
    };


}

function makeId(length) {
    let result = '';
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    const charactersLength = characters.length;
    let counter = 0;
    while (counter < length) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
        counter += 1;
    }
    return result;
}

Exists test

import xk6_couchbase from 'k6/x/couchbase';


const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default () => {
    // syntax :: client.exists("<db>", "<scope>", "<keyspace>", "<docId>");
    var res = client.exists("test", "_default", "_default", "002wPJwiJArcUpz");
    console.log(res);
}

FindOne test

import xk6_couchbase from 'k6/x/couchbase';


const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default () => {
    // syntax :: client.findOne("<db>", "<scope>", "<keyspace>", "<docId>");
    var res = client.findOne("test", "_default", "_default", "002wPJwiJArcUpz");
    console.log(res);
}

FindMany (Batch Get) test

import xk6_couchbase from 'k6/x/couchbase';


const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default () => {
    // syntax :: client.findMany("<db>", "<scope>", "<keyspace>", ["<docId1>", "<docId2>", ...]);
    // Returns a map of docId -> document for keys that exist. Missing keys are omitted.
    var res = client.findMany("test", "_default", "_default", ["key1", "key2", "key3"]);
    console.log(JSON.stringify(res));
}

Document Deletion Test

import xk6_couchbase from 'k6/x/couchbase';


const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default () => {

    client.remove("test", "_default", "_default", "002wPJwiJArcUpz", doc);
}

Query test

import xk6_couchbase from 'k6/x/couchbase';


const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default () => {
    var res = client.find("select * from test._default._default  use keys \"00096zszpZaT47X\"");

    //console.log(res);

}

Query using a prepared statement

import xk6_couchbase from 'k6/x/couchbase';


const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default () => {
    var res = client.findByPreparedStmt("select * from test._default._default  use keys \"00096zszpZaT47X\"");

    //console.log(res);

}

Upsert test

Initial data load is expected. Data with sequential Id's could be generated with below script. File - examples/load-sequential-data.js

import xk6_couchbase from 'k6/x/couchbase';
const jobConfig = {
    vus: 10,
    count: 10000
}
export const options = {
    scenarios: {
        contacts: {
            executor: 'per-vu-iterations',
            vus: jobConfig.vus,
            iterations: jobConfig.count
        },
    },
};
const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default function () {
    let doc = {
        correlationId: `test--couchbase`,
        title: 'Perf test experiment',
        url: 'example.com',
        locale: 'en',
        time: `${new Date(Date.now()).toISOString()}`
    };

    let docId = ((__VU - 1) * jobConfig.count) + __ITER;
    client.insert("test", "_default", "_default", String(docId), doc);
}

Upsert test script to generate random id in range and excute Upsert. File - examples/test-upsert.js

import xk6_couchbase from 'k6/x/couchbase';
import {randomIntBetween} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';

const client = xk6_couchbase.newClient('localhost', '<username>', '<password>');
export default () => {

    let doc = {
        correlationId: `test--couchbase`,
        title: 'Perf test experiment',
        url: 'example.com',
        locale: 'en',
        time: `${new Date(Date.now()).toISOString()}`,
        randomNo: randomIntBetween(0,1000000)
    };
    client.upsert("test", "_default", "_default", String(randomIntBetween(0, 100000)), doc);
}

Client with options

Default NewClient is a good start to get familiar with the extension with little wiring and get going. For refined flexibility of connection management during the tests, following additional interfaces are provided:

  • NewClientWithSharedConnection: Shares a single connection across all the VUs and maximizes the QPS without affecting client resources.
  • NewClientPerVU: Creates new client connection for each VU. Useful for testing max number of connections that the cluster can handle.

In addition to controlling number of connections, both the functions provide:

  • Ability to pre-warm the buckets as part of initialization.
  • Ability to set timeout duration for bucket readiness (WaitUntilReady timeout).
  • Ability to control the connection buffer size. By default couchbase client uses 20MB buffer size which limits the number of concurrent connections that can be created by a single client instance.

Example for wiring client with shared connection across VUs

import xk6_couchbase from 'k6/x/couchbase';


const dbConfig = { hostname: 'localhost', username: '<username>', password: '<password>' };
const bucketsToPreWarm = ['test'];
const client = xk6_couchbase.newClientWithSharedConnection(dbConfig, bucketsToPreWarm, "5s");
export default () => {
    // syntax :: client.findOne("<db>", "<scope>", "<keyspace>", "<docId>");
    var res = client.findOne("test", "_default", "_default", "002wPJwiJArcUpz");
    console.log(res);
}

Example for wiring client with unique connections per VUs

import xk6_couchbase from 'k6/x/couchbase';


const dbConfig = { hostname: 'localhost', username: '<username>', password: '<password>' };
const bucketsToPreWarm = ['test'];
const client = xk6_couchbase.newClientPerVU(dbConfig, bucketsToPreWarm, "5s");
export default () => {
    // syntax :: client.findOne("<db>", "<scope>", "<keyspace>", "<docId>");
    var res = client.findOne("test", "_default", "_default", "002wPJwiJArcUpz");
    console.log(res);
}

About

K6 extension to test Couchbase DB

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages