Proposal for Cuckoo Filter #3530
nagisa-kunhah
started this conversation in
General
Replies: 1 comment 1 reply
-
|
You did a great job, but I would like the content to include more data-structure-related material like this (https://kvrocks.apache.org/community/data-structure-on-rocksdb). That way, we can directly move it onto the page when we release. |
Beta Was this translation helpful? Give feedback.
1 reply
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Cuckoo Filter Design Proposal for Apache Kvrocks
1. Introduction
This proposal describes the design for adding RedisBloom-compatible Cuckoo Filter support to Apache Kvrocks.
The related tracking issue is #3123. The first implementation work is being developed in #3481, starting with
CF.RESERVEandCF.ADD. This proposal updates the earlier Cuckoo Filter discussion in #3079 and reflects the design changes made during review, especially the move from one RocksDB key per bucket to paged bucket storage.Cuckoo Filter is a probabilistic data structure for approximate set membership. It is similar to Bloom Filter in that it can return false positives, but it also supports deletion because each inserted item is represented by a small fingerprint stored in one of two candidate buckets. RedisBloom exposes Cuckoo Filter through commands such as
CF.RESERVE,CF.ADD,CF.ADDNX,CF.EXISTS,CF.MEXISTS,CF.COUNT,CF.DEL,CF.INFO,CF.INSERT,CF.INSERTNX,CF.SCANDUMP, andCF.LOADCHUNK.Kvrocks already supports RedisBloom-style Bloom Filter commands. Adding Cuckoo Filter support improves RedisBloom compatibility and provides users with a probabilistic structure that can later support duplicate counting and deletion semantics.
2. Design Summary
This proposal uses a chained Cuckoo Filter design. A logical Cuckoo Filter key stores compact metadata in the Metadata column family and stores bucket contents as paged subkeys in the PrimarySubkey column family. Each page contains multiple logical buckets to avoid the overhead of one RocksDB key per bucket. Expansion appends new sub-filters instead of rebuilding existing data.
3. Command Categories
The RedisBloom-compatible Cuckoo Filter command set can be implemented in several groups. This grouping also gives us a natural way to split the work into reviewable PRs.
Creation and insertion commands
These commands create a filter or add items to it. They are the foundation of the data layout because they define how metadata, pages, buckets, fingerprints, duplicate items, and expansion are written.
CF.RESERVE key capacity [BUCKETSIZE bucket_size] [MAXITERATIONS max_iterations] [EXPANSION expansion]CF.ADD key itemCF.ADDNX key itemCF.INSERT key [CAPACITY capacity] [NOCREATE] ITEMS item [item ...]CF.INSERTNX key [CAPACITY capacity] [NOCREATE] ITEMS item [item ...]Membership and count commands
These commands read the candidate buckets of one or more items. They need to search all sub-filters in the chain because an item may have been inserted before or after expansion.
CF.EXISTS key itemCF.MEXISTS key item [item ...]CF.COUNT key itemDelete command
This command removes one matching fingerprint occurrence from the filter. It depends on the same lookup path as
CF.EXISTS, but also needs to update the page and metadata atomically.CF.DEL key itemInformation command
This command exposes the logical filter state and is useful for users and tests to inspect capacity, size, number of filters, expansion, bucket size, and related metadata.
CF.INFO keyDump and load commands
These commands export and restore the internal Cuckoo Filter representation. They are important for RedisBloom compatibility and for workflows that need chunked serialization.
CF.SCANDUMP key iteratorCF.LOADCHUNK key iterator data4. High-Level Design
A logical Cuckoo Filter is represented by one Redis key. Internally, it is modeled as a chain of sub-filters:
CuckooChainrepresents the logical Redis key and owns high-level operations such as reserve, add, expansion, metadata validation, and write-batch commit.CuckooSubFilterrepresents one sub-filter inside the chain. It implements normal insertion and kick-out insertion for one sub-filter.CuckooPageCachemaps logical buckets to persisted page values, reads the required pages, stages dirty page updates, and writes modified pages into the final RocksDB write batch.CuckooFilterHelperprovides stable algorithm-level helpers such as hashing, fingerprint generation, alternate-bucket calculation, capacity normalization, and bucket-count calculation.The design uses chained expansion instead of in-place resizing. Resizing a Cuckoo Filter in place would change bucket indexes because bucket placement depends on the number of buckets, which would require rebuilding existing data. Instead, when the current chain cannot accept an item and scaling is enabled, Kvrocks appends a new sub-filter. Existing sub-filters remain valid and do not need to be rewritten.
The main storage decision is to persist pages instead of individual buckets. The Cuckoo algorithm still works on logical bucket indexes, but RocksDB stores fewer, larger values.
5. Cuckoo Filter on RocksDB
Kvrocks stores one logical Cuckoo Filter as one metadata entry and multiple page subkeys. The metadata describes the logical filter chain, while page subkeys store bucket slots. This follows Kvrocks' metadata/subkey model and avoids introducing a new RocksDB column family.
5.1 Logical Structure
A logical Cuckoo Filter contains one or more sub-filters. Each sub-filter contains logical buckets. Each bucket contains
bucket_sizeone-byte fingerprint slots. Buckets are grouped into page values before being stored in RocksDB.5.2 Metadata
In the examples below,
ns_keyrepresents Kvrocks' internal key after namespace handling.The Redis type name is
MBbloomCF, matching RedisBloom's Cuckoo Filter type name.CuckooChainMetadataextends the existing KvrocksMetadatabase class. The base metadata continues to provide common key-level fields such as type, expiration, version, and logical size. Cuckoo-specific fields are encoded after the base metadata.sizeuint64_texpireuint64_tversionuint64_tn_filtersuint16_texpansionuint16_t0means non-scaling mode. Non-zero values are normalized to a power of two.base_capacityuint64_texpansion.bucket_sizeuint8_tmax_iterationsuint16_tnum_deleted_itemsuint64_tpage_sizeuint32_tpage_sizeis an internal Kvrocks storage parameter. It is not exposed as a Redis command option.5.3 Page Subkeys and Values
The earlier design stored each Cuckoo bucket as a separate RocksDB key. Review feedback pointed out that this would create too many tiny keys. For example, if a bucket contains only a few bytes, the RocksDB internal key, memtable, block index/filter, and compaction overhead can dominate the actual payload.
The updated design groups multiple logical buckets into one persisted page value.
sub_keyis encoded separately:filter_index: the sub-filter index inside the chain.page_index: the page index inside that sub-filter.page_data: a byte array containing multiple logical buckets.The
versionis inherited from the metadata and encoded byInternalKey. It separates current page subkeys from stale page subkeys left by older versions of the same logical key.A fingerprint value of
0means the slot is empty. Valid fingerprints are in the range1..255.For example, if
bucket_size = 2andpage_size = 2048, each page can store1024logical buckets. Updating one bucket rewrites the containing page value, but avoids creating one RocksDB key per bucket.5.4 Bucket Location
The number of buckets per page is:
The mapping from a logical bucket to a page is:
6. Hashing and Fingerprints
The hashing model is part of the persistent data layout, so it must remain stable once Cuckoo Filter data has been written.
The current implementation follows RedisBloom-style hashing:
HllMurMurHash64A(data, length, 0).hash % 255 + 1.0is reserved as the empty-slot marker.hash % num_buckets.hash ^ (fingerprint * 0x5bd1e995).alternate_hash % num_buckets.For an item:
During kick-out insertion, the implementation may no longer have the original full item hash for an evicted fingerprint. It computes the alternate bucket from the current bucket index and the fingerprint:
The bucket count is rounded to a power of two. With a power-of-two bucket count, using the bucket index in the alternate-bucket calculation preserves the required symmetry for moving a fingerprint between its two candidate buckets.
7. Capacity and Expansion
The requested capacity is converted into a bucket count using a target load factor of
0.955.The power-of-two bucket count is required for correct alternate-bucket behavior and better distribution.
The default parameters are aligned with RedisBloom compatibility:
capacityfor auto-created filters1024BUCKETSIZE2MAXITERATIONS20EXPANSION1page_size2048bytesEXPANSIONbehavior:EXPANSION 0disables scaling. If the filter becomes full, insertion returns an error.EXPANSION 1appends new sub-filters with the same capacity as the first sub-filter.EXPANSIONvalue is32768.The capacity of sub-filter
iis derived as:For non-scaling filters,
n_filtersshould remain1.8. Atomicity, Expiration, and Versioning
All writes that modify bucket/page data and metadata are committed through one RocksDB write batch.
This is important because
metadata.size,metadata.n_filters, and page contents must describe the same logical state. For example, after a successfulCF.ADD, the modified page and the incremented metadata size should become visible together.Kvrocks metadata versioning is reused for Cuckoo Filter page subkeys. The page key includes the current metadata version, so stale pages from older versions can be ignored after key overwrite or deletion. This follows the same general pattern used by other complex data types in Kvrocks.
Expiration is handled by the existing metadata layer. Cuckoo Filter-specific code should use
Database::GetMetadata/type-aware metadata loading instead of raw metadata CF reads so expired keys and wrong-type cases are handled consistently.9. Replication and Migration
Replication uses Kvrocks' existing write-batch propagation mechanism. Cuckoo Filter writes include write-batch log data with the
kRedisCuckooFiltertype. The actual metadata and page updates are then replayed as part of the replicated write batch.For slot migration:
Command-based migration is difficult because Cuckoo Filter pages contain only fingerprints, not original items. Kvrocks cannot reconstruct a semantically equivalent sequence of
CF.ADDcommands from the persisted data.The current behavior should explicitly return an unsupported error for
MBbloomCFcommand migration and ask users to use raw key-value migration.10. Disk Usage and Scan/Type Integration
The new type is registered as
kRedisCuckooFilterwith Redis type nameMBbloomCF.Integration points:
TYPE keyshould returnMBbloomCF.SCAN ... TYPE MBbloomCFshould include Cuckoo Filter keys.DISK USAGE keyshould account for the metadata entry and page subkeys.metadata.size == 0.11. Trade-Offs
11.1 Chained Sub-Filters Instead of Rehashing
Appending sub-filters avoids rebuilding existing data and keeps expansion cheap. The trade-off is that future lookup, count, and delete commands may need to inspect candidate buckets in multiple sub-filters.
For
CF.EXISTS, this means checking the two candidate buckets in each sub-filter until a match is found or all sub-filters are exhausted.For
CF.COUNT, this means scanning the two candidate buckets in each sub-filter and summing matching fingerprints.For
CF.DEL, this means finding and removing one matching fingerprint occurrence across the chain.11.2 Paged Buckets Instead of Bucket-Per-Key
Paged bucket storage reduces RocksDB key overhead and improves space efficiency. The trade-off is write amplification at the page level: modifying one bucket rewrites the containing page value. This is acceptable because the page size is bounded and the alternative bucket-per-key design would create a large number of very small RocksDB entries.
The default page size is currently 2048 bytes. This should be treated as an internal storage parameter, not a user-facing RedisBloom option.
12. References
Beta Was this translation helpful? Give feedback.
All reactions