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(hyperloglog): add support of the Hyperloglog data structure #2142

Merged
merged 38 commits into from
Jul 31, 2024

Conversation

tutububug
Copy link
Contributor

@tutububug tutububug commented Mar 7, 2024

storage format description: https://github.com/apache/kvrocks-website/pull/207/files.

Only dense is supported now, and Merge is moved in d3b2978 / 22923f9 and would be back in coming patches

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.

I'll take a careful review this week

src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved
src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved
@torwig
Copy link
Contributor

torwig commented Mar 7, 2024

@tutububug Thank you for your contribution. Running ./x.py format should eliminate the linting issues.

@Yangsx-1
Copy link
Contributor

Yangsx-1 commented Mar 7, 2024

Thank you for your contribution! Maybe you need to add a command parser to use the data structure with actual command like redis.

@PragmaTwice
Copy link
Member

Thank you for your contribution!

Could you include your design in the PR description? For example, explain how to encode the metadata and HLL data (subkeys), similar to what is shown on https://kvrocks.apache.org/community/data-structure-on-rocksdb.

src/storage/redis_db.cc Outdated Show resolved Hide resolved
@tutububug
Copy link
Contributor Author

tutububug commented Mar 7, 2024

Thank you for your contribution! Maybe you need to add a command parser to use the data structure with actual command like redis.

Yes, I will give the commit later.

Thank you for your contribution!

Could you include your design in the PR description? For example, explain how to encode the metadata and HLL data (subkeys), similar to what is shown on https://kvrocks.apache.org/community/data-structure-on-rocksdb.

OK.

@tutububug
Copy link
Contributor Author

