Skip to content
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

feat: add support of the command JSON.DEBUG MEMORY #2323

Merged
merged 27 commits into from
May 31, 2024

Conversation

jackyyyyyssss
Copy link
Contributor

Add support of the command JSON.DEBUG MEMORY
https://redis.io/docs/latest/commands/json.debug-memory/

Copy link
Member

@mapleFU mapleFU left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another problem is that, should we emit the memory size ( And memory size might larger than str size ) or just export the size on disk?

std::string path = "$";

if (!util::EqualICase(args_[1], "memory")) {
return {Status::RedisExecErr, "The number of arguments is more than expected"};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this is number more than expected?

@@ -626,5 +626,17 @@ std::vector<rocksdb::Status> Json::readMulti(const std::vector<Slice> &ns_keys,
}
return statuses;
}
rocksdb::Status Json::DebugMemory(const std::string &user_key, const std::string &path, Optionals<uint64_t> *results) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

format or adding a space between them?

src/types/json.h Outdated
if (!origin.empty()) {
// This is only a rough calculation of the size of the string size char, not the entire byte occupied by
// the object
results.emplace_back(origin.as_string().size() * sizeof(std::string::value_type));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this json_query matches more than 1 element?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If multiple elements are hit, an array is returned. This is the return value of Redis 7.0
251ef557533affef376c009aaf4d218

Copy link
Member

@PragmaTwice PragmaTwice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several issues in this implemetation:

  • the result of as_string().size() is not equal to the real size, we should at least use to_string()
  • but actually to_string is also wrong, since it maybe not equal to the result of compact_json_string_encoder. also it would be totally wrong if CBOR format is enabled.
  • there may be multiple location found from one jsonpath, which is not handled in this impl
  • if jsonpath is $, no json decoding is required

@jackyyyyyssss
Copy link
Contributor Author

There are several issues in this implemetation:

  • the result of as_string().size() is not equal to the real size, we should at least use to_string()
  • but actually to_string is also wrong, since it maybe not equal to the result of compact_json_string_encoder. also it would be totally wrong if CBOR format is enabled.
  • there may be multiple location found from one jsonpath, which is not handled in this impl
  • if jsonpath is $, no json decoding is required

Thank you for your help. The modifications have been completed, please help me take a look again

@jackyyyyyssss jackyyyyyssss marked this pull request as draft May 21, 2024 09:45
@jackyyyyyssss jackyyyyyssss marked this pull request as ready for review May 21, 2024 09:58
@jackyyyyyssss jackyyyyyssss marked this pull request as draft May 21, 2024 10:30
src/types/json.h Outdated
value, path,
[&results, max_nesting_depth, format](const std::string & /*path*/, const jsoncons::json &origin) {
if (!origin.empty()) {
Status s;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it useful here?

src/types/json.h Outdated
Comment on lines 235 to 240
origin.dump(encoder, ec);
} else if (format == JsonStorageFormat::CBOR) {
jsoncons::cbor::cbor_options options;
options.max_nesting_depth(max_nesting_depth);
jsoncons::cbor::basic_cbor_encoder<jsoncons::string_sink<std::string>> encoder{buffer, options};
origin.dump(encoder, ec);
Copy link
Member

@PragmaTwice PragmaTwice May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the exception version of dump and catch it later.

src/types/json.h Outdated
@@ -217,6 +217,42 @@ struct JsonValue {
return results;
}

StatusOr<Optionals<uint64_t>> StrBytes(std::string_view path, JsonStorageFormat format,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is optional useful here? Could we just use vector?

src/types/json.h Outdated
@@ -217,6 +217,42 @@ struct JsonValue {
return results;
}

StatusOr<Optionals<uint64_t>> StrBytes(std::string_view path, JsonStorageFormat format,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
StatusOr<Optionals<uint64_t>> StrBytes(std::string_view path, JsonStorageFormat format,
StatusOr<Optionals<uint64_t>> GetBytes(std::string_view path, JsonStorageFormat format,

Slice rest;
auto s = GetMetadata(GetOptions{}, {kRedisJson}, ns_key, &bytes, &metadata, &rest);
if (!s.ok()) return s;
results->emplace_back(bytes.size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
results->emplace_back(bytes.size());
results->emplace_back(rest.size());

src/types/json.h Outdated
@@ -217,6 +217,42 @@ struct JsonValue {
return results;
}

StatusOr<std::vector<std::string>> GetBytes(std::string_view path, JsonStorageFormat format,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why vector<string> instead of vector<size_t>?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because currently ArrayOfBulkStrings only supports string types, if modified to a template, it cannot meet the writing requirements of std:: initializer_list. Do you suggest that I add another method, std: vector<size_t>?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use a loop to dump RESP output. It doesn't matter whether you use ArrayOfBulkStrings or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok thanks

src/types/json.h Outdated
try {
jsoncons::jsonpath::json_query(value, path, [&](const std::string & /*path*/, const jsoncons::json &origin) {
if (!origin.empty()) {
Status s;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is s really useful here? (I have to ask again)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is used to traverse the internal structure of JSON and will loop through the callback function multiple times inside

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the variable s.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see

src/types/json.h Outdated
Comment on lines 229 to 239
if (format == JsonStorageFormat::JSON) {
jsoncons::json_options options;
options.max_nesting_depth(max_nesting_depth);
jsoncons::compact_json_string_encoder encoder{buffer, options};
origin.dump(encoder, ec);
} else if (format == JsonStorageFormat::CBOR) {
jsoncons::cbor::cbor_options options;
options.max_nesting_depth(max_nesting_depth);
jsoncons::cbor::basic_cbor_encoder<jsoncons::string_sink<std::string>> encoder{buffer, options};
origin.dump(encoder, ec);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use Dump and DumpCBOR.

src/types/json.h Outdated
Comment on lines 240 to 242
if (ec) {
throw std::system_error(ec);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove it.

var result2 = make([]interface{}, 0)
result2 = append(result2, int64(4), int64(6), int64(2))
require.Equal(t, rdb.Do(ctx, "JSON.DEBUG", "MEMORY", "a", "$..a").Val(), result2)
require.ErrorIs(t, rdb.Do(ctx, "JSON.DEBUG", "MEMORY", "not_exists", "$").Err(), redis.Nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add more test cases? e.g. for a valid $, or a number/array/object, or a non-existent path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I will add all the testcases

src/types/json.h Outdated
std::vector<std::string> results;
try {
jsoncons::jsonpath::json_query(value, path, [&](const std::string & /*path*/, const jsoncons::json &origin) {
if (!origin.empty()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the meaning of this empty check?
Could you think about whether an empty JSON array/object will occupy storage space?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty arrays and nulls actually take up space, and the test cases have been supplemented. Please see if there are any areas that can be optimized

@jackyyyyyssss jackyyyyyssss marked this pull request as ready for review May 22, 2024 08:29
@@ -45,6 +45,14 @@ std::string OptionalsToString(const Connection *conn, Optionals<T> &opts) {
return str;
}

std::string SizeToString(const std::vector<std::size_t> &elems) {
std::string result = "*" + std::to_string(elems.size()) + CRLF;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use MultiLen here.

auto s = json.DebugMemory(args_[2], path, &results);

if (s.IsNotFound()) {
*output = conn->NilString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's wrong. For nonexistent key, the result is 0.

Please confirm with redis-stack and redis-cli by yourself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you again. The latest version of Redis Stack returns all cases, and I will make the necessary modifications later
image

src/types/json.h Outdated
Comment on lines 239 to 241
if (results.size() == 0) {
results.emplace_back(0);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's wrong.

Please use redis-stack and redis-cli to confirm the correct result for this case.

@jackyyyyyssss jackyyyyyssss requested a review from PragmaTwice May 27, 2024 01:13
std::vector<size_t> results;
Status s;
try {
jsoncons::jsonpath::json_query(value, path, [&](const std::string & /*path*/, const jsoncons::json &origin) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
jsoncons::jsonpath::json_query(value, path, [&](const std::string & /*path*/, const jsoncons::json &origin) {
jsoncons::jsonpath::json_query(value, path, [&](const std::string & /*path*/, const jsoncons::json &origin) {
if (!s) return;

Copy link
Contributor Author

@jackyyyyyssss jackyyyyyssss May 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, I have two questions I would like to ask. 1. When does the status initialization fail and needs to be returned in advance? 2. Currently, obtaining the byte is still inaccurate, and the actual value should be larger than the encoded string. size(). Does this meet the project's standards?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no initialization failure.

We should check it since the lambda function can be executed more than once.

@@ -647,6 +689,7 @@ REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandJsonSet>("json.set", 4, "write", 1, 1
MakeCmdAttr<CommandJsonStrAppend>("json.strappend", -3, "write", 1, 1, 1),
MakeCmdAttr<CommandJsonStrLen>("json.strlen", -2, "read-only", 1, 1, 1),
MakeCmdAttr<CommandJsonMGet>("json.mget", -3, "read-only", 1, -2, 1),
MakeCmdAttr<CommandJsonMSet>("json.mset", -4, "write", 1, -3, 3), );
MakeCmdAttr<CommandJsonMSet>("json.mset", -4, "write", 1, -3, 3),
MakeCmdAttr<CommandJsonDebug>("json.debug", -3, "read-only", 1, 2, 1));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
MakeCmdAttr<CommandJsonDebug>("json.debug", -3, "read-only", 1, 2, 1));
MakeCmdAttr<CommandJsonDebug>("json.debug", -3, "read-only", 2, 2, 1));

src/commands/cmd_json.cc Outdated Show resolved Hide resolved
@jackyyyyyssss jackyyyyyssss changed the title Add support of the command JSON.DEBUG MEMORY feat[json]: Add support of the command JSON.DEBUG MEMORY May 31, 2024
@jackyyyyyssss jackyyyyyssss changed the title feat[json]: Add support of the command JSON.DEBUG MEMORY feat[json]: add support of the command JSON.DEBUG MEMORY May 31, 2024
@jackyyyyyssss jackyyyyyssss changed the title feat[json]: add support of the command JSON.DEBUG MEMORY feat: add support of the command JSON.DEBUG MEMORY May 31, 2024
Copy link
Member

@PragmaTwice PragmaTwice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm review comment still doesn't get solved yet

@jackyyyyyssss
Copy link
Contributor Author

hmm review comment still doesn't get solved yet
I'm sorry I'm a bit clumsy, may I ask where I'm referring to
image
image

@PragmaTwice
Copy link
Member

Ahh sorry I checked the diff again. It's fixed.

@PragmaTwice PragmaTwice merged commit 3557ad3 into apache:unstable May 31, 2024
32 checks passed
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarCloud

Catch issues before they fail your Quality Gate with our IDE extension SonarLint

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants