Skip to content

Instantly share code, notes, and snippets.

@Brayyy
Last active February 22, 2022 17:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Brayyy/128394ea44e9748057de310112ef3540 to your computer and use it in GitHub Desktop.
Save Brayyy/128394ea44e9748057de310112ef3540 to your computer and use it in GitHub Desktop.
Blog 2022.02.21 Redis Lua tests
// THIS SCRIPT WILL CALL REDIS FLUSHDB, USE AT YOUR OWN RISK, DO NOT RUN ON AN ACTIVE REDIS DB
// 2022-02-21 Bray Almini
const redis = require("redis");
const { promisify } = require("util");
const crypto = require("crypto");
const REDIS_URI = process.env.REDIS_URI;
const REDIS_PROMISE_METHODS = ["ping", "evalsha", "script", "flushdb", "expire", "set", "setex", "hset", "setbit"]
const wait = ms => new Promise(resolve => { setTimeout(resolve, ms) });
const luaScripts = {
luaSet: `return redis.call("SET", KEYS[1], ARGV[1])`,
luaSetEx: `return redis.call("SETEX", KEYS[1], 30, ARGV[1])`,
luaHSet: `return redis.call("HSET", KEYS[1], unpack(ARGV))`,
luaHSetEx: `
local newCount = redis.call("HSET", KEYS[1], unpack(ARGV))
if tonumber(newCount) > 0 then
redis.call("EXPIRE", KEYS[1], 30)
end
return newCount`,
luaSetBit: `return redis.call("SETBIT", KEYS[1], ARGV[1], ARGV[2])`,
luaSetBitEx: `
local prev = redis.call("SETBIT", KEYS[1], ARGV[1], ARGV[2])
if prev == 0 then
redis.call("EXPIRE", KEYS[1], 30)
end
return prev`,
};
const connectRedis = async () => {
const r = redis.createClient(REDIS_URI);
// Promisify Redis 3.x methods
REDIS_PROMISE_METHODS.forEach(methodName => {
r[methodName] = promisify(r[methodName]).bind(r);
});
// Load Lua scripts as new methods on Redis client
for (luaName in luaScripts) {
const digest = crypto.createHash("sha1").update(luaScripts[luaName]).digest("hex");
if (await r.script("exists", digest)[0] !== 1) r.script("load", luaScripts[luaName]);
r[luaName] = (...args) => r.evalsha(digest, ...args);
}
return r;
}
const prepRedis = async r => {
await r.flushdb();
await wait(5000);
await r.ping();
};
async function benchmark() {
const r = await connectRedis();
const times = 25000;
const pad = times;
const listToSet = ["field1", "val1", "field2", "val2", "field3", "val3"];
console.log("\n--- SET");
await prepRedis(r);
console.time("await SET");
for (let i = 0; i < times; i++) {
await r.set(`test_${pad + i}`, "bar");
}
console.timeEnd("await SET");
await prepRedis(r);
console.time("await luaSet");
for (let i = 0; i < times; i++) {
await r.luaSet(1, `test_${pad + i}`, ...listToSet);
}
console.timeEnd("await luaSet");
console.log("\n--- SETEX");
await prepRedis(r);
console.time("await SETEX");
for (let i = 0; i < times; i++) {
await r.setex(`test_${pad + i}`, 30, "bar");
}
console.timeEnd("await SETEX");
await prepRedis(r);
console.time("await luaSetEx");
for (let i = 0; i < times; i++) {
await r.luaSetEx(1, `test_${pad + i}`, "bar");
}
console.timeEnd("await luaSetEx");
console.log("\n--- HSET");
await prepRedis(r);
console.time("await HSET");
for (let i = 0; i < times; i++) {
await r.hset(`test_${pad + i}`, ...listToSet);
}
console.timeEnd("await HSET");
await prepRedis(r);
console.time("await luaHSet");
for (let i = 0; i < times; i++) {
await r.luaHSet(1, `test_${pad + i}`, ...listToSet);
}
console.timeEnd("await luaHSet");
console.log("\n--- HSET + EXPIRE");
await prepRedis(r);
console.time("await HSET, blind EXPIRE");
for (let i = 0; i < times; i++) {
const resp = await r.hset(`test_${pad + i}`, ...listToSet);
if (resp > 0) r.expire(`test_${pad + i}`, 30);
}
console.timeEnd("await HSET, blind EXPIRE");
await prepRedis(r);
console.time("await HSET+EXPIRE");
for (let i = 0; i < times; i++) {
const resp = await r.hset(`test_${pad + i}`, ...listToSet);
if (resp > 0) await r.expire(`test_${pad + i}`, 30);
}
console.timeEnd("await HSET+EXPIRE");
await prepRedis(r);
console.time("await luaHSetEx");
for (let i = 0; i < times; i++) {
await r.luaHSetEx(1, `test_${pad + i}`, ...listToSet);
}
console.timeEnd("await luaHSetEx");
// SETBIT
console.log("\n--- SETBIT");
await prepRedis(r);
console.time("await SETBIT");
for (let i = 0; i < times; i++) {
await r.setbit(`test_${pad + i}`, 12, 1);
}
console.timeEnd("await SETBIT");
await prepRedis(r);
console.time("await luaSetBit");
for (let i = 0; i < times; i++) {
await r.luaSetBit(1, `test_${pad + i}`, 12, 1);
}
console.timeEnd("await luaSetBit");
console.log("\n--- SETBIT + EXPIRE");
await prepRedis(r);
console.time("await SETBIT, blind EXPIRE");
for (let i = 0; i < times; i++) {
const resp = await r.setbit(`test_${pad + i}`, 12, 1);
if (resp === 0) r.expire(`test_${pad + i}`, 30);
}
console.timeEnd("await SETBIT, blind EXPIRE");
await prepRedis(r);
console.time("await SETBIT+EXPIRE");
for (let i = 0; i < times; i++) {
const resp = await r.setbit(`test_${pad + i}`, 12, 1);
if (resp === 0) await r.expire(`test_${pad + i}`, 30);
}
console.timeEnd("await SETBIT+EXPIRE");
await prepRedis(r);
console.time("await luaSetBitEx");
for (let i = 0; i < times; i++) {
await r.luaSetBitEx(1, `test_${pad + i}`, 12, 1);
}
console.timeEnd("await luaSetBitEx");
// Clear, close, and exit
await prepRedis(r);
r.quit();
}
async function loadTest(run) {
const r = await connectRedis();
await prepRedis(r);
const times = 25000;
const listToSet = ["field1", "val1", "field2", "val2", "field3", "val3"];
let k = 0;
if (run === "A") {
while (true) {
console.time("await HSET, blind EXPIRE");
for (let i = 0; i < times; i++) {
const key = `test_${k++}`;
const resp = await r.hset(key, ...listToSet);
if (resp > 0) r.expire(key, 30);
}
console.timeEnd("await HSET, blind EXPIRE");
await wait(1000);
}
}
if (run === "B") {
while (true) {
console.time("await luaHSetEx");
for (let i = 0; i < times; i++) {
const key = `test_${k++}`;
const resp = await r.luaHSetEx(1, k, ...listToSet);
}
console.timeEnd("await luaHSetEx");
await wait(1000);
}
}
if (run === "C") {
while (true) {
console.time("await SETEX");
for (let i = 0; i < times; i++) {
const key = `test_${k++}`;
await r.setex(key, 30, "bar");
}
console.timeEnd("await SETEX");
await wait(1000);
}
}
if (run === "D") {
while (true) {
console.time("await luaSetEx");
for (let i = 0; i < times; i++) {
const key = `test_${k++}`;
await r.luaSetEx(1, key, "bar");
}
console.timeEnd("await luaSetEx");
await wait(1000);
}
}
}
// Uncomment one by one to run tests
// loadTest() must be Ctrl-C'd to stop
// benchmark();
// loadTest("A");
// loadTest("B");
// loadTest("C");
// loadTest("D");
-- Set EXPIRE on hash after HSET if new fields added
-- OPs: usually 2, occasionally 1 (when hash and fields are reused)
local expire = table.remove(ARGV, 1)
local newCount = redis.call("HSET", KEYS[1], unpack(ARGV))
if tonumber(newCount) > 0 then
redis.call("EXPIRE", KEYS[1], expire)
end
return newCount
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment