mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-11 10:25:47 +02:00
fix(stream_family): Fix replication for the XADD and XTRIM commands (#4591)
* chore(stream): Revert changes in the redis code Signed-off-by: Stepan Bagritsevich <stefan@dragonflydb.io> * fix(stream_family): Fix replication for the XADD and XTRIM commands Signed-off-by: Stepan Bagritsevich <stefan@dragonflydb.io> --------- Signed-off-by: Stepan Bagritsevich <stefan@dragonflydb.io>
This commit is contained in:
parent
fedca95e56
commit
f3ce3ce0c8
6 changed files with 93 additions and 73 deletions
|
@ -76,8 +76,7 @@ struct StringCollectorTranslator : public ObjectExplorer {
|
||||||
values.emplace_back(str);
|
values.emplace_back(str);
|
||||||
}
|
}
|
||||||
void OnArrayStart(unsigned len) final {
|
void OnArrayStart(unsigned len) final {
|
||||||
CHECK(values.empty());
|
values.reserve(values.size() + len);
|
||||||
values.reserve(len);
|
|
||||||
}
|
}
|
||||||
void OnArrayEnd() final {
|
void OnArrayEnd() final {
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,9 +125,6 @@ typedef struct {
|
||||||
/* Prototypes of exported APIs. */
|
/* Prototypes of exported APIs. */
|
||||||
// struct client;
|
// struct client;
|
||||||
|
|
||||||
// Use this to in streamTrimByLength and streamTrimByID
|
|
||||||
#define NO_TRIM_LIMIT (-1)
|
|
||||||
|
|
||||||
/* Flags for streamCreateConsumer */
|
/* Flags for streamCreateConsumer */
|
||||||
#define SCC_DEFAULT 0
|
#define SCC_DEFAULT 0
|
||||||
#define SCC_NO_NOTIFY (1<<0) /* Do not notify key space if consumer created */
|
#define SCC_NO_NOTIFY (1<<0) /* Do not notify key space if consumer created */
|
||||||
|
@ -166,12 +163,9 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||||
int streamDeleteItem(stream *s, streamID *id);
|
int streamDeleteItem(stream *s, streamID *id);
|
||||||
void streamGetEdgeID(stream *s, int first, int skip_tombstones, streamID *edge_id);
|
void streamGetEdgeID(stream *s, int first, int skip_tombstones, streamID *edge_id);
|
||||||
long long streamEstimateDistanceFromFirstEverEntry(stream *s, streamID *id);
|
long long streamEstimateDistanceFromFirstEverEntry(stream *s, streamID *id);
|
||||||
int64_t streamTrim(stream *s, streamAddTrimArgs *args, streamID *last_id);
|
int64_t streamTrim(stream *s, streamAddTrimArgs *args);
|
||||||
|
int64_t streamTrimByLength(stream *s, long long maxlen, int approx);
|
||||||
// If you don't want to specify a limit, use NO_TRIM_LIMIT
|
int64_t streamTrimByID(stream *s, streamID minid, int approx);
|
||||||
int64_t streamTrimByLength(stream *s, long long maxlen, int approx, streamID *last_id, long long limit);
|
|
||||||
int64_t streamTrimByID(stream *s, streamID minid, int approx, streamID *last_id, long long limit);
|
|
||||||
|
|
||||||
void streamFreeCG(streamCG *cg);
|
void streamFreeCG(streamCG *cg);
|
||||||
void streamDelConsumer(streamCG *cg, streamConsumer *consumer);
|
void streamDelConsumer(streamCG *cg, streamConsumer *consumer);
|
||||||
void streamLastValidID(stream *s, streamID *maxid);
|
void streamLastValidID(stream *s, streamID *maxid);
|
||||||
|
|
|
@ -300,7 +300,7 @@ void streamGetEdgeID(stream *s, int first, int skip_tombstones, streamID *edge_i
|
||||||
* that should be trimmed, there is a chance we will still have entries with
|
* that should be trimmed, there is a chance we will still have entries with
|
||||||
* IDs < 'id' (or number of elements >= maxlen in case of MAXLEN).
|
* IDs < 'id' (or number of elements >= maxlen in case of MAXLEN).
|
||||||
*/
|
*/
|
||||||
int64_t streamTrim(stream *s, streamAddTrimArgs *args, streamID *last_id) {
|
int64_t streamTrim(stream *s, streamAddTrimArgs *args) {
|
||||||
size_t maxlen = args->maxlen;
|
size_t maxlen = args->maxlen;
|
||||||
streamID *id = &args->minid;
|
streamID *id = &args->minid;
|
||||||
int approx = args->approx_trim;
|
int approx = args->approx_trim;
|
||||||
|
@ -315,8 +315,6 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args, streamID *last_id) {
|
||||||
raxSeek(&ri,"^",NULL,0);
|
raxSeek(&ri,"^",NULL,0);
|
||||||
|
|
||||||
int64_t deleted = 0;
|
int64_t deleted = 0;
|
||||||
streamID last_deleted_id = {0, 0}; // Initialize last deleted ID
|
|
||||||
|
|
||||||
while (raxNext(&ri)) {
|
while (raxNext(&ri)) {
|
||||||
if (trim_strategy == TRIM_STRATEGY_MAXLEN && s->length <= maxlen)
|
if (trim_strategy == TRIM_STRATEGY_MAXLEN && s->length <= maxlen)
|
||||||
break;
|
break;
|
||||||
|
@ -333,24 +331,16 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args, streamID *last_id) {
|
||||||
streamID master_id = {0}; /* For MINID */
|
streamID master_id = {0}; /* For MINID */
|
||||||
if (trim_strategy == TRIM_STRATEGY_MAXLEN) {
|
if (trim_strategy == TRIM_STRATEGY_MAXLEN) {
|
||||||
remove_node = s->length - entries >= maxlen;
|
remove_node = s->length - entries >= maxlen;
|
||||||
if (remove_node) {
|
|
||||||
streamDecodeID(ri.key, &master_id);
|
|
||||||
// Write last ID to last_deleted_id
|
|
||||||
lpGetEdgeStreamID(lp, 0, &master_id, &last_deleted_id);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/* Read the master ID from the radix tree key. */
|
/* Read the master ID from the radix tree key. */
|
||||||
streamDecodeID(ri.key, &master_id);
|
streamDecodeID(ri.key, &master_id);
|
||||||
|
|
||||||
/* Read last ID. */
|
/* Read last ID. */
|
||||||
streamID last_id = {0, 0};
|
streamID last_id = {0, 0};
|
||||||
lpGetEdgeStreamID(lp, 0, &master_id, &last_id);
|
lpGetEdgeStreamID(lp, 0, &master_id, &last_id);
|
||||||
|
|
||||||
/* We can remove the entire node id its last ID < 'id' */
|
/* We can remove the entire node id its last ID < 'id' */
|
||||||
remove_node = streamCompareID(&last_id, id) < 0;
|
remove_node = streamCompareID(&last_id, id) < 0;
|
||||||
if (remove_node) {
|
|
||||||
last_deleted_id = last_id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remove_node) {
|
if (remove_node) {
|
||||||
|
@ -366,10 +356,6 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args, streamID *last_id) {
|
||||||
* stop here. */
|
* stop here. */
|
||||||
if (approx) break;
|
if (approx) break;
|
||||||
|
|
||||||
if (trim_strategy == TRIM_STRATEGY_MAXLEN) {
|
|
||||||
streamDecodeID(ri.key, &master_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now we have to trim entries from within 'lp' */
|
/* Now we have to trim entries from within 'lp' */
|
||||||
int64_t deleted_from_lp = 0;
|
int64_t deleted_from_lp = 0;
|
||||||
|
|
||||||
|
@ -400,7 +386,11 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args, streamID *last_id) {
|
||||||
int64_t seq_delta = lpGetInteger(p);
|
int64_t seq_delta = lpGetInteger(p);
|
||||||
p = lpNext(lp, p); /* Skip ID seq delta */
|
p = lpNext(lp, p); /* Skip ID seq delta */
|
||||||
|
|
||||||
streamID currid = {master_id.ms + ms_delta, master_id.seq + seq_delta};
|
streamID currid = {0}; /* For MINID */
|
||||||
|
if (trim_strategy == TRIM_STRATEGY_MINID) {
|
||||||
|
currid.ms = master_id.ms + ms_delta;
|
||||||
|
currid.seq = master_id.seq + seq_delta;
|
||||||
|
}
|
||||||
|
|
||||||
int stop;
|
int stop;
|
||||||
if (trim_strategy == TRIM_STRATEGY_MAXLEN) {
|
if (trim_strategy == TRIM_STRATEGY_MAXLEN) {
|
||||||
|
@ -432,7 +422,6 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args, streamID *last_id) {
|
||||||
deleted_from_lp++;
|
deleted_from_lp++;
|
||||||
s->length--;
|
s->length--;
|
||||||
p = lp + delta;
|
p = lp + delta;
|
||||||
last_deleted_id = currid;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deleted += deleted_from_lp;
|
deleted += deleted_from_lp;
|
||||||
|
@ -469,42 +458,29 @@ int64_t streamTrim(stream *s, streamAddTrimArgs *args, streamID *last_id) {
|
||||||
streamGetEdgeID(s,1,1,&s->first_id);
|
streamGetEdgeID(s,1,1,&s->first_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set the last deleted ID, if applicable. */
|
|
||||||
if (last_id) {
|
|
||||||
*last_id = last_deleted_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trims a stream by length. Returns the number of deleted items. */
|
/* Trims a stream by length. Returns the number of deleted items. */
|
||||||
int64_t streamTrimByLength(stream *s, long long maxlen, int approx, streamID *last_id, long long limit) {
|
int64_t streamTrimByLength(stream *s, long long maxlen, int approx) {
|
||||||
if (limit == NO_TRIM_LIMIT) {
|
|
||||||
limit = approx ? 100 * server.stream_node_max_entries : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
streamAddTrimArgs args = {
|
streamAddTrimArgs args = {
|
||||||
.trim_strategy = TRIM_STRATEGY_MAXLEN,
|
.trim_strategy = TRIM_STRATEGY_MAXLEN,
|
||||||
.approx_trim = approx,
|
.approx_trim = approx,
|
||||||
.limit = limit,
|
.limit = approx ? 100 * server.stream_node_max_entries : 0,
|
||||||
.maxlen = maxlen
|
.maxlen = maxlen
|
||||||
};
|
};
|
||||||
return streamTrim(s, &args, last_id);
|
return streamTrim(s, &args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trims a stream by minimum ID. Returns the number of deleted items. */
|
/* Trims a stream by minimum ID. Returns the number of deleted items. */
|
||||||
int64_t streamTrimByID(stream *s, streamID minid, int approx, streamID *last_id, long long limit) {
|
int64_t streamTrimByID(stream *s, streamID minid, int approx) {
|
||||||
if (limit == NO_TRIM_LIMIT) {
|
|
||||||
limit = approx ? 100 * server.stream_node_max_entries : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
streamAddTrimArgs args = {
|
streamAddTrimArgs args = {
|
||||||
.trim_strategy = TRIM_STRATEGY_MINID,
|
.trim_strategy = TRIM_STRATEGY_MINID,
|
||||||
.approx_trim = approx,
|
.approx_trim = approx,
|
||||||
.limit = limit,
|
.limit = approx ? 100 * server.stream_node_max_entries : 0,
|
||||||
.minid = minid
|
.minid = minid
|
||||||
};
|
};
|
||||||
return streamTrim(s, &args, last_id);
|
return streamTrim(s, &args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the stream iterator, so that we can call iterating functions
|
/* Initialize the stream iterator, so that we can call iterating functions
|
||||||
|
|
|
@ -75,6 +75,12 @@ struct RangeId {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TrimOpts {
|
struct TrimOpts {
|
||||||
|
static constexpr int32_t kNoTrimLimit = -1;
|
||||||
|
|
||||||
|
bool HasLimit() const {
|
||||||
|
return limit != kNoTrimLimit;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsMaxLen() const {
|
bool IsMaxLen() const {
|
||||||
return std::holds_alternative<uint32_t>(length_or_id);
|
return std::holds_alternative<uint32_t>(length_or_id);
|
||||||
}
|
}
|
||||||
|
@ -89,7 +95,7 @@ struct TrimOpts {
|
||||||
|
|
||||||
// First is MaxLen, second is MinId.
|
// First is MaxLen, second is MinId.
|
||||||
std::variant<uint32_t, ParsedStreamId> length_or_id;
|
std::variant<uint32_t, ParsedStreamId> length_or_id;
|
||||||
int32_t limit = NO_TRIM_LIMIT;
|
int32_t limit = kNoTrimLimit;
|
||||||
bool approx = false;
|
bool approx = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -631,21 +637,30 @@ std::string StreamsIdToString(streamID id) {
|
||||||
return absl::StrCat(id.ms, "-", id.seq);
|
return absl::StrCat(id.ms, "-", id.seq);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The first value represents the number of deleted items, and the second is the last trimmed ID. */
|
/* Return value represents the number of deleted items. */
|
||||||
std::pair<int64_t, streamID> TrimStream(const TrimOpts& opts, stream* s) {
|
int64_t TrimStream(const TrimOpts& opts, stream* s) {
|
||||||
streamID last_id = {0, 0};
|
if (!opts.HasLimit()) {
|
||||||
|
|
||||||
auto trim = [&]() {
|
|
||||||
if (opts.IsMaxLen()) {
|
if (opts.IsMaxLen()) {
|
||||||
return streamTrimByLength(s, opts.AsMaxLen(), opts.approx, &last_id, opts.limit);
|
return streamTrimByLength(s, opts.AsMaxLen(), opts.approx);
|
||||||
} else {
|
} else {
|
||||||
const auto& min_id = opts.AsMinId().val;
|
const auto& min_id = opts.AsMinId().val;
|
||||||
return streamTrimByID(s, min_id, opts.approx, &last_id, opts.limit);
|
return streamTrimByID(s, min_id, opts.approx);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const int64_t deleted_items_number = trim();
|
streamAddTrimArgs trim_args = {};
|
||||||
return {deleted_items_number, last_id};
|
trim_args.approx_trim = opts.approx;
|
||||||
|
trim_args.limit = opts.limit;
|
||||||
|
|
||||||
|
if (opts.IsMaxLen()) {
|
||||||
|
trim_args.trim_strategy = TRIM_STRATEGY_MAXLEN;
|
||||||
|
trim_args.maxlen = opts.AsMaxLen();
|
||||||
|
} else {
|
||||||
|
trim_args.trim_strategy = TRIM_STRATEGY_MINID;
|
||||||
|
trim_args.minid = opts.AsMinId().val;
|
||||||
|
}
|
||||||
|
|
||||||
|
return streamTrim(s, &trim_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JournalAsMinId(const TrimOpts& opts) {
|
bool JournalAsMinId(const TrimOpts& opts) {
|
||||||
|
@ -697,30 +712,44 @@ OpResult<streamID> OpAdd(const OpArgs& op_args, string_view key, const AddOpts&
|
||||||
return OpStatus::OUT_OF_MEMORY;
|
return OpStatus::OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<int64_t, streamID> trim_result{};
|
|
||||||
if (opts.trim_opts) {
|
if (opts.trim_opts) {
|
||||||
trim_result = TrimStream(opts.trim_opts.value(), stream_inst);
|
int64_t deleted_items_number = TrimStream(opts.trim_opts.value(), stream_inst);
|
||||||
|
VLOG(2) << "Trimmed " << deleted_items_number << " items from stream " << key
|
||||||
|
<< " during the XADD command";
|
||||||
}
|
}
|
||||||
|
|
||||||
mem_tracker.UpdateStreamSize(it->second);
|
mem_tracker.UpdateStreamSize(it->second);
|
||||||
|
|
||||||
if (op_args.shard->journal()) {
|
if (op_args.shard->journal()) {
|
||||||
std::string result_id_as_string = StreamsIdToString(result_id);
|
std::string result_id_as_string = StreamsIdToString(result_id);
|
||||||
|
const bool stream_is_empty = stream_inst->length == 0;
|
||||||
|
|
||||||
if (opts.trim_opts && JournalAsMinId(opts.trim_opts.value())) {
|
if (opts.trim_opts && (stream_is_empty || JournalAsMinId(opts.trim_opts.value()))) {
|
||||||
// We need to set exact MinId in the journal.
|
std::string last_id;
|
||||||
std::string last_id = StreamsIdToString(trim_result.second);
|
|
||||||
CmdArgVec journal_args = {key, "MINID"sv, "="sv, last_id};
|
CmdArgVec journal_args = {key};
|
||||||
journal_args.reserve(args.size() + 4);
|
journal_args.reserve(args.size() + 4);
|
||||||
|
|
||||||
if (opts.no_mkstream) {
|
if (stream_is_empty) {
|
||||||
journal_args.push_back("NOMKSTREAM"sv);
|
// We need remove the whole stream in replica
|
||||||
|
journal_args.emplace_back("MAXLEN"sv);
|
||||||
|
journal_args.emplace_back("0"sv);
|
||||||
|
} else {
|
||||||
|
// We need to set exact MinId in the journal.
|
||||||
|
// For this we are using new first_id from the stream
|
||||||
|
last_id = StreamsIdToString(stream_inst->first_id);
|
||||||
|
journal_args.emplace_back("MINID"sv);
|
||||||
|
journal_args.emplace_back(last_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
journal_args.push_back(result_id_as_string);
|
if (opts.no_mkstream) {
|
||||||
|
journal_args.emplace_back("NOMKSTREAM"sv);
|
||||||
|
}
|
||||||
|
|
||||||
|
journal_args.emplace_back(result_id_as_string);
|
||||||
|
|
||||||
for (size_t i = 0; i < args.size(); i++) {
|
for (size_t i = 0; i < args.size(); i++) {
|
||||||
journal_args.push_back(args[i]);
|
journal_args.emplace_back(args[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordJournal(op_args, "XADD"sv, journal_args);
|
RecordJournal(op_args, "XADD"sv, journal_args);
|
||||||
|
@ -2006,16 +2035,24 @@ OpResult<int64_t> OpTrim(const OpArgs& op_args, std::string_view key, const Trim
|
||||||
CompactObj& cobj = res_it->it->second;
|
CompactObj& cobj = res_it->it->second;
|
||||||
stream* s = (stream*)cobj.RObjPtr();
|
stream* s = (stream*)cobj.RObjPtr();
|
||||||
|
|
||||||
auto res = TrimStream(opts, s);
|
int64_t deleted_items_number = TrimStream(opts, s);
|
||||||
|
|
||||||
mem_tracker.UpdateStreamSize(cobj);
|
mem_tracker.UpdateStreamSize(cobj);
|
||||||
|
|
||||||
if (op_args.shard->journal() && journal_as_minid) {
|
if (op_args.shard->journal() && journal_as_minid) {
|
||||||
std::string last_id = StreamsIdToString(res.second);
|
const bool stream_is_empty = s->length == 0;
|
||||||
RecordJournal(op_args, "XTRIM"sv, ArgSlice{key, "MINID"sv, "="sv, last_id});
|
if (stream_is_empty) {
|
||||||
|
// We need remove the whole stream in replica
|
||||||
|
RecordJournal(op_args, "XTRIM"sv, ArgSlice{key, "MAXLEN"sv, "0"sv});
|
||||||
|
} else {
|
||||||
|
// We need to set exact MinId in the journal.
|
||||||
|
// For this we are using new first_id from the stream
|
||||||
|
std::string last_id = StreamsIdToString(s->first_id);
|
||||||
|
RecordJournal(op_args, "XTRIM"sv, ArgSlice{key, "MINID"sv, last_id});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.first;
|
return deleted_items_number;
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseResult<TrimOpts> ParseTrimOpts(bool max_len, CmdArgParser* parser) {
|
ParseResult<TrimOpts> ParseTrimOpts(bool max_len, CmdArgParser* parser) {
|
||||||
|
|
|
@ -2790,6 +2790,16 @@ async def test_stream_approximate_trimming(df_factory):
|
||||||
replica_data = await StaticSeeder.capture(c_replica)
|
replica_data = await StaticSeeder.capture(c_replica)
|
||||||
assert master_data == replica_data
|
assert master_data == replica_data
|
||||||
|
|
||||||
|
# Step 3: Trim all streams to 0
|
||||||
|
for i in range(num_streams):
|
||||||
|
stream_name = f"stream{i}"
|
||||||
|
await c_master.execute_command("XTRIM", stream_name, "MAXLEN", "0")
|
||||||
|
|
||||||
|
# Check replica data consistent
|
||||||
|
master_data = await StaticSeeder.capture(c_master)
|
||||||
|
replica_data = await StaticSeeder.capture(c_replica)
|
||||||
|
assert master_data == replica_data
|
||||||
|
|
||||||
|
|
||||||
async def test_preempt_in_atomic_section_of_heartbeat(df_factory: DflyInstanceFactory):
|
async def test_preempt_in_atomic_section_of_heartbeat(df_factory: DflyInstanceFactory):
|
||||||
master = df_factory.create(proactor_threads=1, serialization_max_chunk_size=100000000000)
|
master = df_factory.create(proactor_threads=1, serialization_max_chunk_size=100000000000)
|
||||||
|
|
|
@ -29,3 +29,7 @@ function LH_funcs.json(key, hash)
|
||||||
-- add values to hash, note JSON.GET returns just a string
|
-- add values to hash, note JSON.GET returns just a string
|
||||||
return dragonfly.ihash(hash, false, 'JSON.GET', key)
|
return dragonfly.ihash(hash, false, 'JSON.GET', key)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function LH_funcs.stream(key, hash)
|
||||||
|
return dragonfly.ihash(hash, false, 'XRANGE', key, '-', '+')
|
||||||
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue