summaryrefslogtreecommitdiff
path: root/src/listpack.c
diff options
context:
space:
mode:
authorsundb <sundbcn@gmail.com>2021-11-24 19:34:13 +0800
committerGitHub <noreply@github.com>2021-11-24 13:34:13 +0200
commit4512905961b3a2f4c00e5fe7ffff8d96db82861e (patch)
treeba9627b240ede304c3c87a542d22bee5173dd929 /src/listpack.c
parentfb4f7be22c6f26faf3f222d1ff8d7119fd6c084e (diff)
downloadredis-4512905961b3a2f4c00e5fe7ffff8d96db82861e.tar.gz
Replace ziplist with listpack in quicklist (#9740)
Part three of implementing #8702, following #8887 and #9366 . ## Description of the feature 1. Replace the ziplist container of quicklist with listpack. 2. Convert existing quicklist ziplists on RDB loading time. an O(n) operation. ## Interface changes 1. New `list-max-listpack-size` config is an alias for `list-max-ziplist-size`. 2. Replace `debug ziplist` command with `debug listpack`. ## Internal changes 1. Add `lpMerge` to merge two listpacks . (same as `ziplistMerge`) 2. Add `lpRepr` to print info of listpack which is used in debugCommand and `quicklistRepr`. (same as `ziplistRepr`) 3. Replace `QUICKLIST_NODE_CONTAINER_ZIPLIST` with `QUICKLIST_NODE_CONTAINER_PACKED`(following #9357 ). It represent that a quicklistNode is a packed node, as opposed to a plain node. 4. Remove `createZiplistObject` method, which is never used. 5. Calculate listpack entry size using overhead overestimation in `quicklistAllowInsert`. We prefer an overestimation, which would at worse lead to a few bytes below the lowest limit of 4k. ## Improvements 1. Calling `lpShrinkToFit` after converting Ziplist to listpack, which was missed at #9366. 2. Optimize `quicklistAppendPlainNode` to avoid memcpy data. ## Bugfix 1. Fix crash in `quicklistRepr` when ziplist is compressed, introduced from #9366. ## Test 1. Add unittest for `lpMerge`. 2. Modify the old quicklist ziplist corrupt dump test. Co-authored-by: Oran Agra <oran@redislabs.com>
Diffstat (limited to 'src/listpack.c')
-rw-r--r--src/listpack.c202
1 files changed, 201 insertions, 1 deletions
diff --git a/src/listpack.c b/src/listpack.c
index 0d91cb55b..9d412d07c 100644
--- a/src/listpack.c
+++ b/src/listpack.c
@@ -1021,7 +1021,7 @@ unsigned char *lpDeleteRangeWithEntry(unsigned char *lp, unsigned char **p, unsi
* address again after a reallocation. */
unsigned long poff = first-lp;
- /* Move tail to the front of the ziplist */
+ /* Move tail to the front of the listpack */
memmove(first, tail, eofptr - tail + 1);
lpSetTotalBytes(lp, bytes - (tail - first));
uint32_t numele = lpGetNumElements(lp);
@@ -1064,6 +1064,103 @@ unsigned char *lpDeleteRange(unsigned char *lp, long index, unsigned long num) {
return lp;
}
+/* Merge listpacks 'first' and 'second' by appending 'second' to 'first'.
+ *
+ * NOTE: The larger listpack is reallocated to contain the new merged listpack.
+ * Either 'first' or 'second' can be used for the result. The parameter not
+ * used will be free'd and set to NULL.
+ *
+ * After calling this function, the input parameters are no longer valid since
+ * they are changed and free'd in-place.
+ *
+ * The result listpack is the contents of 'first' followed by 'second'.
+ *
+ * On failure: returns NULL if the merge is impossible.
+ * On success: returns the merged listpack (which is expanded version of either
+ * 'first' or 'second', also frees the other unused input listpack, and sets the
+ * input listpack argument equal to newly reallocated listpack return value. */
+unsigned char *lpMerge(unsigned char **first, unsigned char **second) {
+ /* If any params are null, we can't merge, so NULL. */
+ if (first == NULL || *first == NULL || second == NULL || *second == NULL)
+ return NULL;
+
+ /* Can't merge same list into itself. */
+ if (*first == *second)
+ return NULL;
+
+ size_t first_bytes = lpBytes(*first);
+ unsigned long first_len = lpLength(*first);
+
+ size_t second_bytes = lpBytes(*second);
+ unsigned long second_len = lpLength(*second);
+
+ int append;
+ unsigned char *source, *target;
+ size_t target_bytes, source_bytes;
+ /* Pick the largest listpack so we can resize easily in-place.
+ * We must also track if we are now appending or prepending to
+ * the target listpack. */
+ if (first_bytes >= second_bytes) {
+ /* retain first, append second to first. */
+ target = *first;
+ target_bytes = first_bytes;
+ source = *second;
+ source_bytes = second_bytes;
+ append = 1;
+ } else {
+ /* else, retain second, prepend first to second. */
+ target = *second;
+ target_bytes = second_bytes;
+ source = *first;
+ source_bytes = first_bytes;
+ append = 0;
+ }
+
+ /* Calculate final bytes (subtract one pair of metadata) */
+ unsigned long long lpbytes = (unsigned long long)first_bytes + second_bytes - LP_HDR_SIZE - 1;
+ assert(lpbytes < UINT32_MAX); /* larger values can't be stored */
+ unsigned long lplength = first_len + second_len;
+
+ /* Combined lp length should be limited within UINT16_MAX */
+ lplength = lplength < UINT16_MAX ? lplength : UINT16_MAX;
+
+ /* Extend target to new lpbytes then append or prepend source. */
+ target = zrealloc(target, lpbytes);
+ if (append) {
+ /* append == appending to target */
+ /* Copy source after target (copying over original [END]):
+ * [TARGET - END, SOURCE - HEADER] */
+ memcpy(target + target_bytes - 1,
+ source + LP_HDR_SIZE,
+ source_bytes - LP_HDR_SIZE);
+ } else {
+ /* !append == prepending to target */
+ /* Move target *contents* exactly size of (source - [END]),
+ * then copy source into vacated space (source - [END]):
+ * [SOURCE - END, TARGET - HEADER] */
+ memmove(target + source_bytes - 1,
+ target + LP_HDR_SIZE,
+ target_bytes - LP_HDR_SIZE);
+ memcpy(target, source, source_bytes - 1);
+ }
+
+ lpSetNumElements(target, lplength);
+ lpSetTotalBytes(target, lpbytes);
+
+ /* Now free and NULL out what we didn't realloc */
+ if (append) {
+ zfree(*second);
+ *second = NULL;
+ *first = target;
+ } else {
+ zfree(*first);
+ *first = NULL;
+ *second = target;
+ }
+
+ return target;
+}
+
/* Return the total number of bytes the listpack is composed of. */
size_t lpBytes(unsigned char *lp) {
return lpGetTotalBytes(lp);
@@ -1377,6 +1474,57 @@ unsigned int lpRandomPairsUnique(unsigned char *lp, unsigned int count, listpack
return picked;
}
+/* Print info of listpack which is used in debugCommand */
+void lpRepr(unsigned char *lp) {
+ unsigned char *p, *vstr;
+ int64_t vlen;
+ unsigned char intbuf[LP_INTBUF_SIZE];
+ int index = 0;
+
+ printf("{total bytes %zu} {num entries %lu}\n", lpBytes(lp), lpLength(lp));
+
+ p = lpFirst(lp);
+ while(p) {
+ uint32_t encoded_size_bytes = lpCurrentEncodedSizeBytes(p);
+ uint32_t encoded_size = lpCurrentEncodedSizeUnsafe(p);
+ unsigned long back_len = lpEncodeBacklen(NULL, encoded_size);
+ printf(
+ "{\n"
+ "\taddr: 0x%08lx,\n"
+ "\tindex: %2d,\n"
+ "\toffset: %1lu,\n"
+ "\thdr+entrylen+backlen: %2lu,\n"
+ "\thdrlen: %3u,\n"
+ "\tbacklen: %2lu,\n"
+ "\tpayload: %1u\n",
+ (long unsigned)p,
+ index,
+ (unsigned long) (p-lp),
+ encoded_size + back_len,
+ encoded_size_bytes,
+ back_len,
+ encoded_size - encoded_size_bytes);
+ printf("\tbytes: ");
+ for (unsigned int i = 0; i < (encoded_size + back_len); i++) {
+ printf("%02x|",p[i]);
+ }
+ printf("\n");
+
+ vstr = lpGet(p, &vlen, intbuf);
+ printf("\t[str]");
+ if (vlen > 40) {
+ if (fwrite(vstr, 40, 1, stdout) == 0) perror("fwrite");
+ printf("...");
+ } else {
+ if (fwrite(vstr, vlen, 1, stdout) == 0) perror("fwrite");
+ }
+ printf("\n}\n");
+ index++;
+ p = lpNext(lp, p);
+ }
+ printf("{end}\n\n");
+}
+
#ifdef REDIS_TEST
#include <sys/time.h>
@@ -1845,6 +1993,58 @@ int listpackTest(int argc, char *argv[], int flags) {
lpFree(lp);
}
+ TEST("lpMerge two empty listpacks") {
+ unsigned char *lp1 = lpNew(0);
+ unsigned char *lp2 = lpNew(0);
+
+ /* Merge two empty listpacks, get empty result back. */
+ lp1 = lpMerge(&lp1, &lp2);
+ assert(lpLength(lp1) == 0);
+ zfree(lp1);
+ }
+
+ TEST("lpMerge two listpacks - first larger than second") {
+ unsigned char *lp1 = createIntList();
+ unsigned char *lp2 = createList();
+
+ size_t lp1_bytes = lpBytes(lp1);
+ size_t lp2_bytes = lpBytes(lp2);
+ unsigned long lp1_len = lpLength(lp1);
+ unsigned long lp2_len = lpLength(lp2);
+
+ unsigned char *lp3 = lpMerge(&lp1, &lp2);
+ assert(lp3 == lp1);
+ assert(lp2 == NULL);
+ assert(lpLength(lp3) == (lp1_len + lp2_len));
+ assert(lpBytes(lp3) == (lp1_bytes + lp2_bytes - LP_HDR_SIZE - 1));
+ verifyEntry(lpSeek(lp3, 0), (unsigned char*)"4294967296", 10);
+ verifyEntry(lpSeek(lp3, 5), (unsigned char*)"much much longer non integer", 28);
+ verifyEntry(lpSeek(lp3, 6), (unsigned char*)"hello", 5);
+ verifyEntry(lpSeek(lp3, -1), (unsigned char*)"1024", 4);
+ zfree(lp3);
+ }
+
+ TEST("lpMerge two listpacks - second larger than first") {
+ unsigned char *lp1 = createList();
+ unsigned char *lp2 = createIntList();
+
+ size_t lp1_bytes = lpBytes(lp1);
+ size_t lp2_bytes = lpBytes(lp2);
+ unsigned long lp1_len = lpLength(lp1);
+ unsigned long lp2_len = lpLength(lp2);
+
+ unsigned char *lp3 = lpMerge(&lp1, &lp2);
+ assert(lp3 == lp2);
+ assert(lp1 == NULL);
+ assert(lpLength(lp3) == (lp1_len + lp2_len));
+ assert(lpBytes(lp3) == (lp1_bytes + lp2_bytes - LP_HDR_SIZE - 1));
+ verifyEntry(lpSeek(lp3, 0), (unsigned char*)"hello", 5);
+ verifyEntry(lpSeek(lp3, 3), (unsigned char*)"1024", 4);
+ verifyEntry(lpSeek(lp3, 4), (unsigned char*)"4294967296", 10);
+ verifyEntry(lpSeek(lp3, -1), (unsigned char*)"much much longer non integer", 28);
+ zfree(lp3);
+ }
+
TEST("Random pair with one element") {
listpackEntry key, val;
unsigned char *lp = lpNew(0);