Unverified Commit 1b0968df authored by zhugezy's avatar zhugezy Committed by GitHub
Browse files

Remove EVAL script verbatim replication, propagation, and deterministic execution logic (#9812)



# Background

The main goal of this PR is to remove relevant logics on Lua script verbatim replication,
only keeping effects replication logic, which has been set as default since Redis 5.0.
As a result, Lua in Redis 7.0 would be acting the same as Redis 6.0 with default
configuration from users' point of view.

There are lots of reasons to remove verbatim replication.
Antirez has listed some of the benefits in Issue #5292:

>1. No longer need to explain to users side effects into scripts.
    They can do whatever they want.
>2. No need for a cache about scripts that we sent or not to the slaves.
>3. No need to sort the output of certain commands inside scripts
    (SMEMBERS and others): this both simplifies and gains speed.
>4. No need to store scripts inside the RDB file in order to startup correctly.
>5. No problems about evicting keys during the script execution.

When looking back at Redis 5.0, antirez and core team decided to set the config
`lua-replicate-commands yes` by default instead of removing verbatim replication
directly, in case some bad situations happened. 3 years later now before Redis 7.0,
it's time to remove it formally.

# Changes

- configuration for lua-replicate-commands removed
  - created config file stub for backward compatibility
- Replication script cache removed
  - this is useless under script effects replication
  - relevant statistics also removed
- script persistence in RDB files is also removed
- Propagation of SCRIPT LOAD and SCRIPT FLUSH to replica / AOF removed
- Deterministic execution logic in scripts removed (i.e. don't run write commands
  after random ones, and sorting output of commands with random order)
  - the flags indicating which commands have non-deterministic results are kept as hints to clients.
- `redis.replicate_commands()` & `redis.set_repl()` changed
  - now `redis.replicate_commands()` does nothing and return an 1
  - ...and then `redis.set_repl()` can be issued before `redis.replicate_commands()` now
- Relevant TCL cases adjusted
- DEBUG lua-always-replicate-commands removed

# Other changes
- Fix a recent bug comparing CLIENT_ID_AOF to original_client->flags instead of id. (introduced in #9780)
Co-authored-by: default avatarOran Agra <oran@redislabs.com>
parent febc3f63
......@@ -128,91 +128,3 @@ start_server {tags {"repl external:skip"}} {
}
}
}
start_server {tags {"repl external:skip"}} {
start_server {} {
test {First server should have role slave after SLAVEOF} {
r -1 slaveof [srv 0 host] [srv 0 port]
wait_for_condition 50 100 {
[s -1 master_link_status] eq {up}
} else {
fail "Replication not started."
}
}
set numops 20000 ;# Enough to trigger the Script Cache LRU eviction.
# While we are at it, enable AOF to test it will be consistent as well
# after the test.
r config set appendonly yes
test {MASTER and SLAVE consistency with EVALSHA replication} {
array set oldsha {}
for {set j 0} {$j < $numops} {incr j} {
set key "key:$j"
# Make sure to create scripts that have different SHA1s
set script "return redis.call('incr','$key')"
set sha1 [r eval "return redis.sha1hex(\"$script\")" 0]
set oldsha($j) $sha1
r eval $script 0
set res [r evalsha $sha1 0]
assert {$res == 2}
# Additionally call one of the old scripts as well, at random.
set res [r evalsha $oldsha([randomInt $j]) 0]
assert {$res > 2}
# Trigger an AOF rewrite while we are half-way, this also
# forces the flush of the script cache, and we will cover
# more code as a result.
if {$j == $numops / 2} {
catch {r bgrewriteaof}
}
}
wait_for_condition 50 100 {
[r dbsize] == $numops &&
[r -1 dbsize] == $numops &&
[r debug digest] eq [r -1 debug digest]
} else {
set csv1 [csvdump r]
set csv2 [csvdump {r -1}]
set fd [open /tmp/repldump1.txt w]
puts -nonewline $fd $csv1
close $fd
set fd [open /tmp/repldump2.txt w]
puts -nonewline $fd $csv2
close $fd
puts "Master - Replica inconsistency"
puts "Run diff -u against /tmp/repldump*.txt for more info"
}
set old_digest [r debug digest]
r config set appendonly no
r debug loadaof
set new_digest [r debug digest]
assert {$old_digest eq $new_digest}
}
test {SLAVE can reload "lua" AUX RDB fields of duplicated scripts} {
# Force a Slave full resynchronization
r debug change-repl-id
r -1 client kill type master
# Check that after a full resync the slave can still load
# correctly the RDB file: such file will contain "lua" AUX
# sections with scripts already in the memory of the master.
wait_for_condition 1000 100 {
[s -1 master_link_status] eq {up}
} else {
fail "Replication not started."
}
wait_for_condition 50 100 {
[r debug digest] eq [r -1 debug digest]
} else {
fail "DEBUG DIGEST mismatch after full SYNC with many scripts"
}
}
}
}
......@@ -602,26 +602,26 @@ start_server {tags {"multi"}} {
test {MULTI propagation of SCRIPT LOAD} {
set repl [attach_to_replication_stream]
# make sure that SCRIPT LOAD inside MULTI is propagated in a transaction
# make sure that SCRIPT LOAD inside MULTI isn't propagated
r multi
r script load {redis.call('set', KEYS[1], 'foo')}
r set foo bar
set res [r exec]
set sha [lindex $res 0]
assert_replication_stream $repl {
{select *}
{multi}
{script load *}
{set foo bar}
{exec}
}
close_replication_stream $repl
} {} {needs:repl}
test {MULTI propagation of SCRIPT LOAD} {
test {MULTI propagation of EVAL} {
set repl [attach_to_replication_stream]
# make sure that EVAL inside MULTI is propagated in a transaction
r config set lua-replicate-commands no
# make sure that EVAL inside MULTI is propagated in a transaction in effects
r multi
r eval {redis.call('set', KEYS[1], 'bar')} 1 bar
r exec
......@@ -629,12 +629,30 @@ start_server {tags {"multi"}} {
assert_replication_stream $repl {
{select *}
{multi}
{eval *}
{set bar bar}
{exec}
}
close_replication_stream $repl
} {} {needs:repl}
test {MULTI propagation of SCRIPT FLUSH} {
set repl [attach_to_replication_stream]
# make sure that SCRIPT FLUSH isn't propagated
r multi
r script flush
r set foo bar
r exec
assert_replication_stream $repl {
{select *}
{multi}
{set foo bar}
{exec}
}
close_replication_stream $repl
} {} {need:repl}
tags {"stream"} {
test {MULTI propagation of XREADGROUP} {
# stream is a special case because it calls propagate() directly for XREADGROUP
......
......@@ -37,18 +37,11 @@ start_server {tags {"pause network"}} {
$rd2 publish foo bar
wait_for_blocked_clients_count 2 50 100
# Test that SCRIPT LOAD, which is replicated.
set rd3 [redis_deferring_client]
$rd3 script load "return 1"
wait_for_blocked_clients_count 3 50 100
r client unpause
assert_match "1" [$rd read]
assert_match "0" [$rd2 read]
assert_match "*" [$rd3 read]
$rd close
$rd2 close
$rd3 close
}
test "Test read/admin mutli-execs are not blocked by pause RO" {
......
......@@ -227,18 +227,13 @@ start_server {tags {"scripting"}} {
assert_error "*xreadgroup command is not allowed with BLOCK option from scripts" {run_script {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s}
}
if {$is_eval eq 1} {
# only is_eval Lua can not execute randomkey
test {EVAL - Scripts can't run certain commands} {
test {EVAL - Scripts can run non-deterministic commands} {
set e {}
r debug lua-always-replicate-commands 0
catch {
run_script "redis.pcall('randomkey'); return redis.pcall('set','x','ciao')" 0
} e
r debug lua-always-replicate-commands 1
set e
} {*not allowed after*} {needs:debug}
} ;# is_eval
} {*OK*}
test {EVAL - No arguments to redis.call/pcall is considered an error} {
set e {}
......@@ -406,16 +401,6 @@ start_server {tags {"scripting"}} {
[r evalsha b534286061d4b9e4026607613b95c06c06015ae8 0]
} {b534286061d4b9e4026607613b95c06c06015ae8 loaded}
# reply oredering is only relevant for is_eval Lua
test "In the context of Lua the output of random commands gets ordered" {
r debug lua-always-replicate-commands 0
r del myset
r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
set res [r eval {return redis.call('smembers',KEYS[1])} 1 myset]
r debug lua-always-replicate-commands 1
set res
} {a aa aaa azz b c d e f g h i l m n o p q r s t u v z} {needs:debug}
test "SORT is normally not alpha re-ordered for the scripting engine" {
r del myset
r sadd myset 1 2 3 4 10
......@@ -474,10 +459,13 @@ start_server {tags {"scripting"}} {
if {$is_eval eq 1} {
# random handling is only relevant for is_eval Lua
test {Scripting engine resets PRNG at every script execution} {
test {random numbers are random now} {
set rand1 [r eval {return tostring(math.random())} 0]
set rand2 [r eval {return tostring(math.random())} 0]
assert_equal $rand1 $rand2
wait_for_condition 100 1 {
$rand1 ne [r eval {return tostring(math.random())} 0]
} else {
fail "random numbers should be random, now it's fixed value"
}
}
test {Scripting engine PRNG can be seeded correctly} {
......@@ -510,78 +498,71 @@ start_server {tags {"scripting"}} {
r get x
} {10000}
test {EVAL processes writes from AOF in read-only slaves} {
r flushall
r config set appendonly yes
r config set aof-use-rdb-preamble no
run_script {redis.call("set",KEYS[1],"100")} 1 foo
run_script {redis.call("incr",KEYS[1])} 1 foo
run_script {redis.call("incr",KEYS[1])} 1 foo
wait_for_condition 50 100 {
[s aof_rewrite_in_progress] == 0
} else {
fail "AOF rewrite can't complete after CONFIG SET appendonly yes."
}
r config set slave-read-only yes
r slaveof 127.0.0.1 0
r debug loadaof
set res [r get foo]
r slaveof no one
r config set aof-use-rdb-preamble yes
set res
} {102} {external:skip}
if {$is_eval eq 1} {
# script propagation is irrelevant on functions
test {EVAL timeout from AOF} {
# generate a long running script that is propagated to the AOF as script
# make sure that the script times out during loading
r config set appendonly no
r config set aof-use-rdb-preamble no
r config set lua-replicate-commands no
r flushall
r config set appendonly yes
wait_for_condition 50 100 {
[s aof_rewrite_in_progress] == 0
} else {
fail "AOF rewrite can't complete after CONFIG SET appendonly yes."
}
r config set lua-time-limit 1
set rd [redis_deferring_client]
set start [clock clicks -milliseconds]
$rd eval {redis.call('set',KEYS[1],'y'); for i=1,1500000 do redis.call('ping') end return 'ok'} 1 x
$rd flush
after 100
catch {r ping} err
assert_match {BUSY*} $err
$rd read
set elapsed [expr [clock clicks -milliseconds]-$start]
if {$::verbose} { puts "script took $elapsed milliseconds" }
set start [clock clicks -milliseconds]
$rd debug loadaof
$rd flush
after 100
catch {r ping} err
assert_match {LOADING*} $err
$rd read
set elapsed [expr [clock clicks -milliseconds]-$start]
if {$::verbose} { puts "loading took $elapsed milliseconds" }
$rd close
r get x
} {y} {external:skip}
test {We can call scripts rewriting client->argv from Lua} {
test {SPOP: We can call scripts rewriting client->argv from Lua} {
set repl [attach_to_replication_stream]
#this sadd operation is for external-cluster test. If myset doesn't exist, 'del myset' won't get propagated.
r sadd myset ppp
r del myset
r sadd myset a b c
assert {[r eval {return redis.call('spop', 'myset')} 0] ne {}}
assert {[r eval {return redis.call('spop', 'myset', 1)} 0] ne {}}
assert {[r eval {return redis.call('spop', KEYS[1])} 1 myset] ne {}}
#this one below should be replicated by an empty MULTI/EXEC
assert {[r eval {return redis.call('spop', KEYS[1])} 1 myset] eq {}}
r set trailingkey 1
assert_replication_stream $repl {
{select *}
{sadd *}
{del *}
{sadd *}
{multi}
{srem myset *}
{exec}
{multi}
{srem myset *}
{exec}
{multi}
{srem myset *}
{exec}
{multi}
{exec}
{set *}
}
close_replication_stream $repl
} {} {need:repl}
test {MGET: mget shouldn't be propagated in Lua} {
set repl [attach_to_replication_stream]
r mset a{t} 1 b{t} 2 c{t} 3 d{t} 4
assert {[r spop myset] ne {}}
assert {[r spop myset 1] ne {}}
assert {[r spop myset] ne {}}
assert {[r mget a{t} b{t} c{t} d{t}] eq {1 2 3 4}}
assert {[r spop myset] eq {}}
}
} ;# is_eval
#read-only, won't be replicated
assert {[r eval {return redis.call('mget', 'a{t}', 'b{t}', 'c{t}', 'd{t}')} 0] eq {1 2 3 4}}
r set trailingkey 2
assert_replication_stream $repl {
{select *}
{mset *}
{set *}
}
close_replication_stream $repl
} {} {need:repl}
test {EXPIRE: We can call scripts rewriting client->argv from Lua} {
set repl [attach_to_replication_stream]
r set expirekey 1
#should be replicated as EXPIREAT
assert {[r eval {return redis.call('expire', KEYS[1], ARGV[1])} 1 expirekey 3] eq 1}
assert_replication_stream $repl {
{select *}
{set *}
{multi}
{pexpireat expirekey *}
{exec}
}
close_replication_stream $repl
} {} {need:repl}
} ;# is_eval
test {Call Redis command with many args from Lua (issue #1764)} {
run_script {
......@@ -812,17 +793,9 @@ start_server {tags {"scripting"}} {
} {} {external:skip}
}
foreach cmdrepl {0 1} {
start_server {tags {"scripting repl needs:debug external:skip"}} {
start_server {} {
if {$cmdrepl == 1} {
set rt "(commands replication)"
} else {
set rt "(scripts replication)"
r debug lua-always-replicate-commands 1
}
test "Before the replica connects we issue two EVAL commands $rt" {
test "Before the replica connects we issue two EVAL commands" {
# One with an error, but still executing a command.
# SHA is: 67164fc43fa971f76fd1aaeeaf60c1c178d25876
catch {
......@@ -833,7 +806,7 @@ foreach cmdrepl {0 1} {
run_script {return redis.call('incr',KEYS[1])} 1 x
} {2}
test "Connect a replica to the master instance $rt" {
test "Connect a replica to the master instance" {
r -1 slaveof [srv 0 host] [srv 0 port]
wait_for_condition 50 100 {
[s -1 role] eq {slave} &&
......@@ -844,7 +817,7 @@ foreach cmdrepl {0 1} {
}
if {$is_eval eq 1} {
test "Now use EVALSHA against the master, with both SHAs $rt" {
test "Now use EVALSHA against the master, with both SHAs" {
# The server should replicate successful and unsuccessful
# commands as EVAL instead of EVALSHA.
catch {
......@@ -853,7 +826,7 @@ foreach cmdrepl {0 1} {
r evalsha 6f5ade10a69975e903c6d07b10ea44c6382381a5 1 x
} {4}
test "If EVALSHA was replicated as EVAL, 'x' should be '4' $rt" {
test "'x' should be '4' for EVALSHA being replicated by effects" {
wait_for_condition 50 100 {
[r -1 get x] eq {4}
} else {
......@@ -862,7 +835,7 @@ foreach cmdrepl {0 1} {
}
} ;# is_eval
test "Replication of script multiple pushes to list with BLPOP $rt" {
test "Replication of script multiple pushes to list with BLPOP" {
set rd [redis_deferring_client]
$rd brpop a 0
run_script {
......@@ -880,7 +853,7 @@ foreach cmdrepl {0 1} {
} {a 1}
if {$is_eval eq 1} {
test "EVALSHA replication when first call is readonly $rt" {
test "EVALSHA replication when first call is readonly" {
r del x
r eval {if tonumber(ARGV[1]) > 0 then redis.call('incr', KEYS[1]) end} 1 x 0
r evalsha 6e0e2745aa546d0b50b801a20983b70710aef3ce 1 x 0
......@@ -893,7 +866,7 @@ foreach cmdrepl {0 1} {
}
} ;# is_eval
test "Lua scripts using SELECT are replicated correctly $rt" {
test "Lua scripts using SELECT are replicated correctly" {
run_script {
redis.call("set","foo1","bar1")
redis.call("select","10")
......@@ -916,7 +889,6 @@ foreach cmdrepl {0 1} {
} {} {singledb:skip}
}
}
}
start_server {tags {"scripting repl external:skip"}} {
start_server {overrides {appendonly yes aof-use-rdb-preamble no}} {
......@@ -930,32 +902,22 @@ start_server {tags {"scripting repl external:skip"}} {
}
}
if {$is_eval eq 1} {
# replicate_commands is the default on Redis Function
test "Redis.replicate_commands() must be issued before any write" {
test "Redis.replicate_commands() can be issued anywhere now" {
r eval {
redis.call('set','foo','bar');
return redis.replicate_commands();
} 0
} {}
test "Redis.replicate_commands() must be issued before any write (2)" {
r eval {
return redis.replicate_commands();
} 0
} {1}
test "Redis.set_repl() must be issued after replicate_commands()" {
r debug lua-always-replicate-commands 0
test "Redis.set_repl() can be issued before replicate_commands() now" {
catch {
r eval {
redis.set_repl(redis.REPL_ALL);
} 0
} e
r debug lua-always-replicate-commands 1
set e
} {*only after turning on*}
} ;# is_eval
} {}
test "Redis.set_repl() don't accept invalid values" {
catch {
......@@ -981,7 +943,7 @@ start_server {tags {"scripting repl external:skip"}} {
wait_for_condition 50 100 {
[r -1 mget a b c d] eq {1 {} {} 4}
} else {
fail "Only a and c should be replicated to replica"
fail "Only a and d should be replicated to replica"
}
# Master should have everything right now
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment