mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-11 18:35:46 +02:00
fix: Remove incremental locking (#1094)
This commit is contained in:
parent
023abfa46e
commit
b345604226
5 changed files with 15 additions and 118 deletions
|
@ -120,7 +120,7 @@ Redis transactions (MULTI/EXEC sequences) and commands produced by Lua scripts a
|
|||
|
||||
The multi feature of the transactional framework allows running consecutive commands without rescheduling the transaction for each command as if they are part of one single transaction. This feature is transparent to the commands itself, so no changes are required for them to be used in a multi-transaction.
|
||||
|
||||
There are four modes called "multi modes" in which a multi transaction can be executed, each with its own benefits and drawbacks.
|
||||
There are three modes called "multi modes" in which a multi transaction can be executed, each with its own benefits and drawbacks.
|
||||
|
||||
__1. Global mode__
|
||||
|
||||
|
@ -130,11 +130,7 @@ __2. Lock ahead mode__
|
|||
|
||||
The transaction is equivalent to a regular transaction with multiple hops. It is scheduled on all keys used by the commands in the transaction block, or Lua script, and the commands are executed as a series of consecutive hops.
|
||||
|
||||
__3. Incremental lock mode__
|
||||
|
||||
The transaction schedules itself on all the shards that are accessed by the Redis transaction or Lua script, but does not lock any keys ahead. Only when it executes and occupies all the predetermined shards, it starts locking the keys it accesses. This mode _can_ be useful for delaying the acquisition of locks for contended keys and thus allowing other transactions to run in parallel for a longer period of time, however this mode disabled a wide range of optimizations for the multi-transaction itself, such as running out of order.
|
||||
|
||||
__4. Non atomic mode__
|
||||
__3. Non atomic mode__
|
||||
|
||||
All commands are executed as separate transactions making the multi-transaction not atomic. It vastly improves the throughput with contended keys, as locks are acquired only for single commands. This mode is useful for Lua scripts without atomicity requirements.
|
||||
|
||||
|
|
|
@ -46,8 +46,7 @@ ABSL_FLAG(uint32_t, port, 6379, "Redis port");
|
|||
ABSL_FLAG(uint32_t, memcache_port, 0, "Memcached port");
|
||||
|
||||
ABSL_FLAG(uint32_t, multi_exec_mode, 1,
|
||||
"Set multi exec atomicity mode: 1 for global, 2 for locking ahead, 3 for locking "
|
||||
"incrementally, 4 for non atomic");
|
||||
"Set multi exec atomicity mode: 1 for global, 2 for locking ahead, 3 for non atomic");
|
||||
|
||||
ABSL_FLAG(bool, multi_exec_squash, true,
|
||||
"Whether multi exec will squash single shard commands to optimize performance");
|
||||
|
@ -1132,13 +1131,6 @@ void Service::EvalSha(CmdArgList args, ConnectionContext* cntx) {
|
|||
ss->RecordCallLatency(sha, (end - start) / 1000);
|
||||
}
|
||||
|
||||
vector<bool> DetermineKeyShards(CmdArgList keys) {
|
||||
vector<bool> out(shard_set->size());
|
||||
for (auto k : keys)
|
||||
out[Shard(facade::ToSV(k), out.size())] = true;
|
||||
return out;
|
||||
}
|
||||
|
||||
optional<ScriptMgr::ScriptParams> LoadScipt(string_view sha, ScriptMgr* script_mgr,
|
||||
Interpreter* interpreter) {
|
||||
auto ss = ServerState::tlocal();
|
||||
|
@ -1176,8 +1168,7 @@ bool StartMultiEval(DbIndex dbid, CmdArgList keys, ScriptMgr::ScriptParams param
|
|||
Transaction* trans) {
|
||||
Transaction::MultiMode multi_mode = DetermineMultiMode(params);
|
||||
|
||||
if (keys.empty() &&
|
||||
(multi_mode == Transaction::LOCK_AHEAD || multi_mode == Transaction::LOCK_INCREMENTAL))
|
||||
if (keys.empty() && multi_mode == Transaction::LOCK_AHEAD)
|
||||
return false;
|
||||
|
||||
switch (multi_mode) {
|
||||
|
@ -1187,9 +1178,6 @@ bool StartMultiEval(DbIndex dbid, CmdArgList keys, ScriptMgr::ScriptParams param
|
|||
case Transaction::LOCK_AHEAD:
|
||||
trans->StartMultiLockedAhead(dbid, keys);
|
||||
return true;
|
||||
case Transaction::LOCK_INCREMENTAL:
|
||||
trans->StartMultiLockedIncr(dbid, DetermineKeyShards(keys));
|
||||
return true;
|
||||
case Transaction::NON_ATOMIC:
|
||||
trans->StartMultiNonAtomic();
|
||||
return true;
|
||||
|
@ -1346,16 +1334,6 @@ CmdArgVec CollectAllKeys(ConnectionState::ExecInfo* exec_info) {
|
|||
return out;
|
||||
}
|
||||
|
||||
vector<bool> DetermineKeyShards(ConnectionState::ExecInfo* exec_info) {
|
||||
vector<bool> out(shard_set->size());
|
||||
|
||||
IterateAllKeys(exec_info, [&out](MutableSlice key) {
|
||||
ShardId sid = Shard(facade::ToSV(key), shard_set->size());
|
||||
out[sid] = true;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
// Return true if transaction was scheduled, false if scheduling was not required.
|
||||
bool StartMultiExec(DbIndex dbid, Transaction* trans, ConnectionState::ExecInfo* exec_info,
|
||||
CmdArgVec* tmp_keys) {
|
||||
|
@ -1375,8 +1353,7 @@ bool StartMultiExec(DbIndex dbid, Transaction* trans, ConnectionState::ExecInfo*
|
|||
DCHECK(multi_mode >= Transaction::GLOBAL && multi_mode <= Transaction::NON_ATOMIC);
|
||||
|
||||
// Atomic modes fall back to GLOBAL if they contain global commands.
|
||||
if (global &&
|
||||
(multi_mode == Transaction::LOCK_AHEAD || multi_mode == Transaction::LOCK_INCREMENTAL))
|
||||
if (global && multi_mode == Transaction::LOCK_AHEAD)
|
||||
multi_mode = Transaction::GLOBAL;
|
||||
|
||||
switch ((Transaction::MultiMode)multi_mode) {
|
||||
|
@ -1387,9 +1364,6 @@ bool StartMultiExec(DbIndex dbid, Transaction* trans, ConnectionState::ExecInfo*
|
|||
*tmp_keys = CollectAllKeys(exec_info);
|
||||
trans->StartMultiLockedAhead(dbid, CmdArgList{*tmp_keys});
|
||||
break;
|
||||
case Transaction::LOCK_INCREMENTAL:
|
||||
trans->StartMultiLockedIncr(dbid, DetermineKeyShards(exec_info));
|
||||
break;
|
||||
case Transaction::NON_ATOMIC:
|
||||
trans->StartMultiNonAtomic();
|
||||
break;
|
||||
|
|
|
@ -27,7 +27,7 @@ template <typename F> void IterateKeys(CmdArgList args, KeyIndex keys, F&& f) {
|
|||
MultiCommandSquasher::MultiCommandSquasher(absl::Span<StoredCmd> cmds, ConnectionContext* cntx)
|
||||
: cmds_{cmds}, cntx_{cntx}, base_cid_{cntx->transaction->GetCId()} {
|
||||
auto mode = cntx->transaction->GetMultiMode();
|
||||
track_keys_ = (mode == Transaction::LOCK_INCREMENTAL) || (mode == Transaction::NON_ATOMIC);
|
||||
track_keys_ = mode == Transaction::NON_ATOMIC;
|
||||
}
|
||||
|
||||
MultiCommandSquasher::ShardExecInfo& MultiCommandSquasher::PrepareShardInfo(ShardId sid) {
|
||||
|
|
|
@ -130,16 +130,11 @@ void Transaction::InitShardData(absl::Span<const PerShardCache> shard_index, siz
|
|||
sd.arg_count = si.args.size();
|
||||
sd.arg_start = args_.size();
|
||||
|
||||
if (multi_) {
|
||||
// Multi transactions can re-intitialize on different shards, so clear ACTIVE flag.
|
||||
if (multi_)
|
||||
sd.local_mask &= ~ACTIVE;
|
||||
|
||||
// If we increase locks, clear KEYLOCK_ACQUIRED to track new locks.
|
||||
if (multi_->IsIncrLocks())
|
||||
sd.local_mask &= ~KEYLOCK_ACQUIRED;
|
||||
}
|
||||
|
||||
if (sd.arg_count == 0 && !si.requested_active)
|
||||
if (sd.arg_count == 0)
|
||||
continue;
|
||||
|
||||
sd.local_mask |= ACTIVE;
|
||||
|
@ -163,9 +158,7 @@ void Transaction::InitMultiData(KeyIndex key_index) {
|
|||
if (multi_->mode == NON_ATOMIC)
|
||||
return;
|
||||
|
||||
// TODO: determine correct locking mode for transactions, scripts and regular commands.
|
||||
IntentLock::Mode mode = Mode();
|
||||
multi_->keys.clear();
|
||||
|
||||
auto& tmp_uniques = tmp_space.uniq_keys;
|
||||
tmp_uniques.clear();
|
||||
|
@ -174,16 +167,12 @@ void Transaction::InitMultiData(KeyIndex key_index) {
|
|||
if (auto [_, inserted] = tmp_uniques.insert(key); !inserted)
|
||||
return;
|
||||
|
||||
if (multi_->IsIncrLocks()) {
|
||||
multi_->keys.emplace_back(key);
|
||||
} else {
|
||||
multi_->lock_counts[key][mode]++;
|
||||
}
|
||||
};
|
||||
|
||||
// With EVAL, we call this function for EVAL itself as well as for each command
|
||||
// for eval. currently, we lock everything only during the eval call.
|
||||
if (multi_->IsIncrLocks() || !multi_->locks_recorded) {
|
||||
if (!multi_->locks_recorded) {
|
||||
for (size_t i = key_index.start; i < key_index.end; i += key_index.step)
|
||||
lock_key(ArgS(full_args_, i));
|
||||
if (key_index.bonus)
|
||||
|
@ -192,7 +181,7 @@ void Transaction::InitMultiData(KeyIndex key_index) {
|
|||
|
||||
multi_->locks_recorded = true;
|
||||
DCHECK(IsAtomicMulti());
|
||||
DCHECK(multi_->mode == GLOBAL || !multi_->keys.empty() || !multi_->lock_counts.empty());
|
||||
DCHECK(multi_->mode == GLOBAL || !multi_->lock_counts.empty());
|
||||
}
|
||||
|
||||
void Transaction::StoreKeysInArgs(KeyIndex key_index, bool rev_mapping) {
|
||||
|
@ -384,24 +373,6 @@ void Transaction::StartMultiLockedAhead(DbIndex dbid, CmdArgList keys) {
|
|||
ScheduleInternal();
|
||||
}
|
||||
|
||||
void Transaction::StartMultiLockedIncr(DbIndex dbid, const vector<bool>& shards) {
|
||||
DCHECK(multi_);
|
||||
DCHECK(shard_data_.empty()); // Make sure default InitByArgs didn't run.
|
||||
DCHECK(std::any_of(shards.begin(), shards.end(), [](bool s) { return s; }));
|
||||
|
||||
multi_->mode = LOCK_INCREMENTAL;
|
||||
InitBase(dbid, {});
|
||||
|
||||
auto& shard_index = tmp_space.GetShardIndex(shard_set->size());
|
||||
for (size_t i = 0; i < shards.size(); i++)
|
||||
shard_index[i].requested_active = shards[i];
|
||||
|
||||
shard_data_.resize(shard_index.size());
|
||||
InitShardData(shard_index, 0, false);
|
||||
|
||||
ScheduleInternal();
|
||||
}
|
||||
|
||||
void Transaction::StartMultiNonAtomic() {
|
||||
DCHECK(multi_);
|
||||
multi_->mode = NON_ATOMIC;
|
||||
|
@ -461,7 +432,6 @@ bool Transaction::RunInShard(EngineShard* shard) {
|
|||
|
||||
bool was_suspended = sd.local_mask & SUSPENDED_Q;
|
||||
bool awaked_prerun = sd.local_mask & AWAKED_Q;
|
||||
bool incremental_lock = multi_ && multi_->IsIncrLocks();
|
||||
|
||||
// For multi we unlock transaction (i.e. its keys) in UnlockMulti() call.
|
||||
// Therefore we differentiate between concluding, which says that this specific
|
||||
|
@ -472,15 +442,6 @@ bool Transaction::RunInShard(EngineShard* shard) {
|
|||
bool should_release = is_concluding && !IsAtomicMulti();
|
||||
IntentLock::Mode mode = Mode();
|
||||
|
||||
// We make sure that we lock exactly once for each (multi-hop) transaction inside
|
||||
// transactions that lock incrementally.
|
||||
if (!IsGlobal() && incremental_lock && ((sd.local_mask & KEYLOCK_ACQUIRED) == 0)) {
|
||||
DCHECK(!awaked_prerun); // we should not have a blocking transaction inside multi block.
|
||||
|
||||
sd.local_mask |= KEYLOCK_ACQUIRED;
|
||||
shard->db_slice().Acquire(mode, GetLockArgs(idx));
|
||||
}
|
||||
|
||||
DCHECK(IsGlobal() || (sd.local_mask & KEYLOCK_ACQUIRED) || (multi_ && multi_->mode == GLOBAL));
|
||||
|
||||
/*************************************************************************/
|
||||
|
@ -620,7 +581,6 @@ void Transaction::ScheduleInternal() {
|
|||
coordinator_state_ |= COORD_SCHED;
|
||||
// If we granted all locks, we can run out of order.
|
||||
if (!ooo_disabled && lock_granted_cnt.load(memory_order_relaxed) == num_shards) {
|
||||
// Currently we don't support OOO for incremental locking. Sp far they are global.
|
||||
coordinator_state_ |= COORD_OOO;
|
||||
}
|
||||
VLOG(2) << "Scheduled " << DebugId()
|
||||
|
@ -667,18 +627,6 @@ void Transaction::ScheduleInternal() {
|
|||
}
|
||||
}
|
||||
|
||||
void Transaction::MultiData::AddLocks(IntentLock::Mode mode) {
|
||||
DCHECK(IsIncrLocks());
|
||||
for (auto& key : keys) {
|
||||
lock_counts[std::move(key)][mode]++;
|
||||
}
|
||||
keys.clear();
|
||||
}
|
||||
|
||||
bool Transaction::MultiData::IsIncrLocks() const {
|
||||
return mode == LOCK_INCREMENTAL;
|
||||
}
|
||||
|
||||
// Optimized "Schedule and execute" function for the most common use-case of a single hop
|
||||
// transactions like set/mset/mget etc. Does not apply for more complicated cases like RENAME or
|
||||
// BLPOP where a data must be read from multiple shards before performing another hop.
|
||||
|
@ -734,9 +682,6 @@ OpStatus Transaction::ScheduleSingleHop(RunnableType cb) {
|
|||
if (!IsAtomicMulti()) // Multi schedule in advance.
|
||||
ScheduleInternal();
|
||||
|
||||
if (multi_ && multi_->IsIncrLocks())
|
||||
multi_->AddLocks(Mode());
|
||||
|
||||
ExecuteAsync();
|
||||
}
|
||||
|
||||
|
@ -808,9 +753,6 @@ void Transaction::Schedule() {
|
|||
if (multi_ && multi_->role == SQUASHED_STUB)
|
||||
return;
|
||||
|
||||
if (multi_ && multi_->IsIncrLocks())
|
||||
multi_->AddLocks(Mode());
|
||||
|
||||
if (!IsAtomicMulti())
|
||||
ScheduleInternal();
|
||||
}
|
||||
|
@ -1101,7 +1043,7 @@ bool Transaction::CancelShardCb(EngineShard* shard) {
|
|||
if (sd.local_mask & KEYLOCK_ACQUIRED) {
|
||||
auto mode = Mode();
|
||||
auto lock_args = GetLockArgs(shard->shard_id());
|
||||
DCHECK(lock_args.args.size() > 0 || (multi_ && multi_->mode == LOCK_INCREMENTAL));
|
||||
DCHECK(lock_args.args.size() > 0);
|
||||
shard->db_slice().Release(mode, lock_args);
|
||||
sd.local_mask &= ~KEYLOCK_ACQUIRED;
|
||||
}
|
||||
|
@ -1115,7 +1057,7 @@ bool Transaction::CancelShardCb(EngineShard* shard) {
|
|||
|
||||
// runs in engine-shard thread.
|
||||
ArgSlice Transaction::GetShardArgs(ShardId sid) const {
|
||||
DCHECK(!args_.empty() || (multi_ && multi_->IsIncrLocks()));
|
||||
DCHECK(!args_.empty());
|
||||
|
||||
// We can read unique_shard_cnt_ only because ShardArgsInShard is called after IsArmedInShard
|
||||
// barrier.
|
||||
|
|
|
@ -113,11 +113,8 @@ class Transaction {
|
|||
GLOBAL = 1,
|
||||
// Keys are locked ahead during Schedule.
|
||||
LOCK_AHEAD = 2,
|
||||
// Keys are locked incrementally during each new command.
|
||||
// The shards to schedule on are detemined ahead and remain fixed.
|
||||
LOCK_INCREMENTAL = 3,
|
||||
// Each command is executed separately. Equivalent to a pipeline.
|
||||
NON_ATOMIC = 4,
|
||||
NON_ATOMIC = 3,
|
||||
};
|
||||
|
||||
// Squashed parallel execution requires a separate transaction for each shard. Those "stubs"
|
||||
|
@ -206,9 +203,6 @@ class Transaction {
|
|||
// Start multi in LOCK_AHEAD mode with given keys.
|
||||
void StartMultiLockedAhead(DbIndex dbid, CmdArgList keys);
|
||||
|
||||
// Start multi in LOCK_INCREMENTAL mode on given shards.
|
||||
void StartMultiLockedIncr(DbIndex dbid, const std::vector<bool>& shards);
|
||||
|
||||
// Start multi in NON_ATOMIC mode.
|
||||
void StartMultiNonAtomic();
|
||||
|
||||
|
@ -353,17 +347,10 @@ class Transaction {
|
|||
|
||||
// State of a multi transaction.
|
||||
struct MultiData {
|
||||
// Increase lock counts for all current keys for mode. Clear keys.
|
||||
void AddLocks(IntentLock::Mode mode);
|
||||
|
||||
// Whether it locks incrementally.
|
||||
bool IsIncrLocks() const;
|
||||
|
||||
MultiRole role;
|
||||
MultiMode mode;
|
||||
|
||||
absl::flat_hash_map<std::string, LockCnt> lock_counts;
|
||||
std::vector<std::string> keys;
|
||||
|
||||
// The shard_journal_write vector variable is used to determine the number of shards
|
||||
// involved in a multi-command transaction. This information is utilized by replicas when
|
||||
|
@ -386,12 +373,10 @@ class Transaction {
|
|||
};
|
||||
|
||||
struct PerShardCache {
|
||||
bool requested_active = false; // Activate on shard regardless of presence of keys.
|
||||
std::vector<std::string_view> args;
|
||||
std::vector<uint32_t> original_index;
|
||||
|
||||
void Clear() {
|
||||
requested_active = false;
|
||||
args.clear();
|
||||
original_index.clear();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue