├── docker-compose.yml ├── .editorconfig ├── .gitignore ├── package.json ├── LICENSE ├── benchmarks ├── keys.js ├── get.js ├── incr.js ├── hgetall.js ├── hmset.js └── set.js ├── README.md └── benchmark.js /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | redis: 4 | image: "redis:6.2-alpine" 5 | ports: 6 | - "6379:6379" 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | end_of_line = crlf 7 | charset = utf-8-bom 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.yml] 12 | indent_style = space 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | dump.rdb 40 | 41 | package-lock.json 42 | yarn.lock 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node_redis-vs-ioredis", 3 | "version": "0.0.1", 4 | "description": "A simple benchmark for node_redis and ioredis.", 5 | "main": "benchmark.js", 6 | "scripts": { 7 | "benchmark": "node benchmark.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/poppinlp/node_redis-vs-ioredis.git" 12 | }, 13 | "keywords": [ 14 | "nodeRedis", 15 | "ioredis", 16 | "benchmark" 17 | ], 18 | "author": "PoppinL", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/poppinlp/node_redis-vs-ioredis/issues" 22 | }, 23 | "homepage": "https://github.com/poppinlp/node_redis-vs-ioredis#readme", 24 | "dependencies": { 25 | "ioredis": "^4.28.3", 26 | "redis": "4.0.3" 27 | }, 28 | "engines": { 29 | "node": ">= 16" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 PoppinL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /benchmarks/keys.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ nodeRedis, ioredis, type }) => { 2 | const NODE_REDIS_KEY = type; 3 | const IOREDIS_KEY = type; 4 | 5 | return [ 6 | { 7 | name: "node_redis keys", 8 | obj: "node_redis", 9 | loop: () => nodeRedis.keys(NODE_REDIS_KEY), 10 | }, 11 | { 12 | name: "node_redis keys with multi", 13 | obj: "node_redis", 14 | beforeLoop: (ctx) => (ctx.multi = nodeRedis.multi()), 15 | loop: (ctx) => ctx.multi.keys(NODE_REDIS_KEY), 16 | afterLoop: (ctx) => ctx.multi.exec(), 17 | }, 18 | { 19 | name: "node_redis keys with batch", 20 | obj: "node_redis", 21 | beforeLoop: (ctx) => (ctx.batch = nodeRedis.multi()), 22 | loop: (ctx) => ctx.batch.keys(NODE_REDIS_KEY), 23 | afterLoop: (ctx) => ctx.batch.execAsPipeline(), 24 | }, 25 | { 26 | name: "ioredis keys", 27 | obj: "ioredis", 28 | loop: () => ioredis.keys(IOREDIS_KEY), 29 | }, 30 | { 31 | name: "ioredis keys with multi", 32 | obj: "ioredis", 33 | beforeLoop: (ctx) => (ctx.multi = ioredis.multi()), 34 | loop: (ctx) => ctx.multi.keys(IOREDIS_KEY), 35 | afterLoop: (ctx) => ctx.multi.exec(), 36 | }, 37 | { 38 | name: "ioredis keys with pipeline", 39 | obj: "ioredis", 40 | beforeLoop: (ctx) => (ctx.pipeline = ioredis.pipeline()), 41 | loop: (ctx) => ctx.pipeline.keys(IOREDIS_KEY), 42 | afterLoop: (ctx) => ctx.pipeline.exec(), 43 | }, 44 | ]; 45 | }; 46 | -------------------------------------------------------------------------------- /benchmarks/get.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ nodeRedis, ioredis, type }) => { 2 | const NODE_REDIS_KEY = `node_redis:${type}`; 3 | const IOREDIS_KEY = `ioredis:${type}`; 4 | 5 | return [ 6 | { 7 | name: "node_redis get", 8 | obj: "node_redis", 9 | loop: () => nodeRedis.get(NODE_REDIS_KEY), 10 | }, 11 | { 12 | name: "node_redis get with multi", 13 | obj: "node_redis", 14 | beforeLoop: (ctx) => (ctx.multi = nodeRedis.multi()), 15 | loop: (ctx) => ctx.multi.get(NODE_REDIS_KEY), 16 | afterLoop: (ctx) => ctx.multi.exec(), 17 | }, 18 | { 19 | name: "node_redis get with batch", 20 | obj: "node_redis", 21 | beforeLoop: (ctx) => (ctx.batch = nodeRedis.multi()), 22 | loop: (ctx) => ctx.batch.get(NODE_REDIS_KEY), 23 | afterLoop: (ctx) => ctx.batch.execAsPipeline(), 24 | }, 25 | { 26 | name: "ioredis get", 27 | obj: "ioredis", 28 | loop: () => ioredis.get(IOREDIS_KEY), 29 | }, 30 | { 31 | name: "ioredis get with multi", 32 | obj: "ioredis", 33 | beforeLoop: (ctx) => (ctx.multi = ioredis.multi()), 34 | loop: (ctx) => ctx.multi.get(IOREDIS_KEY), 35 | afterLoop: (ctx) => ctx.multi.exec(), 36 | }, 37 | { 38 | name: "ioredis get with pipeline", 39 | obj: "ioredis", 40 | beforeLoop: (ctx) => (ctx.pipeline = ioredis.pipeline()), 41 | loop: (ctx) => ctx.pipeline.get(IOREDIS_KEY), 42 | afterLoop: (ctx) => ctx.pipeline.exec(), 43 | }, 44 | ]; 45 | }; 46 | -------------------------------------------------------------------------------- /benchmarks/incr.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ nodeRedis, ioredis, type }) => { 2 | const NODE_REDIS_KEY = `node_redis:${type}`; 3 | const IOREDIS_KEY = `ioredis:${type}`; 4 | 5 | return [ 6 | { 7 | name: "node_redis incr", 8 | obj: "node_redis", 9 | loop: () => nodeRedis.incr(NODE_REDIS_KEY), 10 | }, 11 | { 12 | name: "node_redis incr with multi", 13 | obj: "node_redis", 14 | beforeLoop: (ctx) => (ctx.multi = nodeRedis.multi()), 15 | loop: (ctx) => ctx.multi.incr(NODE_REDIS_KEY), 16 | afterLoop: (ctx) => ctx.multi.exec(), 17 | }, 18 | { 19 | name: "node_redis incr with batch", 20 | obj: "node_redis", 21 | beforeLoop: (ctx) => (ctx.batch = nodeRedis.multi()), 22 | loop: (ctx) => ctx.batch.incr(NODE_REDIS_KEY), 23 | afterLoop: (ctx) => ctx.batch.execAsPipeline(), 24 | }, 25 | { 26 | name: "ioredis incr", 27 | obj: "ioredis", 28 | loop: () => ioredis.incr(IOREDIS_KEY), 29 | }, 30 | { 31 | name: "ioredis incr with multi", 32 | obj: "ioredis", 33 | beforeLoop: (ctx) => (ctx.multi = ioredis.multi()), 34 | loop: (ctx) => ctx.multi.incr(IOREDIS_KEY), 35 | afterLoop: (ctx) => ctx.multi.exec(), 36 | }, 37 | { 38 | name: "ioredis incr with pipeline", 39 | obj: "ioredis", 40 | beforeLoop: (ctx) => (ctx.pipeline = ioredis.pipeline()), 41 | loop: (ctx) => ctx.pipeline.incr(IOREDIS_KEY), 42 | afterLoop: (ctx) => ctx.pipeline.exec(), 43 | }, 44 | ]; 45 | }; 46 | -------------------------------------------------------------------------------- /benchmarks/hgetall.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ nodeRedis, ioredis, type }) => { 2 | const NODE_REDIS_KEY = `node_redis:${type}`; 3 | const IOREDIS_KEY = `ioredis:${type}`; 4 | 5 | return [ 6 | { 7 | name: "node_redis hgetall", 8 | obj: "node_redis", 9 | loop: () => nodeRedis.hGetAll(NODE_REDIS_KEY), 10 | }, 11 | { 12 | name: "node_redis hgetall with multi", 13 | obj: "node_redis", 14 | beforeLoop: (ctx) => (ctx.multi = nodeRedis.multi()), 15 | loop: (ctx) => ctx.multi.hGetAll(NODE_REDIS_KEY), 16 | afterLoop: (ctx) => ctx.multi.exec(), 17 | }, 18 | { 19 | name: "node_redis hgetall with batch", 20 | obj: "node_redis", 21 | beforeLoop: (ctx) => (ctx.batch = nodeRedis.multi()), 22 | loop: (ctx) => ctx.batch.hGetAll(NODE_REDIS_KEY), 23 | afterLoop: (ctx) => ctx.batch.execAsPipeline(), 24 | }, 25 | { 26 | name: "ioredis hgetall", 27 | obj: "ioredis", 28 | loop: () => ioredis.hgetall(IOREDIS_KEY), 29 | }, 30 | { 31 | name: "ioredis hgetall with multi", 32 | obj: "ioredis", 33 | beforeLoop: (ctx) => (ctx.multi = ioredis.multi()), 34 | loop: (ctx) => ctx.multi.hgetall(IOREDIS_KEY), 35 | afterLoop: (ctx) => ctx.multi.exec(), 36 | }, 37 | { 38 | name: "ioredis hgetall with pipeline", 39 | obj: "ioredis", 40 | beforeLoop: (ctx) => (ctx.pipeline = ioredis.pipeline()), 41 | loop: (ctx) => ctx.pipeline.hgetall(IOREDIS_KEY), 42 | afterLoop: (ctx) => ctx.pipeline.exec(), 43 | }, 44 | ]; 45 | }; 46 | -------------------------------------------------------------------------------- /benchmarks/hmset.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ TEST_DATA, nodeRedis, ioredis, type }) => { 2 | const NODE_REDIS_KEY = `node_redis:${type}`; 3 | const IOREDIS_KEY = `ioredis:${type}`; 4 | 5 | return [ 6 | { 7 | name: "node_redis hmset", 8 | obj: "node_redis", 9 | loop: () => nodeRedis.hSet(NODE_REDIS_KEY, TEST_DATA.hash), 10 | }, 11 | { 12 | name: "node_redis hmset with multi", 13 | obj: "node_redis", 14 | beforeLoop: (ctx) => (ctx.multi = nodeRedis.multi()), 15 | loop: (ctx) => ctx.multi.hSet(NODE_REDIS_KEY, TEST_DATA.hash), 16 | afterLoop: (ctx) => ctx.multi.exec(), 17 | }, 18 | { 19 | name: "node_redis hmset with batch", 20 | obj: "node_redis", 21 | beforeLoop: (ctx) => (ctx.batch = nodeRedis.multi()), 22 | loop: (ctx) => ctx.batch.hSet(NODE_REDIS_KEY, TEST_DATA.hash), 23 | afterLoop: (ctx) => ctx.batch.execAsPipeline(), 24 | }, 25 | { 26 | name: "ioredis hmset", 27 | obj: "ioredis", 28 | loop: () => ioredis.hmset(IOREDIS_KEY, TEST_DATA.hash), 29 | }, 30 | { 31 | name: "ioredis hmset with multi", 32 | obj: "ioredis", 33 | beforeLoop: (ctx) => (ctx.multi = ioredis.multi()), 34 | loop: (ctx) => ctx.multi.hmset(IOREDIS_KEY, TEST_DATA.hash), 35 | afterLoop: (ctx) => ctx.multi.exec(), 36 | }, 37 | { 38 | name: "ioredis hmset with pipeline", 39 | obj: "ioredis", 40 | beforeLoop: (ctx) => (ctx.pipeline = ioredis.pipeline()), 41 | loop: (ctx) => ctx.pipeline.hmset(IOREDIS_KEY, TEST_DATA.hash), 42 | afterLoop: (ctx) => ctx.pipeline.exec(), 43 | }, 44 | ]; 45 | }; 46 | -------------------------------------------------------------------------------- /benchmarks/set.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ TEST_DATA, nodeRedis, ioredis, type }) => { 2 | const NODE_REDIS_KEY = `node_redis:${type}`; 3 | const IOREDIS_KEY = `ioredis:${type}`; 4 | 5 | return [ 6 | { 7 | name: "node_redis set", 8 | obj: "node_redis", 9 | loop: () => nodeRedis.set(NODE_REDIS_KEY, TEST_DATA.string), 10 | }, 11 | { 12 | name: "node_redis set with multi", 13 | obj: "node_redis", 14 | beforeLoop: (ctx) => (ctx.multi = nodeRedis.multi()), 15 | loop: (ctx) => ctx.multi.set(NODE_REDIS_KEY, TEST_DATA.string), 16 | afterLoop: (ctx) => ctx.multi.exec(), 17 | }, 18 | // https://github.com/redis/node-redis/issues/1796 19 | { 20 | name: "node_redis set with batch", 21 | obj: "node_redis", 22 | beforeLoop: (ctx) => (ctx.batch = nodeRedis.multi()), 23 | loop: (ctx) => ctx.batch.set(NODE_REDIS_KEY, TEST_DATA.string), 24 | afterLoop: (ctx) => ctx.batch.execAsPipeline(), 25 | }, 26 | { 27 | name: "ioredis set", 28 | obj: "ioredis", 29 | loop: () => ioredis.set(IOREDIS_KEY, TEST_DATA.string), 30 | }, 31 | { 32 | name: "ioredis set with multi", 33 | obj: "ioredis", 34 | beforeLoop: (ctx) => (ctx.multi = ioredis.multi()), 35 | loop: (ctx) => ctx.multi.set(IOREDIS_KEY, TEST_DATA.string), 36 | afterLoop: (ctx) => ctx.multi.exec(), 37 | }, 38 | { 39 | name: "ioredis set with pipeline", 40 | obj: "ioredis", 41 | beforeLoop: (ctx) => (ctx.pipeline = ioredis.pipeline()), 42 | loop: (ctx) => ctx.pipeline.set(IOREDIS_KEY, TEST_DATA.string), 43 | afterLoop: (ctx) => ctx.pipeline.exec(), 44 | }, 45 | ]; 46 | }; 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node_redis-vs-ioredis 2 | 3 | There are two popular redis client for Node: [node_redis](https://github.com/NodeRedis/node_redis) and [ioredis](https://github.com/luin/ioredis). 4 | 5 | This repository is a simple performance benchmark for them. PR is welcomed to makes this not tooooo simple >.< 6 | 7 | ## About this benchmark 8 | 9 | First of all, I must say ioredis is a real full-featured redis client right now. This is a really big PRO. 10 | 11 | I have no malice for both libraries. Just wanna do a performance benchmark. 12 | 13 | ## Run 14 | 15 | 1. Use `git clone git@github.com:poppinlp/node_redis-vs-ioredis.git` to clone this repo to local. 16 | 1. Use `yarn` or `npm i` to install dependencies. 17 | 1. Use `redis-server` to start redis server local with no password. (or `docker compose up -d`) 18 | 1. Use `yarn benchmark` or `npm run benchmark` to start benchmark. 19 | 20 | ## Result 21 | 22 | I do this benchmark on my laptop with: 23 | 24 | - OS: macOS 10.14.6 25 | - Processor: 2.7 GHz Intel Core i7 26 | - Memory: 16 GB 2133 MHz LPDDR3 27 | 28 | And the result is like: 29 | 30 | | Operation | node_redis(ms) | node_redis with multi(ms) | node_redis with batch(ms) | 31 | | --------- | -------------- | ------------------------- | ------------------------- | 32 | | set | 975.642 | 90.095 | 62.411 | 33 | | get | 1067.484 | 83.753 | 47.990 | 34 | | hmset | 937.843 | 122.092 | 95.343 | 35 | | hgetall | 1016.114 | 81.270 | 48.576 | 36 | | incr | 827.890 | 47.513 | 33.893 | 37 | | keys | 1045.368 | 245.800 | 202.975 | 38 | 39 | | Operation | ioredis(ms) | ioredis with multi(ms) | ioredis with pipeline(ms) | 40 | | --------- | ----------- | ---------------------- | ------------------------- | 41 | | set | 972.004 | 132.737 | 95.534 | 42 | | get | 964.135 | 117.334 | 103.084 | 43 | | hmset | 1006.690 | 162.359 | 152.416 | 44 | | hgetall | 1091.209 | 124.997 | 113.609 | 45 | | incr | 910.978 | 127.755 | 81.202 | 46 | | keys | 975.638 | 302.184 | 233.408 | 47 | 48 | It will also output an average for both ioredis and node_redis as well as a "winner". 49 | 50 | ### Command line arguments 51 | 52 | This benchmarking tool accepts following CLI arguments: 53 | 54 | - `--length [int]` The length of the benchmark (default: `20000`) 55 | - `--datafile [filepath]` A custom JSON file to use for testing. Has to have a `string`, `number` and `hash` property which are the only ones processed. 56 | 57 | PR is welcomed to makes this not tooooo simple >.< 58 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | const NodeRedis = require("redis"); 2 | const IORedis = require("ioredis"); 3 | const fs = require("fs"); 4 | 5 | const REDIS_CONFIG = { 6 | host: "127.0.0.1", 7 | port: 6379, 8 | dropBufferSupport: true 9 | }; 10 | const TEST_LEN = 20000; 11 | const TEST_DATA = { 12 | string: "hello world", 13 | number: 0, 14 | hash: { 15 | foo: "bar", 16 | hello: "world" 17 | } 18 | }; 19 | 20 | // Read args 21 | var args = process.argv.slice(2); 22 | args.forEach((el, i, arr) => { 23 | if (el == "--length" && arr[i + 1] && (len = parseInt(arr[i + 1]))) { 24 | TEST_LEN = len; 25 | console.log("Using custom length"); 26 | } else if (el == "--datafile" && (file = arr[i + 1])) { 27 | fs.readFile(file, "utf8", (err, data) => { 28 | if (err) throw err; 29 | let json = JSON.parse(data); 30 | if (json.string && json.number && json.hash) { 31 | TEST_DATA = json; 32 | console.log("Using datafile"); 33 | } 34 | }); 35 | } 36 | }); 37 | 38 | const nodeRedis = NodeRedis.createClient( 39 | `redis://${REDIS_CONFIG.host}:${REDIS_CONFIG.port}` 40 | ); 41 | const ioredis = new IORedis(REDIS_CONFIG); 42 | 43 | const units = [ 44 | { name: "set", type: "string" }, 45 | { name: "get", type: "string" }, 46 | { name: "hmset", type: "hash" }, 47 | { name: "hgetall", type: "hash" }, 48 | { name: "incr", type: "number" }, 49 | { name: "keys", type: "*" } 50 | ]; 51 | 52 | let avg = []; 53 | 54 | const runTests = async (TEST_LEN, TEST_DATA, output) => { 55 | for (let i = 0; i < units.length; ++i) { 56 | const { name, type } = units[i]; 57 | const taskArgs = { TEST_DATA, nodeRedis, ioredis, type }; 58 | const tasks = require(`./benchmarks/${name}.js`)(taskArgs); 59 | for (const { name, obj, beforeLoop, loop, afterLoop } of tasks) { 60 | let startTime = process.hrtime(); 61 | const ctx = {}; 62 | beforeLoop && beforeLoop(ctx); 63 | for (let i = 0; i < TEST_LEN; ++i) await loop(ctx); 64 | afterLoop && (await afterLoop(ctx)); 65 | if (output) { 66 | let endTime = process.hrtime(startTime); 67 | let time = parseFloat( 68 | (endTime[1] / 1000000 + endTime[0] * 1000).toFixed(6) 69 | ); 70 | console.log(`${name}: ${time}ms`); 71 | if (!avg.some(val => val.obj == obj)) 72 | avg.push({ obj, time: 0, calls: 0 }); 73 | let lavg = avg.find(val => val.obj == obj); 74 | lavg.calls++; 75 | lavg.time += time; 76 | } 77 | } 78 | } 79 | }; 80 | 81 | (async () => { 82 | try { 83 | await nodeRedis.connect(); 84 | // warm up a bit first 85 | await runTests(100, TEST_DATA, false); 86 | // now go 87 | console.log(".: Warmup complete :."); 88 | await runTests(TEST_LEN, TEST_DATA, true); 89 | console.log(".: Test Results :."); 90 | avg = avg.map(el => { 91 | el.average = parseFloat((el.time / el.calls).toFixed(3)); 92 | console.log(`${el.obj} had an average time of ${el.average}ms`); 93 | return el; 94 | }); 95 | console.log(".: WINNER :."); 96 | let winner = avg.reduce((obj1, obj2) => { 97 | return obj1.average > obj2.average ? obj2 : obj1; 98 | }); 99 | console.log( 100 | `The winner is ${winner.obj} with an average of ${winner.average}ms` 101 | ); 102 | } catch (e) { 103 | console.error("ERR", e); 104 | } 105 | 106 | nodeRedis.quit(); 107 | ioredis.quit(); 108 | })(); 109 | --------------------------------------------------------------------------------