@PragmaTwice I create a PR(apache/kvrocks-website#207) for describe hyperloglog storage format.

@PragmaTwice
Copy link
Member

PragmaTwice commented Mar 9, 2024

@PragmaTwice I create a PR(apache/kvrocks-website#207) for describe hyperloglog storage format.

Thank you!
Currently, it is only necessary to include your design in the PR description, not on the website as the design is not finalized yet.

Regarding your design, I have some questions:

  1. Based on the current design, typically one Redis key will introduce a maximum of 16384 RocksDB keys (registers). Each value corresponding to a RocksDB key contains only one integer. This may be inefficient; merging multiple registers onto one key could reduce the number of keys introduced. WDYT?
  2. I noticed that integers are stored as string representations (std::to_string) rather than their binary form (e.g., subkey and register value). What is the reason for this approach?
  3. Having a constant size seems illogical since the number of subkeys linked to this Redis key varies. (However, if every write operation modifies the metadata, it may lead to a decrease in performance. I don't have a clear idea about this aspect.)

Concerning the code, although I haven't reviewed it thoroughly yet, there are some points worth mentioning:

  1. The complete source code of MurmurHash can be placed in the vendor directory.
  2. It appears that using PFADD in the code leads to an increasing number of RocksDB keys (registers). There seems to be no operation that reduces these keys until deleting this Redis key. How can we prevent an increase in RocksDB keys without a mechanism to decrease them?

cc @git-hulk @mapleFU

@PragmaTwice PragmaTwice changed the title Support Hyperloglog Add support of the Hyperloglog data structure Mar 9, 2024
@git-hulk
Copy link
Member

git-hulk commented Mar 9, 2024

@tutububug As @PragmaTwice mentioned in #2142 (comment), it's unnecessary to use a static number of 16384 since it may heavily affect the read performance while using PFMERGE, I guess a smaller one like 16 is enough and every subkey has 1000 integers.

@git-hulk git-hulk self-requested a review March 9, 2024 07:25
src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved
src/common/status.h Outdated Show resolved Hide resolved
@PragmaTwice
Copy link
Member

PragmaTwice commented Mar 9, 2024

I suggest that we can store the number of registers in one rocksdb key in the metadata (for example, referred to as register_number_in_one_key), so that even if adjustments are made later for performance reasons, the compatibility of kvrocks data can be maintained.

Currently, a fixed value for register_number_in_one_key can be hardcoded in the code.

cc @tutububug

@mapleFU
Copy link
Member

mapleFU commented Mar 9, 2024

Based on the current design, typically one Redis key will introduce a maximum of 16384 RocksDB keys (registers). Each value corresponding to a RocksDB key contains only one integer. This may be inefficient; merging multiple registers onto one key could reduce the number of keys introduced. WDYT?

There're two parts of things.

  1. Bitmap type can be used to do the dense logic, which would reducing the keycount
  2. sparse hll type can be introduced to save some overhead here.

@PragmaTwice
Copy link
Member

Based on the current design, typically one Redis key will introduce a maximum of 16384 RocksDB keys (registers). Each value corresponding to a RocksDB key contains only one integer. This may be inefficient; merging multiple registers onto one key could reduce the number of keys introduced. WDYT?

There're two parts of things.

  1. Bitmap type can be used to do the dense logic, which would reducing the keycount
  2. sparse hll type can be introduced to save some overhead here.

So I think the question is, should we also introduce two mode of hll encoding (sparse and dense layout) and an auto switching policy between these two layout?

@mapleFU
Copy link
Member

mapleFU commented Mar 9, 2024

So I think the question is, should we also introduce two mode of hll encoding (sparse and dense layout) and an auto switching policy between these two layout?

I prefer to do this :-) But we can regard it as a further optimization

src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved
src/types/redis_hyperloglog.h Outdated Show resolved Hide resolved
src/types/redis_hyperloglog.h Outdated Show resolved Hide resolved
src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved
src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved
src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved

/* Store the value of the register at position 'regnum' into variable 'target'.
* 'p' is an array of unsigned bytes. */
#define HLL_DENSE_GET_REGISTER(target, p, regnum) \
Copy link
Member

Choose a reason for hiding this comment

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

can you change it to a function?

src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved
src/types/redis_hyperloglog.cc Outdated Show resolved Hide resolved
@mapleFU
Copy link
Member

mapleFU commented Mar 9, 2024

Other Looks ok to me

@tutububug tutububug closed this Mar 9, 2024
@tutububug tutububug reopened this Mar 9, 2024
@tutububug
Copy link
Contributor Author

Regarding your design, I have some questions:

1. Based on the current design, typically one Redis key will introduce a maximum of 16384 RocksDB keys (registers). Each value corresponding to a RocksDB key contains only one integer. This may be inefficient; merging multiple registers onto one key could reduce the number of keys introduced. WDYT?

Not really. The register(subkey) only be stored which its count is not zero. This point is different from the memory implementation with static array as dense encode. On disk, I think its sparse encode naturally.

2. I noticed that integers are stored as string representations (`std::to_string`) rather than their binary form (e.g., subkey and register value). What is the reason for this approach?

The number of consecutive 0s is calculated from the last 50 digits of the hash value, so the maximum value is 50, and the maximum value stored in a string is 2 bytes. It should not waste a lot of space, and at the same time save the overhead of integer encoding and decoding. For keys, it may be more efficient, but the largest index is only 5 bytes (16383).

3. Having a constant `size` seems illogical since the number of subkeys linked to this Redis key varies. (However, if every write operation modifies the metadata, it may lead to a decrease in performance. I don't have a clear idea about this aspect.)

Currently, the ‘size’ variable has no practical purpose; the only requirement is that it be non-zero. Due to the implementation of kvrocks getmetadata, non-string type data structures with a size of 0 are judged to be expired, and the HLL add parameter that redis has implemented allows no parameters but the key will be stored. For compatibility, size is used as a constant to prevent expiration.

Concerning the code, although I haven't reviewed it thoroughly yet, there are some points worth mentioning:

1. The complete source code of MurmurHash can be placed in the `vendor` directory.

OK.

2. It appears that using `PFADD` in the code leads to an increasing number of RocksDB keys (registers). There seems to be no operation that reduces these keys until deleting this Redis key. How can we prevent an increase in RocksDB keys without a mechanism to decrease them?

For an HLL user key, the maximum register value is 16384, and it cannot be larger. In fact, I think this should be considered controllable compared to data structures such as hash, set, list, etc. whose size is determined by user input.

@PragmaTwice cc @git-hulk @mapleFU

@mapleFU
Copy link
Member

mapleFU commented Mar 9, 2024

The number of consecutive 0s is calculated from the last 50 digits of the hash value, so the maximum value is 50, and the maximum value stored in a string is 2 bytes. It should not waste a lot of space, and at the same time save the overhead of integer encoding and decoding. For keys, it may be more efficient, but the largest index is only 5 bytes (16383).

For rocksdb value, it's should be 1-2 bytes payload, the value size is also included. So, it introduce an extremly huge overhead. So I prefer the impl of bitmap/bitfield.

Get 2^14 times in rocksdb would also be heavy, and might break some statistics in rocksdb, which making it tent to compaction more or caching the unneccessary blocks.

@mapleFU mapleFU marked this pull request as ready for review July 30, 2024 15:52
@mapleFU mapleFU changed the title Add support of the Hyperloglog data structure feat(HLL): Add support of the Hyperloglog data structure Jul 30, 2024
@@ -49,6 +49,8 @@ enum RedisType : uint8_t {
kRedisStream = 8,
kRedisBloomFilter = 9,
kRedisJson = 10,
kRedisSearch = 11,
Copy link
Member

Choose a reason for hiding this comment

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

need to delete

};

REDIS_REGISTER_COMMANDS(MakeCmdAttr<CommandPfAdd>("pfadd", -2, "write", 1, 1, 1),
MakeCmdAttr<CommandPfCount>("pfcount", 2, "read-only", 1, 1, 1), );
Copy link
Member

Choose a reason for hiding this comment

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

How about PFMERGE?

Copy link
Member

Choose a reason for hiding this comment

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

Would be add in next patch

DCHECK_LT(index, kHyperLogLogRegisterCount);
hash >>= kHyperLogLogRegisterCountPow; /* Remove bits used to address the register. */
hash |= (static_cast<uint64_t>(1U) << kHyperLogLogHashBitCount);
uint8_t ctz = __builtin_ctzll(hash) + 1;
Copy link
Member

Choose a reason for hiding this comment

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

This is a little difference from valkey code, I've send a pr to them

PragmaTwice
PragmaTwice previously approved these changes Jul 30, 2024
@PragmaTwice PragmaTwice requested a review from git-hulk July 30, 2024 17:08
@git-hulk
Copy link
Member

I think it's good to merge after fixing the lint issues.

@PragmaTwice PragmaTwice changed the title feat(HLL): Add support of the Hyperloglog data structure feat(hyperloglog): Add support of the Hyperloglog data structure Jul 31, 2024
@PragmaTwice PragmaTwice changed the title feat(hyperloglog): Add support of the Hyperloglog data structure feat(hyperloglog): add support of the Hyperloglog data structure Jul 31, 2024
@mapleFU mapleFU mentioned this pull request Jul 31, 2024
9 tasks
@mapleFU mapleFU merged commit 727bec2 into apache:unstable Jul 31, 2024
31 checks passed
@mapleFU
Copy link
Member

mapleFU commented Jul 31, 2024

Merged, thanks all!

Copy link

sonarcloud bot commented Jul 31, 2024

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.

6 participants