Client
Client is a Redis client to interact with a Redis server, sentinel, or cluster. It exposes a promise-based API, which users can interact with in an asynchronous manner.
Though the API intends to be thorough and extensive, it does not expose the whole Redis API. Instead, the intent is to expose Redis for use cases most appropriate to k6.
Usage
Single-node server
You can create a new Client instance that connects to a single Redis server by passing a URL string.
It must be in the format:
redis[s]://[[username][:password]@][host][:port][/db-number]Here’s an example of a URL string that connects to a Redis server running on localhost, on the default port (6379), and using the default database (0):
import redis from 'k6/experimental/redis';
const client = new redis.Client('redis://localhost:6379');A client can also be instantiated using an options object to support more complex use cases, and for more flexibility:
import redis from 'k6/experimental/redis';
const client = new redis.Client({
socket: {
host: 'localhost',
port: 6379,
},
username: 'someusername',
password: 'somepassword',
});TLS
You can configure a TLS connection in a couple of ways.
If the server has a certificate signed by a public Certificate Authority, you can use the rediss URL scheme:
import redis from 'k6/experimental/redis';
const client = new redis.Client('rediss://example.com');Otherwise, you can supply your own self-signed certificate in PEM format using the socket.tls object:
import redis from 'k6/experimental/redis';
const client = new redis.Client({
socket: {
host: 'localhost',
port: 6379,
tls: {
ca: [open('ca.crt')],
},
},
});Note that for self-signed certificates, k6’s
insecureSkipTLSVerify option must be enabled (set to true).
TLS client authentication (mTLS)
You can also enable mTLS by setting two additional properties in the socket.tls object:
import redis from 'k6/experimental/redis';
const client = new redis.Client({
socket: {
host: 'localhost',
port: 6379,
tls: {
ca: [open('ca.crt')],
cert: open('client.crt'), // client certificate
key: open('client.key'), // client private key
},
},
});Cluster client
You can connect to a cluster of Redis servers by using the cluster configuration property, and passing 2 or more node URLs:
import redis from 'k6/experimental/redis';
const client = new redis.Client({
cluster: {
// Cluster options
maxRedirects: 3,
readOnly: true,
routeByLatency: true,
routeRandomly: true,
nodes: ['redis://host1:6379', 'redis://host2:6379'],
},
});Or the same as above, but passing socket objects to the nodes array instead of URLs:
import redis from 'k6/experimental/redis';
const client = new redis.Client({
cluster: {
nodes: [
{
socket: {
host: 'host1',
port: 6379,
},
},
{
socket: {
host: 'host2',
port: 6379,
},
},
],
},
});Sentinel client
A Redis Sentinel provides high availability features, as an alternative to a Redis cluster.
You can connect to a sentinel instance by setting additional
options in the object passed to the Client constructor:
import redis from 'k6/experimental/redis';
const client = new redis.Client({
username: 'someusername',
password: 'somepassword',
socket: {
host: 'localhost',
port: 6379,
},
// Sentinel options
masterName: 'masterhost',
sentinelUsername: 'sentineluser',
sentinelPassword: 'sentinelpass',
});Real world example
import { check } from 'k6';
import http from 'k6/http';
import redis from 'k6/experimental/redis';
import exec from 'k6/execution';
import { textSummary } from 'https://jslib.k6.io/k6-summary/0.0.2/index.js';
export const options = {
scenarios: {
redisPerformance: {
executor: 'shared-iterations',
vus: 10,
iterations: 200,
exec: 'measureRedisPerformance',
},
usingRedisData: {
executor: 'shared-iterations',
vus: 10,
iterations: 200,
exec: 'measureUsingRedisData',
},
},
};
// Instantiate a new redis client
const redisClient = new redis.Client(`redis://localhost:6379`);
// Prepare an array of rating ids for later use
// in the context of the measureUsingRedisData function.
const ratingIDs = new Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
export async function measureRedisPerformance() {
// VUs are executed in a parallel fashion,
// thus, to ensure that parallel VUs are not
// modifying the same key at the same time,
// we use keys indexed by the VU id.
const key = `foo-${exec.vu.idInTest}`;
await redisClient.set(key, 1);
await redisClient.incrBy(key, 10);
const value = await redisClient.get(key);
if (value !== '11') {
throw new Error('foo should have been incremented to 11');
}
await redisClient.del(key);
if ((await redisClient.exists(key)) !== 0) {
throw new Error('foo should have been deleted');
}
}
export async function setup() {
await redisClient.sadd('rating_ids', ...ratingIDs);
}
export async function measureUsingRedisData() {
// Pick a random rating id from the dedicated redis set,
// we have filled in setup().
const randomID = await redisClient.srandmember('rating_ids');
const url = `https://quickpizza.grafana.com/api/ratings/${randomID}`;
const res = await http.asyncRequest('GET', url, {
headers: { Authorization: 'token abcdef0123456789' },
});
check(res, { 'status is 200': (r) => r.status === 200 });
await redisClient.hincrby('k6_rating_fetched', url, 1);
}
export async function teardown() {
await redisClient.del('rating_ids');
}
export function handleSummary(data) {
redisClient
.hgetall('k6_rating_fetched')
.then((fetched) => Object.assign(data, { k6_rating_fetched: fetched }))
.then((data) => redisClient.set(`k6_report_${Date.now()}`, JSON.stringify(data)))
.then(() => redisClient.del('k6_rating_fetched'));
return {
stdout: textSummary(data, { indent: ' ', enableColors: true }),
};
}

