From d1d64eb014661254b4fa2aebc3b7e7d870885cc4 Mon Sep 17 00:00:00 2001 From: Roman Gershman Date: Fri, 17 Jun 2022 10:27:46 +0300 Subject: [PATCH] feat(streams): implement rdb load for streams Signed-off-by: Roman Gershman --- src/redis/object.c | 22 ++-- src/redis/t_stream.c | 48 ++++--- src/server/CMakeLists.txt | 5 +- src/server/rdb_load.cc | 177 ++++++++++++++++++++++++++ src/server/rdb_load.h | 1 + src/server/rdb_test.cc | 11 ++ src/server/testdata/redis6_stream.rdb | Bin 0 -> 36588 bytes 7 files changed, 225 insertions(+), 39 deletions(-) create mode 100644 src/server/testdata/redis6_stream.rdb diff --git a/src/redis/object.c b/src/redis/object.c index 4b70097..45ae81a 100644 --- a/src/redis/object.c +++ b/src/redis/object.c @@ -369,11 +369,12 @@ void freeModuleObject(robj *o) { zfree(mv); } -void freeStreamObject(robj *o) { - freeStream(o->ptr); -} #endif +void freeStreamObject(robj *o) { + freeStream(o->ptr); +} + void incrRefCount(robj *o) { if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) { o->refcount++; @@ -399,8 +400,7 @@ void decrRefCount(robj *o) { // freeModuleObject(o); break; case OBJ_STREAM: - serverPanic("Unsupported OBJ_STREAM type"); - //freeStreamObject(o); + freeStreamObject(o); break; default: serverPanic("Unknown object type"); break; } @@ -658,7 +658,7 @@ size_t stringObjectLen(robj *o) { } } -// ROMAN: Copied from the DISABLED part below +// ROMAN: Copied from the DISABLED part below int getLongLongFromObject(robj *o, long long *target) { long long value; @@ -850,7 +850,7 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) { } } else if (o->type == OBJ_STREAM) { serverPanic("OBJ_STREAM not supported"); -#ifdef ROMAN_CLIENT_DISABLE +#ifdef ROMAN_CLIENT_DISABLE stream *s = o->ptr; asize = sizeof(*o)+sizeof(*s); asize += streamRadixTreeMemoryUsage(s->rax); @@ -911,10 +911,10 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) { } raxStop(&ri); } -#endif +#endif } else if (o->type == OBJ_MODULE) { serverPanic("OBJ_MODULE not supported"); -#ifdef ROMAN_CLIENT_DISABLE +#ifdef ROMAN_CLIENT_DISABLE moduleValue *mv = o->ptr; moduleType *mt = mv->type; if (mt->mem_usage != NULL) { @@ -922,7 +922,7 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) { } else { asize = 0; } -#endif +#endif } else { serverPanic("Unknown object type"); } @@ -1175,7 +1175,7 @@ sds getMemoryDoctorReport(void) { return s; } -#endif +#endif /* Set the object LRU/LFU depending on server.maxmemory_policy. * The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU. diff --git a/src/redis/t_stream.c b/src/redis/t_stream.c index c55eb5e..9ca2577 100644 --- a/src/redis/t_stream.c +++ b/src/redis/t_stream.c @@ -66,7 +66,7 @@ void streamFreeNACK(streamNACK *na); size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamConsumer *consumer); int streamParseStrictIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq, int *seq_given); int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq); -#endif +#endif /* ----------------------------------------------------------------------- * Low level stream encoding: a radix tree of listpacks. @@ -420,7 +420,7 @@ void streamGetEdgeID(stream *s, int first, int skip_tombstones, streamID *edge_i * If 'use_id' is not NULL, the ID is not auto-generated by the function, * but instead the passed ID is used to add the new entry. In this case * adding the entry may fail as specified later in this comment. - * + * * When 'use_id' is used alongside with a zero 'seq-given', the sequence * part of the passed ID is ignored and the function will attempt to use an * auto-generated sequence. @@ -966,7 +966,7 @@ static int streamParseAddOrTrimArgsOrReply(client *c, streamAddTrimArgs *args, i if (streamParseStrictIDOrReply(c,c->argv[i+1],&args->minid,0,NULL) != C_OK) return -1; - + i++; args->trim_strategy = TRIM_STRATEGY_MINID; args->trim_strategy_arg_idx = i; @@ -1026,9 +1026,9 @@ static int streamParseAddOrTrimArgsOrReply(client *c, streamAddTrimArgs *args, i } else { /* User didn't provide LIMIT, we must set it. */ if (args->approx_trim) { - /* In order to prevent from trimming to do too much work and + /* In order to prevent from trimming to do too much work and * cause latency spikes we limit the amount of work it can do. - * We have to cap args->limit from both sides in case + * We have to cap args->limit from both sides in case * stream_node_max_entries is 0 or too big (could cause overflow) */ args->limit = 100 * server.stream_node_max_entries; /* Maximum 100 rax nodes. */ @@ -1044,7 +1044,7 @@ static int streamParseAddOrTrimArgsOrReply(client *c, streamAddTrimArgs *args, i return i; } -#endif +#endif /* Initialize the stream iterator, so that we can call iterating functions * to get the next items. This requires a corresponding streamIteratorStop() @@ -1382,7 +1382,7 @@ void streamLastValidID(stream *s, streamID *maxid) streamIteratorStop(&si); } -#if ROMAN_ENABLE +#if ROMAN_ENABLE /* Emit a reply in the client output buffer by formatting a Stream ID * in the standard - format, using the simple string protocol * of REPL. */ @@ -1396,7 +1396,7 @@ void setDeferredReplyStreamID(client *c, void *dr, streamID *id) { setDeferredReplyBulkSds(c, dr, replyid); } -#endif +#endif /* Similar to the above function, but just creates an object, usually useful * for replication purposes to create arguments. */ @@ -1453,7 +1453,7 @@ int streamRangeHasTombstones(stream *s, streamID *start, streamID *end) { return 0; } -#if ROMAN_ENABLE +#if ROMAN_ENABLE /* Replies with a consumer group's current lag, that is the number of messages * in the stream that are yet to be delivered. In case that the lag isn't * available due to fragmentation, the reply to the client is a null. */ @@ -1490,7 +1490,7 @@ void streamReplyWithCGLag(client *c, stream *s, streamCG *cg) { /* This function returns a value that is the ID's logical read counter, or its * distance (the number of entries) from the first entry ever to have been added * to the stream. - * + * * A counter is returned only in one of the following cases: * 1. The ID is the same as the stream's last ID. In this case, the returned * is the same as the stream's entries_added counter. @@ -1624,7 +1624,7 @@ void streamPropagateConsumerCreation(client *c, robj *key, robj *groupname, sds decrRefCount(argv[4]); } -#endif +#endif /* Send the stream items in the specified range to the client 'c'. The range * the client will receive is between start and end inclusive, if 'count' is @@ -1706,7 +1706,7 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end /* Update the group last_id if needed. */ if (group && streamCompareID(&id,&group->last_id) > 0) { if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&id,NULL)) { - /* A valid counter and no future tombstones mean we can + /* A valid counter and no future tombstones mean we can * increment the read counter to keep tracking the group's * progress. */ group->entries_read++; @@ -1882,7 +1882,7 @@ robj *streamTypeLookupWriteOrCreate(client *c, robj *key, int no_create) { * to be autogenerated. When a non-NULL 'seq_given' argument is provided, this * form is accepted and the argument is set to 0 unless the sequence part is * specified. - * + * * If 'c' is set to NULL, no reply is sent to the client. */ int streamGenericParseIDOrReply(client *c, const robj *o, streamID *id, uint64_t missing_seq, int strict, int *seq_given) { char buf[128]; @@ -1954,7 +1954,7 @@ int streamParseStrictIDOrReply(client *c, robj *o, streamID *id, uint64_t missin /* Helper for parsing a stream ID that is a range query interval. When the * exclude argument is NULL, streamParseIDOrReply() is called and the interval - * is treated as close (inclusive). Otherwise, the exclude argument is set if + * is treated as close (inclusive). Otherwise, the exclude argument is set if * the interval is open (the "(" prefix) and streamParseStrictIDOrReply() is * called in that case. */ @@ -1962,13 +1962,13 @@ int streamParseIntervalIDOrReply(client *c, robj *o, streamID *id, int *exclude, char *p = o->ptr; size_t len = sdslen(p); int invalid = 0; - + if (exclude != NULL) *exclude = (len > 1 && p[0] == '('); if (exclude != NULL && *exclude) { robj *t = createStringObject(p+1,len-1); invalid = (streamParseStrictIDOrReply(c,t,id,missing_seq,NULL) == C_ERR); decrRefCount(t); - } else + } else invalid = (streamParseIDOrReply(c,o,id,missing_seq) == C_ERR); if (invalid) return C_ERR; @@ -2095,7 +2095,7 @@ void xrangeGenericCommand(client *c, int rev) { robj *startarg = rev ? c->argv[3] : c->argv[2]; robj *endarg = rev ? c->argv[2] : c->argv[3]; int startex = 0, endex = 0; - + /* Parse start and end IDs. */ if (streamParseIntervalIDOrReply(c,startarg,&startid,&startex,0) != C_OK) return; @@ -2447,7 +2447,7 @@ cleanup: /* Cleanup. */ zfree(groups); } -#endif +#endif /* ----------------------------------------------------------------------- * Low level implementation of consumer groups @@ -2515,15 +2515,12 @@ streamCG *streamLookupCG(stream *s, sds groupname) { return (cg == raxNotFound) ? NULL : cg; } -#if ROMAN_ENABLE /* Create a consumer with the specified name in the group 'cg' and return. * If the consumer exists, return NULL. As a side effect, when the consumer * is successfully created, the key space will be notified and dirty++ unless * the SCC_NO_NOTIFY or SCC_NO_DIRTIFY flags is specified. */ streamConsumer *streamCreateConsumer(streamCG *cg, sds name, robj *key, int dbid, int flags) { if (cg == NULL) return NULL; - int notify = !(flags & SCC_NO_NOTIFY); - int dirty = !(flags & SCC_NO_DIRTIFY); streamConsumer *consumer = zmalloc(sizeof(*consumer)); int success = raxTryInsert(cg->consumers,(unsigned char*)name, sdslen(name),consumer,NULL); @@ -2534,13 +2531,11 @@ streamConsumer *streamCreateConsumer(streamCG *cg, sds name, robj *key, int dbid consumer->name = sdsdup(name); consumer->pel = raxNew(); consumer->seen_time = mstime(); - if (dirty) server.dirty++; - if (notify) notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-createconsumer",key,dbid); + return consumer; } -#endif -/* Lookup the consumer with the specified name in the group 'cg'. Its last +/* Lookup the consumer with the specified name in the group 'cg'. Its last * seen time is updated unless the SLC_NO_REFRESH flag is specified. */ streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int flags) { if (cg == NULL) return NULL; @@ -3943,6 +3938,8 @@ NULL } } +#endif + /* Validate the integrity stream listpack entries structure. Both in term of a * valid listpack, but also that the structure of the entries matches a valid * stream. return 1 if valid 0 if not valid. */ @@ -4035,4 +4032,3 @@ int streamValidateListpackIntegrity(unsigned char *lp, size_t size, int deep) { return 1; } -#endif \ No newline at end of file diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 4ac3a88..735b245 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -1,7 +1,7 @@ add_executable(dragonfly dfly_main.cc) cxx_link(dragonfly base dragonfly_lib) -add_library(dragonfly_lib blocking_controller.cc channel_slice.cc command_registry.cc +add_library(dragonfly_lib blocking_controller.cc channel_slice.cc command_registry.cc common.cc config_flags.cc conn_context.cc db_slice.cc debugcmd.cc engine_shard_set.cc generic_family.cc hset_family.cc io_mgr.cc @@ -22,7 +22,8 @@ cxx_test(list_family_test dfly_test_lib LABELS DFLY) cxx_test(set_family_test dfly_test_lib LABELS DFLY) cxx_test(stream_family_test dfly_test_lib LABELS DFLY) cxx_test(string_family_test dfly_test_lib LABELS DFLY) -cxx_test(rdb_test dfly_test_lib DATA testdata/empty.rdb testdata/redis6_small.rdb LABELS DFLY) +cxx_test(rdb_test dfly_test_lib DATA testdata/empty.rdb testdata/redis6_small.rdb + testdata/redis6_stream.rdb LABELS DFLY) cxx_test(zset_family_test dfly_test_lib LABELS DFLY) cxx_test(blocking_controller_test dragonfly_lib LABELS DFLY) diff --git a/src/server/rdb_load.cc b/src/server/rdb_load.cc index 3b0ab9e..8f44642 100644 --- a/src/server/rdb_load.cc +++ b/src/server/rdb_load.cc @@ -10,6 +10,7 @@ extern "C" { #include "redis/listpack.h" #include "redis/lzfP.h" /* LZF compression library */ #include "redis/rdb.h" +#include "redis/stream.h" #include "redis/util.h" #include "redis/ziplist.h" #include "redis/zmalloc.h" @@ -834,6 +835,9 @@ io::Result RdbLoader::ReadObj(int rdbtype) { case RDB_TYPE_LIST_QUICKLIST: res_obj = ReadListQuicklist(rdbtype); break; + case RDB_TYPE_STREAM_LISTPACKS: + res_obj = ReadStreams(); + break; default: LOG(ERROR) << "Unsupported rdb type " << rdbtype; return Unexpected(errc::invalid_encoding); @@ -1264,6 +1268,179 @@ io::Result RdbLoader::ReadListQuicklist(int rdbtype) { return res; } +::io::Result RdbLoader::ReadStreams() { + uint64_t listpacks; + SET_OR_UNEXPECT(LoadLen(nullptr), listpacks); + + robj* o = createStreamObject(); + stream* s = (stream*)o->ptr; + + auto cleanup = absl::Cleanup([&] { decrRefCount(o); }); + + while (listpacks--) { + /* Get the master ID, the one we'll use as key of the radix tree + * node: the entries inside the listpack itself are delta-encoded + * relatively to this ID. */ + sds nodekey; + SET_OR_UNEXPECT(ReadKey(), nodekey); + auto cleanup2 = absl::Cleanup([&] { sdsfree(nodekey); }); + + if (sdslen(nodekey) != sizeof(streamID)) { + LOG(ERROR) << "Stream node key entry is not the size of a stream ID"; + + return Unexpected(errc::rdb_file_corrupted); + } + + /* Load the listpack. */ + OpaqueBuf fetch; + SET_OR_UNEXPECT(FetchGenericString(RDB_LOAD_PLAIN), fetch); + if (fetch.second == 0 || fetch.first == nullptr) { + LOG(ERROR) << "Stream listpacks loading failed"; + return Unexpected(errc::rdb_file_corrupted); + } + DCHECK(fetch.first); + + uint8_t* lp = (uint8_t*)fetch.first; + + if (!streamValidateListpackIntegrity(lp, fetch.second, 0)) { + zfree(lp); + LOG(ERROR) << "Stream listpack integrity check failed."; + return Unexpected(errc::rdb_file_corrupted); + } + + unsigned char* first = lpFirst(lp); + if (first == NULL) { + /* Serialized listpacks should never be empty, since on + * deletion we should remove the radix tree key if the + * resulting listpack is empty. */ + LOG(ERROR) << "Empty listpack inside stream"; + zfree(lp); + return Unexpected(errc::rdb_file_corrupted); + } + + /* Insert the key in the radix tree. */ + int retval = raxTryInsert(s->rax_tree, (unsigned char*)nodekey, sizeof(streamID), lp, NULL); + if (!retval) { + LOG(ERROR) << "Listpack re-added with existing key"; + return Unexpected(errc::duplicate_key); + } + } + + /* Load total number of items inside the stream. */ + SET_OR_UNEXPECT(LoadLen(nullptr), s->length); + + /* Load the last entry ID. */ + SET_OR_UNEXPECT(LoadLen(nullptr), s->last_id.ms); + SET_OR_UNEXPECT(LoadLen(nullptr), s->last_id.seq); + + /* Consumer groups loading */ + uint64_t cgroups_count; + SET_OR_UNEXPECT(LoadLen(nullptr), cgroups_count); + + while (cgroups_count--) { + /* Get the consumer group name and ID. We can then create the + * consumer group ASAP and populate its structure as + * we read more data. */ + streamID cg_id; + + sds cgname; + SET_OR_UNEXPECT(ReadKey(), cgname); + + auto cleanup2 = absl::Cleanup([&] { sdsfree(cgname); }); + SET_OR_UNEXPECT(LoadLen(nullptr), cg_id.ms); + SET_OR_UNEXPECT(LoadLen(nullptr), cg_id.seq); + + streamCG* cgroup = streamCreateCG(s, cgname, sdslen(cgname), &cg_id, 0); + if (cgroup == NULL) { + LOG(ERROR) << "Duplicated consumer group name " << cgname; + return Unexpected(errc::duplicate_key); + } + std::move(cleanup2).Invoke(); + // no need to free cgroup because it's attached to s. + + /* Load the global PEL for this consumer group, however we'll + * not yet populate the NACK structures with the message + * owner, since consumers for this group and their messages will + * be read as a next step. So for now leave them not resolved + * and later populate it. */ + uint64_t pel_size; + SET_OR_UNEXPECT(LoadLen(nullptr), pel_size); + + while (pel_size--) { + uint8_t rawid[sizeof(streamID)]; + error_code ec = FetchBuf(sizeof(rawid), rawid); + if (ec) { + LOG(ERROR) << "Stream PEL ID loading failed."; + return make_unexpected(ec); + } + + streamNACK* nack = streamCreateNACK(NULL); + auto cleanup2 = absl::Cleanup([&] { streamFreeNACK(nack); }); + + SET_OR_UNEXPECT(FetchInt(), nack->delivery_time); + SET_OR_UNEXPECT(LoadLen(nullptr), nack->delivery_count); + + if (!raxTryInsert(cgroup->pel, rawid, sizeof(rawid), nack, NULL)) { + LOG(ERROR) << "Duplicated global PEL entry loading stream consumer group"; + return Unexpected(errc::duplicate_key); + } + std::move(cleanup2).Cancel(); + } + + /* Now that we loaded our global PEL, we need to load the + * consumers and their local PELs. */ + uint64_t consumers_num; + SET_OR_UNEXPECT(LoadLen(nullptr), consumers_num); + + while (consumers_num--) { + sds cname; + SET_OR_UNEXPECT(ReadKey(), cname); + + streamConsumer* consumer = + streamCreateConsumer(cgroup, cname, NULL, 0, SCC_NO_NOTIFY | SCC_NO_DIRTIFY); + sdsfree(cname); + if (!consumer) { + LOG(ERROR) << "Duplicate stream consumer detected."; + return Unexpected(errc::duplicate_key); + } + // no need to free consumer because it's attached to cgroup. + + SET_OR_UNEXPECT(FetchInt(), consumer->seen_time); + + /* Load the PEL about entries owned by this specific + * consumer. */ + SET_OR_UNEXPECT(LoadLen(nullptr), pel_size); + while (pel_size--) { + unsigned char rawid[sizeof(streamID)]; + error_code ec = FetchBuf(sizeof(rawid), rawid); + if (ec) { + LOG(ERROR) << "Stream PEL ID loading failed."; + return make_unexpected(ec); + } + streamNACK* nack = (streamNACK*)raxFind(cgroup->pel, rawid, sizeof(rawid)); + if (nack == raxNotFound) { + LOG(ERROR) << "Consumer entry not found in group global PEL"; + return Unexpected(errc::rdb_file_corrupted); + } + + /* Set the NACK consumer, that was left to NULL when + * loading the global PEL. Then set the same shared + * NACK structure also in the consumer-specific PEL. */ + nack->consumer = consumer; + if (!raxTryInsert(consumer->pel, rawid, sizeof(rawid), nack, NULL)) { + LOG(ERROR) << "Duplicated consumer PEL entry loading a stream consumer group"; + streamFreeNACK(nack); + return Unexpected(errc::duplicate_key); + } + } + } // while (consumers_num) + } // while (cgroup_num) + + std::move(cleanup).Cancel(); + + return o; +} + void RdbLoader::ResizeDb(size_t key_num, size_t expire_num) { DCHECK_LT(key_num, 1U << 31); DCHECK_LT(expire_num, 1U << 31); diff --git a/src/server/rdb_load.h b/src/server/rdb_load.h index 14f5cbe..3e4b645 100644 --- a/src/server/rdb_load.h +++ b/src/server/rdb_load.h @@ -72,6 +72,7 @@ class RdbLoader { ::io::Result ReadZSet(int rdbtype); ::io::Result ReadZSetZL(); ::io::Result ReadListQuicklist(int rdbtype); + ::io::Result ReadStreams(); std::error_code EnsureRead(size_t min_sz) { if (mem_buf_.InputLen() >= min_sz) diff --git a/src/server/rdb_test.cc b/src/server/rdb_test.cc index 9504d56..4154a22 100644 --- a/src/server/rdb_test.cc +++ b/src/server/rdb_test.cc @@ -115,6 +115,17 @@ TEST_F(RdbTest, LoadSmall6) { EXPECT_THAT(resp.GetVec(), ElementsAre(IntArg(1), IntArg(1))); } +TEST_F(RdbTest, LoadStream) { + io::FileSource fs = GetSource("redis6_stream.rdb"); + RdbLoader loader(service_->script_mgr()); + + // must run in proactor thread in order to avoid polluting the serverstate + // in the main, testing thread. + auto ec = pp_->at(0)->Await([&] { return loader.Load(&fs); }); + + ASSERT_FALSE(ec) << ec.message(); +} + TEST_F(RdbTest, Reload) { absl::FlagSaver fs; diff --git a/src/server/testdata/redis6_stream.rdb b/src/server/testdata/redis6_stream.rdb new file mode 100644 index 0000000000000000000000000000000000000000..a49c9e20eb95d6becfdb6f28735531dae48b6456 GIT binary patch literal 36588 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 literal 0 HcmV?d00001