We have 10 thousand connections on a redis server . The server is occasionally hang up.
We found in function "clientsCronResizeQueryBuffer", condition (querybuf_size > 1024 && idletime > 2) is matched many times, and a lot of connections release their querybuf at cron job at the same time.The hang up happened at the same time when bulk release.
if (((querybuf_size > PROTO_MBULK_BIG_ARG) &&
(querybuf_size/(c->querybuf_peak+1)) > 2) ||
(querybuf_size > 1024 && idletime > 2))
{
/* Only resize the query buffer if it is actually wasting space. */
if (sdsavail(c->querybuf) > 1024) {
c->querybuf = sdsRemoveFreeSpace(c->querybuf);
}
}
===== As we found querybuff is initially allocated with 32 * 1024 bytes. So we change the condition to "querybuf_size > 1024 * 32 && idletime > 2". The hang up is fixed.
Comment From: antirez
Thank you, I just fixed it in commit cec404f0. What do you think about the new implementation? Thanks.
Comment From: soloestoy
Hi @antirez , after commit cec404f there are still some problem we should fix I think:
-
c->querybuf_peakhas not been updated correctly inreadQueryFromClient.c void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { qblen = sdslen(c->querybuf); if (c->querybuf_peak < qblen) c->querybuf_peak = qblen; c->querybuf = sdsMakeRoomFor(c->querybuf, readlen); nread = read(fd, c->querybuf+qblen, readlen); ... sdsIncrLen(c->querybuf,nread);As we can see, we update
c->querybuf_peakbeforereadandsdsIncrLen, it means thatqblenhere is the lastc->querybuflength. But the length ofc->querybufafterprocessInputBufferis always 0, unless we didn't read the whole request at one time.So, we should update
c->querybuf_peakaftersdsIncrLenI think. -
We should use
sdsallocinstead ofsdsAllocSize.Because we wanna get
c->querybufalloc space except for header, thesdsAllocSizevalue is always 32 * 1024 +sdsHdrSize+ 1 grate than 32 * 1024. -
Check if query buffer is > BIG_ARG and too big for latest peak only when
c->querybuf_peakis not 0.Function
clientsCronResizeQueryBufferresetc->querybuf_peakto be 0 no matter resize the query buffer or not.c int clientsCronResizeQueryBuffer(client *c) { ... /* Reset the peak again to capture the peak memory usage in the next * cycle. */ c->querybuf_peak = 0;If we don't get any requests after that, next time we will trigger the condition definitely.
Submit PR #5013.
Comment From: soloestoy
Another question.
As we found querybuff is initially allocated with 32 * 1024 bytes. So we change the condition to "querybuf_size > 1024 * 32 && idletime > 2". The hang up is fixed.
@northhurricane Sorry I cannot understand this fix.
If your fix is right it means that 1024 < querybuf_size <= 1024 * 32. But I can't find any scenario could make it happen.
As you said querybuff is initially allocated with 32 * 1024 bytes:
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
readlen = PROTO_IOBUF_LEN;
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
That's right, sdsMakeRoomFor can enlarge querybuf. But except for clientsCronResizeQueryBuffer, we have only one situation to resize querybuf:
int processMultibulkBuffer(client *c) {
if (pos == 0 &&
c->bulklen >= PROTO_MBULK_BIG_ARG &&
sdslen(c->querybuf) == (size_t)(c->bulklen+2))
{
c->argv[c->argc++] = createObject(OBJ_STRING,c->querybuf);
sdsIncrLen(c->querybuf,-2); /* remove CRLF */
/* Assume that if we saw a fat argument we'll see another one
* likely... */
c->querybuf = sdsnewlen(SDS_NOINIT,c->bulklen+2);
sdsclear(c->querybuf);
pos = 0;
}
But we get a c->querybuf greater than PROTO_MBULK_BIG_ARG(32 * 1024).
Am I misunderstanding? Do you have any idea @antirez ?
Comment From: oranagra
@soloestoy can we close this one? or is there something more to improve?