All files are assets for an article at:
Last active
February 22, 2022 17:36
-
-
Save Brayyy/128394ea44e9748057de310112ef3540 to your computer and use it in GitHub Desktop.
Blog 2022.02.21 Redis Lua tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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