New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize range deletion in zset encoded with listpack #13132
base: unstable
Are you sure you want to change the base?
Conversation
@sundb I'd appreciate it if you could review this pr. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@lyq2333 it's nothing to do with this PR, i found it yesterday but don't know why. |
src/t_zset.c
Outdated
} else { | ||
/* No longer in range. */ | ||
break; | ||
} | ||
} | ||
|
||
if (num) | ||
zl = lpDeleteRangeWithEntry(zl, &first_ele, 2 * num); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the above loop, we can get the range's tail, which is eptr
(if NULL it means it's the last one in listpack). I think we could implement another function lpDeleteRangeWithEntryPtr(unsigned char *lp, unsigned char **first, unsigned char **tail)
to avoid the traversal search for tail within lpDeleteRangeWithEntry
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case we also need to pass the entry number between first and tail, otherwise we can't update the entry numbers, but that also make this method a little odd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find we have the same problem in zpopmin/max
with count. In zpopmin/max
, we also get the start position, end position and the number elements to be removed. Shall I add a new function like lpDeleteRangeWithEntryPtr
to solve both of them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lyq2333 we can go ahead with this way, we can compromise on it if it really brings a boost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sundb @soloestoy I add the new function lpDeleteRangeWithEntryPtr
and make a test. The result is as follow. It improve about 6% based on the origin branch(not unstable).
Before
Summary:
throughput summary: 64333.51 requests per second
latency summary (msec):
avg min p50 p95 p99 max
0.700 0.152 0.679 0.943 1.023 2.279
Now
Summary:
throughput summary: 68101.34 requests per second
latency summary (msec):
avg min p50 p95 p99 max
0.658 0.160 0.639 0.871 0.927 3.063
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also make a test about zpopmin
. I put the result in the top comment. The performance improvement is about 8% because we costs many time on reply.
src/t_zset.c
Outdated
@@ -4035,11 +4062,11 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey | |||
serverAssertWithInfo(c,zobj,zln != NULL); | |||
ele = sdsdup(zln->ele); | |||
score = zln->score; | |||
serverAssertWithInfo(c,zobj,zsetDel(zobj,ele)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can call zslDeleteRangeByRank
after the whole fake pop loop.
serverAssertWithInfo(c,zobj,sptr != NULL); | ||
score = zzlGetScore(sptr); | ||
/* In previous versions, we call notifyKeyspaceEvent after the first entry is deleted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can move the notifyKeyspaceEvent
to after all the pop completed. This way, we won't need to check the result_count
, and the logic will be much clearer. Commands like zremrange*
operate in the same manner since the execution of the command is atomic.
module is a bit special, but I believe that moving the notification from after the first element is popped to after all elements have been popped will not break compatibility with modules.
I'd also like to ping @oranagra to confirm this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think notification immediately after removing the first element is weird. But I don't know the reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed the logic of firing the notification only on the first removal seems odd (rather than firing on every removal).
when we realize that when this code was created in 56bbab2 there were no modules, then firing it on the first removal was equivalent to firing it at the end (if there was at least one removal).
but with modules it makes less sense.
i'd vote to move this to the end and document the behavior change for modules.
@MeirShpilraien WDYT?
src/t_zset.c
Outdated
@@ -4035,11 +4062,11 @@ void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey | |||
serverAssertWithInfo(c,zobj,zln != NULL); | |||
ele = sdsdup(zln->ele); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another minor optimization: for the skiplist, we don't need to copy the ele. Or perhaps we should change it so that listpack and skiplist handle their own loops respectively.
unsigned long poff = *first-lp; | ||
|
||
/* Move tail to the front of the listpack */ | ||
memmove(*first, tail, eofptr - tail + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm worried about about this, if the bytes
is corrupted, does this mean it's possible to use eofptr to read the memory data after this listpack?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that one reason of the performance improvement is the reduction calls of lpAssertValidEntry
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In lpDeleteRangeWithEntry
, if the bytes
is corrupted, we also have the same problem. If we want to avoid this problem, I think maybe we have to travel the whole listpack to verify the validity of the bytes
.
We get first
and tail
by lpNext
or lpPrev
, in which we have called lpAssertValidEntry
. I think maybe we don't need to repeat it in lpDeleteRangeWithEntryPtr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, you're probably right, please merge the unstable and i'll run a fully CI to check it.
@lyq2333 |
|
|
In past, if we delete a range of elements in
zset
encoded withlistpack
, we calllpDeleteRangeWithEntry
repeatedly. ButlpDeleteRangeWithEntry
callslpShrinkToFit
in whichrealloc
is called. This means we call many unnecessaryrealloc
.In this PR, we optimize
zremrangeby[score|lex]
andzpop[min|max]
by deleting all elements once to avoid redundantrealloc
. We add a new function namedlpDeleteRangeWithEntryPtr
to delete the range of elements from the start address to the tail address and avoid duplicate traversal search.The result shows that the performance of
zremrangeby[score|lex]
can improve about 70% and the performance ofzpop[min|max]
can improve about 8%.zremrangebyscore
I set 500,000 zset keys and each has 100 elements. Then I use
zremrangebyscore
to delete all. The result is as follow. The performance of range deletion increases about 70%.Unstable
This PR
zpopmin
zpop[min|max]
has the same problem. I set 500,000 zset keys and each has 100 elements. Then I usezpopmin
to delete all. The result is as follow. The performance of range deletion increases about 8%.Unstable
This PR