与 redis-cli 客户端上的本机命令 运行 相比,为什么 EVALSHA 命令的性能成本如此之高?
Why does the EVALSHA command come at such a performance cost when compared to native commands run on the redis-cli client?
以下是我针对 redis-benchmark 工具进行的一些测试和结果 运行。
C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 JSON.SET fooz . [9999]
JSON.SET fooz . [9999]: 93049.23 requests per second
C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 evalsha 8d2d42f1e3a5ce869b50a2b65a8bfaafe8eff57a 1 fooz [5555]
evalsha 8d2d42f1e3a5ce869b50a2b65a8bfaafe8eff57a 1 fooz [5555]: 61132.17 requests per second
C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 eval "return redis.call('JSON.SET', KEYS[1], '.', ARGV[1])" 1 fooz [5555]
eval return redis.call('JSON.SET', KEYS[1], '.', ARGV[1]) 1 fooz [5555]: 57423.41 requests per second
对于脚本 运行 宁服务器端相对于客户端 运行 宁脚本客户端具有性能优势的东西来说,这是性能的显着下降。
从客户端到 EVALSHA
= 34% 性能损失
从 EVALSHA
到 EVAL
= 6% 性能损失
非JSON 插入 set
命令的结果相似
C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 set fooz 3333
set fooz 3333: 116414.43 requests per second
C02YLCE2LVCF:Downloads xxxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 evalsha e32aba8d03c97f4418a8593ed4166640651e18da 1 fooz [2222]
evalsha e32aba8d03c97f4418a8593ed4166640651e18da 1 fooz [2222]: 78520.67 requests per second
我第一次注意到这一点是在我执行 info commandstat 并观察到 EVALSHA
命令
的较差性能时
# Commandstats
cmdstat_ping:calls=331,usec=189,usec_per_call=0.57
cmdstat_eval:calls=65,usec=4868,usec_per_call=74.89
cmdstat_del:calls=2,usec=21,usec_per_call=10.50
cmdstat_ttl:calls=78,usec=131,usec_per_call=1.68
cmdstat_psync:calls=51,usec=2515,usec_per_call=49.31
cmdstat_command:calls=5,usec=3976,usec_per_call=795.20
cmdstat_scan:calls=172,usec=1280,usec_per_call=7.44
cmdstat_replconf:calls=185947,usec=217446,usec_per_call=1.17
****cmdstat_json.set:calls=1056,usec=26635,usec_per_call=25.22**
****cmdstat_evalsha:calls=1966,usec=68867,usec_per_call=35.03**
cmdstat_expire:calls=1073,usec=1118,usec_per_call=1.04
cmdstat_flushall:calls=9,usec=694,usec_per_call=77.11
cmdstat_monitor:calls=1,usec=1,usec_per_call=1.00
cmdstat_get:calls=17,usec=21,usec_per_call=1.24
cmdstat_cluster:calls=102761,usec=23379827,usec_per_call=227.52
cmdstat_client:calls=100551,usec=122382,usec_per_call=1.22
cmdstat_json.del:calls=247,usec=2487,usec_per_call=10.07
cmdstat_script:calls=207,usec=10834,usec_per_call=52.34
cmdstat_info:calls=4532,usec=229808,usec_per_call=50.71
cmdstat_json.get:calls=1615,usec=11923,usec_per_call=7.38
cmdstat_type:calls=78,usec=115,usec_per_call=1.47
从 JSON.SET
到 EVALSHA
有大约 30% 的性能降低,这是我在直接测试中观察到的。
问题是,为什么?而且,这有什么值得关注的,或者这种观察是否在合理的预期之内?
对于上下文,我使用 EVALSHA
而不是直接 JSON.SET 命令的原因有两个。
IORedis 客户端库不直接支持使用 RedisJson。
由于之前的事实,我将不得不使用 send_command() 然后将直接命令发送到服务器但在使用时无法使用流水线打字稿。所以我将不得不单独执行所有其他命令并放弃流水线。
我以为这应该是更好的表现?
****** 更新:
所以最后,根据下面的答案,我重构了我的代码,只包含 1 个 EVALSHA
用于写入,因为它使用 2 个命令,即 set 和 expire 命令。同样,我不能将其单独放入 RedisJson,这就是原因。
这是供某人参考的代码:显示 evalsha 和回退
await this.client.evalsha(this.luaWriteCommand, '1', documentChange.id, JSON.stringify(documentChange), expirationSeconds)
.catch((error) => {
console.error(error);
evalSHAFail = true;
});
if (evalSHAFail) {
console.error('EVALSHA for write not processed, using EVAL');
await this.client.eval("return redis.pcall('JSON.SET', KEYS[1], '.', ARGV[1]), redis.pcall('expire', KEYS[1], ARGV[2]);", '1', documentChange.id, JSON.stringify(documentChange), expirationSeconds);
console.log('SRANS FRUNDER');
this.luaWriteCommand = undefined;
Why Lua script is slower in your case?
因为 EVALSHA
需要比单个 JSON.SET
或 SET
命令做更多的工作。当运行宁EVALSHA
时,Redis需要将参数压入Lua栈,运行Lua脚本,从[=26中弹出return值=] 堆栈。它应该比 JSON.SET
或 SET
.
的 c 函数调用慢
So When does server side script has a performance advantage?
首先,您必须运行在脚本中使用多个命令,否则,将没有我上面提到的任何性能优势。
其次,服务器端脚本运行s比一个一个地向Redis发送serval命令更快,从Redis获取结果,并在客户端进行计算工作。因为,Lua 脚本节省了大量往返时间。
第三,如果您需要在 Lua 脚本中进行非常复杂的计算工作。这可能不是一个好主意。因为Redis运行是单线程的脚本,如果脚本耗时过多,会阻塞其他客户端。相反,在客户端,您可以利用多核的优势来进行复杂的计算。
以下是我针对 redis-benchmark 工具进行的一些测试和结果 运行。
C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 JSON.SET fooz . [9999]
JSON.SET fooz . [9999]: 93049.23 requests per second
C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 evalsha 8d2d42f1e3a5ce869b50a2b65a8bfaafe8eff57a 1 fooz [5555]
evalsha 8d2d42f1e3a5ce869b50a2b65a8bfaafe8eff57a 1 fooz [5555]: 61132.17 requests per second
C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 eval "return redis.call('JSON.SET', KEYS[1], '.', ARGV[1])" 1 fooz [5555]
eval return redis.call('JSON.SET', KEYS[1], '.', ARGV[1]) 1 fooz [5555]: 57423.41 requests per second
对于脚本 运行 宁服务器端相对于客户端 运行 宁脚本客户端具有性能优势的东西来说,这是性能的显着下降。
从客户端到 EVALSHA
= 34% 性能损失
从 EVALSHA
到 EVAL
= 6% 性能损失
非JSON 插入 set
命令的结果相似
C02YLCE2LVCF:Downloads xxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 set fooz 3333
set fooz 3333: 116414.43 requests per second
C02YLCE2LVCF:Downloads xxxxxxx$ redis-benchmark -p 7000 -q -r 1000000 -n 2000000 evalsha e32aba8d03c97f4418a8593ed4166640651e18da 1 fooz [2222]
evalsha e32aba8d03c97f4418a8593ed4166640651e18da 1 fooz [2222]: 78520.67 requests per second
我第一次注意到这一点是在我执行 info commandstat 并观察到 EVALSHA
命令
# Commandstats
cmdstat_ping:calls=331,usec=189,usec_per_call=0.57
cmdstat_eval:calls=65,usec=4868,usec_per_call=74.89
cmdstat_del:calls=2,usec=21,usec_per_call=10.50
cmdstat_ttl:calls=78,usec=131,usec_per_call=1.68
cmdstat_psync:calls=51,usec=2515,usec_per_call=49.31
cmdstat_command:calls=5,usec=3976,usec_per_call=795.20
cmdstat_scan:calls=172,usec=1280,usec_per_call=7.44
cmdstat_replconf:calls=185947,usec=217446,usec_per_call=1.17
****cmdstat_json.set:calls=1056,usec=26635,usec_per_call=25.22**
****cmdstat_evalsha:calls=1966,usec=68867,usec_per_call=35.03**
cmdstat_expire:calls=1073,usec=1118,usec_per_call=1.04
cmdstat_flushall:calls=9,usec=694,usec_per_call=77.11
cmdstat_monitor:calls=1,usec=1,usec_per_call=1.00
cmdstat_get:calls=17,usec=21,usec_per_call=1.24
cmdstat_cluster:calls=102761,usec=23379827,usec_per_call=227.52
cmdstat_client:calls=100551,usec=122382,usec_per_call=1.22
cmdstat_json.del:calls=247,usec=2487,usec_per_call=10.07
cmdstat_script:calls=207,usec=10834,usec_per_call=52.34
cmdstat_info:calls=4532,usec=229808,usec_per_call=50.71
cmdstat_json.get:calls=1615,usec=11923,usec_per_call=7.38
cmdstat_type:calls=78,usec=115,usec_per_call=1.47
从 JSON.SET
到 EVALSHA
有大约 30% 的性能降低,这是我在直接测试中观察到的。
问题是,为什么?而且,这有什么值得关注的,或者这种观察是否在合理的预期之内?
对于上下文,我使用 EVALSHA
而不是直接 JSON.SET 命令的原因有两个。
IORedis 客户端库不直接支持使用 RedisJson。
由于之前的事实,我将不得不使用 send_command() 然后将直接命令发送到服务器但在使用时无法使用流水线打字稿。所以我将不得不单独执行所有其他命令并放弃流水线。
我以为这应该是更好的表现?
****** 更新:
所以最后,根据下面的答案,我重构了我的代码,只包含 1 个 EVALSHA
用于写入,因为它使用 2 个命令,即 set 和 expire 命令。同样,我不能将其单独放入 RedisJson,这就是原因。
这是供某人参考的代码:显示 evalsha 和回退
await this.client.evalsha(this.luaWriteCommand, '1', documentChange.id, JSON.stringify(documentChange), expirationSeconds)
.catch((error) => {
console.error(error);
evalSHAFail = true;
});
if (evalSHAFail) {
console.error('EVALSHA for write not processed, using EVAL');
await this.client.eval("return redis.pcall('JSON.SET', KEYS[1], '.', ARGV[1]), redis.pcall('expire', KEYS[1], ARGV[2]);", '1', documentChange.id, JSON.stringify(documentChange), expirationSeconds);
console.log('SRANS FRUNDER');
this.luaWriteCommand = undefined;
Why Lua script is slower in your case?
因为 EVALSHA
需要比单个 JSON.SET
或 SET
命令做更多的工作。当运行宁EVALSHA
时,Redis需要将参数压入Lua栈,运行Lua脚本,从[=26中弹出return值=] 堆栈。它应该比 JSON.SET
或 SET
.
So When does server side script has a performance advantage?
首先,您必须运行在脚本中使用多个命令,否则,将没有我上面提到的任何性能优势。
其次,服务器端脚本运行s比一个一个地向Redis发送serval命令更快,从Redis获取结果,并在客户端进行计算工作。因为,Lua 脚本节省了大量往返时间。
第三,如果您需要在 Lua 脚本中进行非常复杂的计算工作。这可能不是一个好主意。因为Redis运行是单线程的脚本,如果脚本耗时过多,会阻塞其他客户端。相反,在客户端,您可以利用多核的优势来进行复杂的计算。