-
Notifications
You must be signed in to change notification settings - Fork 687
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
Introduce commandlog heavytraffic
to record big response packet.
#336
Conversation
4fd206a
to
c3727a7
Compare
I'm not sure about this specific feature. At least from what I've seen, it's less common for a single command to produce an unexpectedly large result without it also showing up in the slowlog. |
This does happen. Some of our users are very sensitive to network flow spikes, and they couldn't find any clue in slowlog, so thay asked us about it. Slowlog only records time of command executions ss. But a hash lookup and a memory copy (simple |
@CharlesChen888 Before I review your PR, could you please provide some background information about why you want to update the slowlogCommand to heavyLoadLogCommand? Because in the top comment, you mention you want to add some new commands for fatlog, but in your pr, you make some changes for slowlog files. I am confused it. |
@hwware This is because fatlog shares a lot code of slowlog. So the function used to be called slowlogCommand now contains code of both slowlog command and fatlog command. I tried to think of a name meaning "both slow and fat", "heavy load" seems to cover both situation. |
Can you be more precise than this? People ask us at AWS too about issues, but we've always been able to debug this type of problem without what you proposed. What do you mean they are sensitive to "network flow spikes". We've seen a lot more issues with a sudden burst of smaller commands than this type of "single large" packet issue. |
Yes, this is a common case and we've also seen a lot. But there is another situation in which network flow suddenly increases without significant higher QPS, the most common reason is that users are accessing big keys, like getting a long string, or using commands like keys, scan, hget, zrange to request too many elements at once. This is common among our users. And fatlog is mainly helpful in this situation, it is a quick way to pinpoint the cause. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## unstable #336 +/- ##
============================================
+ Coverage 70.12% 70.16% +0.04%
============================================
Files 111 111
Lines 60305 60339 +34
============================================
+ Hits 42289 42338 +49
+ Misses 18016 18001 -15
|
428f06b
to
f894a52
Compare
I think the scenario in the top comment is very clear: "slow" and "fat" are not same. I'd love to explain it further in my understanding. We have different types of resources like CPU, memory, network, etc. The slowlog can only record commands eat lots of CPU during the processing phase (doesn't include read/write network time), but can not record commands eat too many memory and network. For example:
So, the fatlog can help users to locate these special cases. BTW, the PR only record the fat output buffer, we should also record the fat input buffer (by |
+1, although I would imagine unexpected large outputs are a lot more common than unexpected large inputs. There are many surprising ways to see large output, like running I guess I'm convinced about the value of this feature, however I don't believe that this should be solving the case where there is uniform data with large keys, such as only being able to hit 1k tps with 1mb items. That is trivial to debug by just doing a Another thought, should we also accumulate this information on cmd_stats? That would maybe more useful for seeing changes in command output trends compared to a fat log? |
src/Makefile
Outdated
@@ -383,7 +383,7 @@ endif | |||
ENGINE_NAME=valkey | |||
SERVER_NAME=$(ENGINE_NAME)-server$(PROG_SUFFIX) | |||
ENGINE_SENTINEL_NAME=$(ENGINE_NAME)-sentinel$(PROG_SUFFIX) | |||
ENGINE_SERVER_OBJ=threads_mngr.o adlist.o quicklist.o ae.o anet.o dict.o kvstore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_legacy.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o valkey-check-rdb.o valkey-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o | |||
ENGINE_SERVER_OBJ=threads_mngr.o adlist.o quicklist.o ae.o anet.o dict.o kvstore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_legacy.o crc16.o endianconv.o heavyloadlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o valkey-check-rdb.o valkey-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o |
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.
Even assuming we accept this, I don't really like the idea of renaming the slowlog. I think the current naming of "slow" is probably sufficient.
src/commands/slowlog-get.json
Outdated
@@ -6,7 +6,7 @@ | |||
"since": "2.2.12", | |||
"arity": -2, | |||
"container": "SLOWLOG", | |||
"function": "slowlogCommand", | |||
"function": "heavyLoadLogCommand", |
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.
My preference was to avoid large container commands. I would rather have slowlogCommand
call some shared function then have this call a container function.
src/heavyloadlog.c
Outdated
* Similarly, fatlog remembers the latest N queries that has a response | ||
* larger than K bytes. | ||
* | ||
* The size of the response to reach to be logged in the fat lof is set |
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.
* The size of the response to reach to be logged in the fat lof is set | |
* The size of the response to reach to be logged in the fat log is set |
src/module.c
Outdated
@@ -1156,6 +1156,7 @@ int64_t commandFlagsFromString(char *s) { | |||
else if (!strcasecmp(t,"allow-stale")) flags |= CMD_STALE; | |||
else if (!strcasecmp(t,"no-monitor")) flags |= CMD_SKIP_MONITOR; | |||
else if (!strcasecmp(t,"no-slowlog")) flags |= CMD_SKIP_SLOWLOG; | |||
else if (!strcasecmp(t,"no-fatlog")) flags |= CMD_SKIP_FATLOG; |
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.
What would the use case be for logging to the slowlog but not the fatlog (or vice versa)? Today it's only set in exec, since we want the underlying container command to show up instead of the exec, which I imagine is also true here. I almost think we have one flag, no-slowlog
that implies skipping both.
src/config.c
Outdated
@@ -3230,6 +3231,7 @@ standardConfig static_configs[] = { | |||
createLongLongConfig("proto-max-bulk-len", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, server.proto_max_bulk_len, 512ll*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */ | |||
createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL), | |||
createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.repl_backlog_size, 1024*1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 1mb */ | |||
createLongLongConfig("fatlog-log-bigger-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.fatlog_log_bigger_than, 16*1024, INTEGER_CONFIG, NULL, NULL), |
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.
createLongLongConfig("fatlog-log-bigger-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.fatlog_log_bigger_than, 16*1024, INTEGER_CONFIG, NULL, NULL), | |
createLongLongConfig("fatlog-log-bigger-than", NULL, MODIFIABLE_CONFIG, -1, LLONG_MAX, server.fatlog_log_bigger_than, 16*1024, MEMORY_CONFIG, NULL, NULL), |
Any reason we wouldn't let this be a memory config, so you could set it to 1mb or something?
16kb also seems way too small, some historical data was have shows that isn't even in the P99 of data size. I would say at least 1mb.
@@ -0,0 +1,69 @@ | |||
{ |
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.
Although I understand the rational behind a new command, a fat command is logically different from a slow command, I would also like us to more seriously consider just adding one new field to the slowlog, the output/input bytes, and then having both get logged to the same place. It's not a perfect fit, but then users can just look in "one" place for bad behavior. It also allows the two logs to perhaps explain each other, since a large output can cause a slow command. We could extend SLOWLOG GET [count] [TYPE FAT|SLOW]
to only show entries of the specific type.
I'm thinking about it more from a simpler way to explain it to end users.
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.
This does make things easier than adding a new series of commands, if we can ignore that changes in format of command reply may cause backwards compatibility problem.
But a big packet in query or response does not necessarily mean it will be slow. I'm not sure if we should do this.
Discussed this in a separate meeting. Everyone is generally aligned about the idea, but we need to refine the name and details. |
I will try to make some time for it in this week. |
2a80b69
to
9e7f529
Compare
I propose one command name: BigValueLog. From above comments, this command usually is used to record the big value for one key. It is different from the slowlog, which goal is to record the entry exceeded a specified execution time. Sometimes due to cpu or disk higher usage, one entry could be recorded in slowlog, but not in fatlog (BigValueLog). Thus, I do not think adding one more argument in slowlog as SLOWLOG GET [count] [TYPE FAT|SLOW] is a good idea. I still prefer to add a new command in Valkey. Or if we want to extend current slowlog function, I prefer the command format as: SLOWLOG GET [count] [FAT|SLOW|ALL] |
Yes, this is why I prefer to create a new command, rather than adding new field to SLOWLOG.
Or BigPacketLog? I am fine with all three names. |
1aa1d56
to
a0cf493
Compare
src/commands/fatlog-help.json
Outdated
@@ -0,0 +1,22 @@ | |||
{ | |||
"HELP": { | |||
"summary": "Show helpful text about the different subcommands", |
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.
"summary": "Show helpful text about the different subcommands", | |
"summary": "Returns helpful text about the different subcommands", |
@@ -3156,6 +3156,7 @@ standardConfig static_configs[] = { | |||
createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, server.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */ | |||
createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL), | |||
createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL), | |||
createULongConfig("fatlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, server.fatlog_max_len, 128, INTEGER_CONFIG, NULL, NULL), |
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 should add these 2 new config parameter in valkey.conf and describe details, pls reference the Slow Log section.
I go through the PR, most part it looks good to me, But the biggest concern for me is hard to maintenance in the future. Slowlog and Fatlog are 2 different concepts, but in this PR, they share some variables and functions, but name is still only point to slowlog (for example: slowlogCreateEntry, slowlogCommand, slowlogEntry). I worry about it takes longer time to understand the codes several year later. How do you think @valkey-io/core-team |
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. Just some nits.
Actually I'm OK with the name "FATLOG". So far I didn't hear a better suggestion.
In Wikipedia Fat (disambiguation) I find the following Fat abstract concepts:
Fat address
Fat binary, a multi-platform (executable) file in computing
Fat hex
Fat link, a multi-tailed hyperlink
Fat node
Fat object (disambiguation)
Fat pointer, a special type of pointer in some programming languages
Fat shellcode
Thus, using the word "fat" for abstract concepts in programming is nothing new. I think it is the right name for this concept.
/* Log the last command a client executed into the fatlog. */ | ||
void fatlogPushCurrentCommand(client *c, struct serverCommand *cmd, size_t size) { | ||
/* Some commands may contain sensitive data that should not be available in the fatlog. */ | ||
if (cmd->flags & CMD_SKIP_SLOWLOG) return; |
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.
Nit about the comment: The only command marked as SKIP_SLOWLOG
is EXEC
(if I grep correctly), so don't think this is about sensitive data. it can be that EXEC does all the work of the commands in the transaction, so EXEC takes the time and space of these commands. It would be misleading to just report "EXEC" in the slowlog or fatlog. To make it useful, we would need to store the whole transaction in the slowlog/fatlog entry. Maybe just don't mention "sensitive data"?
Suggestion:
/* Some commands should not be available in the slowlog and the fatlog. */
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.
You are right. Maybe this is originally meant for ACL commands, but they already redacted the "sensitive data".
This new metric makes sense to me on a high level but I am very much not a fan of this new command name. I also wonder if it makes sense to fold this into the slowlog command:
Thoughts? |
Adding type to I would not recommend using I think However, if everyone feels that this name is inappropriate, |
We can just rename the common code to make it look like a common lib, then make slowlog and fatlog to become two interfaces of this lib. (This is actually my original idea.) |
I just realized, and maybe this should be noted ... when logging fat input queries, the packet length is |
Agree with you on not adding type to SLOWLOG. Since Fat abstract concepts are already applied in other fields, I think |
Thanks, pls update to a common interface and make the Slowlog and Fatlog implementation separated. I think it is easier to maintain later. |
Signed-off-by: Chen Tianjie <[email protected]>
Signed-off-by: Chen Tianjie <[email protected]>
Signed-off-by: Chen Tianjie <[email protected]>
Signed-off-by: Chen Tianjie <[email protected]>
Signed-off-by: Chen Tianjie <[email protected]>
Signed-off-by: Chen Tianjie <[email protected]>
d004306
to
e928f27
Compare
Signed-off-by: Chen Tianjie <[email protected]>
We discussed this in the core team meeting. The only remaining issue to agree about is the name. Some people don't like FAT because it has a negative connotation. @valkey-io/core-team Please comment your favorite name and some alternatives you can accept. Then we'll see if there is a winner. Remember: The log is not about the size of keys. It's about the command and response sizes, the client traffic. Suggestions:
|
I vote No1. LargeLog No2. BigLog No No 3. |
+1 on "LARGELOG" |
Signed-off-by: Chen Tianjie <[email protected]>
Signed-off-by: Chen Tianjie <[email protected]>
+1 on "LARGELOG" |
Adjectives like "big," "huge," or "large" are all acceptable, but we need to clarify what exactly they are describing. Is it a big key, a huge value, or a large command? Otherwise, it can easily be misconstrued. We need to describe clearly what it is recording. Terms like "bigPacketLog" or "heavyTrafficLog" would be more appropriate, if these terms are too long, then "heavylog" could also work. "Heavy" can be used to describe traffic, no one would say "big traffic," "large traffic," or "huge traffic," right? |
I prefer that we use different words for the size of a key and the size of a command/response. Another area of possible confusion could have been keys on the top-level vs keys in hashes, but here we have the terminology "key" for the top-level and "field" for hash fields. This terminology helps when we talk about these, so we don't have to explain every time what we're talking about. I think we can reserve the word "big" for keys and use another word for commands and responses. I'm leaning towards "heavy" now, since it's about traffic. Let's consider the terminology "heavy command" and "heavy response". 🤔 .... I like it. I did a little grep in docs and code and I find that "heavy" is almost exclusively for traffic and IO ("a write-heavy application", "heavy disk I/O"), while "large" is used for many different things ("large databases", "a very large COUNT option", etc.). +1 on HEAVYLOG |
/* Record query length for fatlog. */ | ||
c->cmd_query_length += c->qb_pos - qb_pos_start; |
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.
Thanks for sharing this commit. The c->qb_pos
tracking is definitely a feasible option.
I left some edge-case considerations on the other thread, and will leave the link here for reference; #720 (comment).
We've internally considered using
c->qb_pos
as well. While trackingc->qb_pos
also works, it comes with a few edge cases, namely;
c->qb_pos
will not start from 0 for some pipeline cases. This means, we must also track the starting position of theqb_pos
, call itqb_pos_start
. I see that this case is already covered by your commit, which is good.Optimization 1. Large object creation. For the case where a non-primary client's buffer contains just the bulk element, the SDS ownership is simply moved from
c->querybuf
torobj
, rather than copying the entire SDS. In such case,c->qb_pos
do not step forward, even though the bytes have actually been read. We must manually account for this, otherwise this ingress bytes is forever lost.Optimization 2. Early c->qb_pos reset.
c->qb_pos
could be prematurely reset to 0, such that its processing could benefit from the above optimization (creating large object from the 0th index ofc->querybuf
). In conjunction with the first edge case (where we start from a non-zeroqb_pos_start
), simply incrementingc->qb_pos - qb_pos_start
can yield a negative ingress bytes result.When comparing both implementations side-by-side, it became clear that
c->qb_pos
including all its edge case handlings - was harder to comprehend when compared to thec->argv_len_sum
variant. Not to mention,c->qb_pos
accounting will need to change upon any newly introduced query processing optimizations, and/or changes to the existing optimizations mentioned above.Ultimately, I find
c->argv_len_sum
to be a stronger contract. Either subroutines (processInlineBuffer()
orprocessMultibulkBuffer()
) hold a guarantee of fully populatedc->argc/argv/argv_len_sum
before command execution. Any existing or futurec->qb_pos
optimizations aren't concerned, as long asc->argc/argv/argv_len_sum
are correctly filled. I also find the implementation more intuitive and easier to maintain.But no strong opinions on this, it's good that we are having this discussion, and I'm fine to swing both ways. Curious to hear the core-team's thoughts as well.
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.
During the latest core meeting (July 15th 2024), we concluded to move forward with the currently upheld c->argv_len_sum
approach. I will now resolve this thread.
@CharlesChen888
Given this decision, I would first wait for the merger of per-slot network-bytes-in PR, which introduces c->net_input_bytes_curr_cmd
based on c->argv_len_sum
calculation. After its merger, your PR may refer to c->net_input_bytes_curr_cmd
as a heuristic for assessing fatlogs. With that said, let me know if you need any help from my end. If there's a better way to collaborate, I'm open for it.
+1 to Largelog. Alternative being considered is |
After a core team meeting, we decided adding a new command |
commandlog heavytraffi
to record big response packet.
commandlog heavytraffi
to record big response packet.commandlog heavytraffic
to record big response packet.
I must apologize for forgetting about this PR. Many things have happened in the past few months and now I finally have the time and energy to return to the Valkey community. I was thinking about finishing this new feature this morning, but it turns out that @soloestoy has taken over the work. So I think I will just close this PR and we continue to finish this feature in the new one. |
welcome back @CharlesChen888 : ) |
Why fat log?
We already have slowlog, which keeps a record of recent queries that take relatively long time to execute. However sometimes it is not enough for debugging.
For example, we often receive questions from users like: "Why is my QPS not high, but the network flow is substantial?" or "Why did the network flow suddenly increase?" The situation is network flow suddenly increases without significant higher QPS.
The most common reason is that users are accessing big keys, like getting a long string, or using commands like keys, scan, hget, zrange to request too many elements at once. This is common among our users.
Here a new log is introduced to keep a record when a query triggers a large response (and we may call this log "fatlog"), in which the command, response size, time stamp, client name and client network address are logged. This can help to monitor traffic flow spikes and big keys. And it is a quick way to pinpoint the problem.
Alternative solution?
Detail introduction
Just like slowlog, fatlog is not actually logged in a file, and is accessible through a series of commands:
also a help command is provided:
And just like slowlog, two config items are provided to set how many log items are preserved and the response size threshold of being logged.
About implementation
You will find that a lot changes have been appllied to slowlog.c. That is because the implementation of fatlog is basically reusing the code of slowlog, since they have similar format and are controled by similar commands. So what used to be slowlog.c now contains code of both slowlog and fatlog.