Почему команда EVALSHA имеет такую высокую производительность по сравнению с собственными командами, выполняемыми на клиенте redis-cli?
Вот некоторые тесты и результаты, которые я провел с помощью инструмента 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(), которая затем отправила бы прямую команду на сервер, но не работает с конвейерной обработкой при использовании TypeScript. Поэтому мне пришлось бы выполнять все остальные команды отдельно и отказаться от конвейерной обработки.
Я думал, это должно было быть лучше?
****** Обновить:
Итак, в конце концов, основываясь на приведенном ниже ответе, я реорганизовал свой код, чтобы включить только 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;
1 ответ
Почему в вашем случае Lua-скрипт работает медленнее?
Потому как EVALSHA
нужно сделать больше, чем один JSON.SET
или SET
команда. При бегеEVALSHA
Redis необходимо передать аргументы в стек Lua, запустить сценарий Lua и извлечь возвращаемые значения из стека Lua. Это должно быть медленнее, чем вызов функции ac дляJSON.SET
или SET
.
Итак, когда сценарий на стороне сервера дает преимущество в производительности?
Прежде всего, вы должны запустить более одной команды в скрипте, иначе не будет никакого преимущества в производительности, как я упоминал выше.
Во-вторых, серверный скрипт выполняется быстрее, чем отправка сервальных команд в Redis одну за другой, получение результатов из Redis и выполнение вычислений на стороне клиента. Потому что сценарий Lua экономит много времени в оба конца.
В-третьих, если вам нужно выполнить действительно сложные вычисления в скрипте Lua. Возможно, это не лучшая идея. Поскольку Redis запускает сценарий в одном потоке, если сценарий занимает слишком много времени, он блокирует других клиентов. Вместо этого на стороне клиента вы можете воспользоваться преимуществом многоядерности для выполнения сложных вычислений.