Skip to content

Ask the HotKV docs

Answers come only from these docs and can be wrong; check the linked pages before you rely on them.

Probabilistic structures

Last updated

HotKV has three structures that trade a small, known amount of accuracy for a fixed and small memory footprint. They are built in: every HotKV server has them, with no module to load and no license needed.

Structure Answers Can be wrong how
Bloom filter “Have I seen this item?” May say yes for an item never added (a false positive); never says no for one that was added
Count-min sketch “How often have I seen this item?” May overestimate a count; never underestimates
Top-K “Which items do I see most often?” May miss or misorder items whose counts are close

For approximate counts of distinct items, see HyperLogLog.

Each structure is a key of its own type: TYPE reports MBbloom--, CMSk-TYPE or TopK-TYPE, the same names Redis gives them. They are saved in snapshots and the AOF, replicated, and can be given an expiry like any other key. The commands, arguments, replies and error messages follow those of Redis 8.

A Bloom filter remembers that items were added, in a fraction of the space the items would take, so you can skip work for items that are certainly new. Typical uses: “has this user already seen this article?”, “is this username taken?” before a database lookup, or “have we crawled this URL?”.

BF.ADD creates a filter with default settings (room for 100 items at a 1% false-positive rate) if the key is missing:

127.0.0.1:6379> BF.ADD seen:urls "https://example.com/a"
(integer) 1
127.0.0.1:6379> BF.ADD seen:urls "https://example.com/a"
(integer) 0
127.0.0.1:6379> BF.EXISTS seen:urls "https://example.com/a"
(integer) 1
127.0.0.1:6379> BF.INFO seen:urls
1) Capacity
2) (integer) 100
3) Size
4) (integer) 240
5) Number of filters
6) (integer) 1
7) Number of items inserted
8) (integer) 1
9) Expansion rate
10) (integer) 2

BF.ADD replies 1 when the item is new and 0 when the filter may already contain it. BF.EXISTS replies 1 for “probably added” and 0 for “definitely not added”. Under RESP3 these are booleans.

For real workloads, size the filter up front with BF.RESERVE: the false-positive rate you can accept, and how many items you expect.

127.0.0.1:6379> BF.RESERVE users:taken 0.001 1000000
OK
127.0.0.1:6379> BF.MADD users:taken "ada" "grace" "ada"
1) (integer) 1
2) (integer) 1
3) (integer) 0
127.0.0.1:6379> BF.MEXISTS users:taken "ada" "linus"
1) (integer) 1
2) (integer) 0
127.0.0.1:6379> BF.CARD users:taken
(integer) 2
127.0.0.1:6379> BF.INFO users:taken CAPACITY
1) (integer) 1000000

What to know about sizing:

  • The error rate must be between 0 and 1 (exclusive) and the capacity from 1 to 1073741824. Lower error rates and higher capacities take more memory.
  • A filter that fills up grows: once its newest layer holds the items it was sized for, the next new item adds a layer EXPANSION times larger (2 by default, up to 32768) with a tighter error rate, so the filter as a whole keeps to the rate you asked for. A good capacity estimate keeps a filter to one layer.
  • A NONSCALING filter never grows: once full, adding a new item fails with ERR non scaling filter is full.
  • No single layer may exceed 4 GiB, and a filter is not created or grown when the server lacks the memory for it (ERR Insufficient memory to create filter).
  • Items cannot be removed from a Bloom filter. To start over, delete the key.

BF.INSERT adds items and, if needed, creates the filter with the options you give; with NOCREATE it refuses to create one. BF.SCANDUMP and BF.LOADCHUNK copy a filter out and back in, in chunks, for example to move a large filter to another HotKV server.

A Count-min sketch counts how often each item occurs in a stream of events, in a fixed-size table no matter how many different items there are. Use it for request counts per IP or per API key, word frequencies, or “how many times was this product viewed” when you track far more items than you want to store counters for.

Create a sketch with explicit dimensions (CMS.INITBYDIM: width and depth) or from the accuracy you need (CMS.INITBYPROB: the acceptable overestimate as a fraction of the total count, and the probability of exceeding it). Wider sketches overestimate less, deeper sketches are more likely to stay within the bound.

127.0.0.1:6379> CMS.INITBYDIM page:views 2000 5
OK
127.0.0.1:6379> CMS.INCRBY page:views /home 3 /pricing 1
1) (integer) 3
2) (integer) 1
127.0.0.1:6379> CMS.QUERY page:views /home /pricing /about
1) (integer) 3
2) (integer) 1
3) (integer) 0
127.0.0.1:6379> CMS.INFO page:views
1) width
2) (integer) 2000
3) depth
4) (integer) 5
5) count
6) (integer) 4
  • CMS.INCRBY takes non-negative increments and replies with each item’s new estimated count. The key must exist; there is no automatic creation.
  • CMS.QUERY returns estimates; an item never added reads as 0 unless it shares every cell with busier items.
  • CMS.MERGE adds several sketches (optionally weighted) into an existing destination of the same width and depth, for example to roll hourly sketches into a daily one.
  • count in CMS.INFO is the total of all increments.

A Top-K list keeps the k items seen most often (the heavy hitters) as events stream in, without keeping a counter for every item. Use it for trending hashtags or products, the most active users or IPs, or the most frequent errors.

TOPK.RESERVE creates the list with its k, and optionally the width, depth and decay of the counting table behind it (8, 7 and 0.9 by default). A larger table counts more accurately when many items compete.

127.0.0.1:6379> TOPK.RESERVE trending 2 1000 7 0.9
OK
127.0.0.1:6379> TOPK.INCRBY trending shoes 3 hats 2
1) (nil)
2) (nil)
127.0.0.1:6379> TOPK.ADD trending socks
1) (nil)
127.0.0.1:6379> TOPK.LIST trending WITHCOUNT
1) "shoes"
2) (integer) 3
3) "hats"
4) (integer) 2
127.0.0.1:6379> TOPK.ADD trending socks
1) "hats"
127.0.0.1:6379> TOPK.LIST trending
1) "shoes"
2) "socks"
127.0.0.1:6379> TOPK.QUERY trending shoes hats
1) (integer) 1
2) (integer) 0
  • TOPK.ADD and TOPK.INCRBY reply, for each item, with the item it pushed out of the list, or nil when nothing was pushed out. In the session above, the second socks pushed hats out. TOPK.INCRBY takes increments from 0 to 100,000.
  • TOPK.LIST returns the current top items, highest first, with WITHCOUNT for their estimated counts.
  • TOPK.QUERY tells whether items are in the list right now; TOPK.COUNT estimates each item’s count, in the list or not.
  • TOPK.INFO returns k, width, depth and decay.
  • TOPK.ADD and the other commands need an existing list (TopK: key does not exist otherwise).

Cheap pre-check before an expensive lookup. BF.EXISTS first; only on 1 do you query the database. A 0 is always right, so most misses never reach the database.

Rate or abuse signals across millions of keys. CMS.INCRBY per event keyed by IP or token, and act when CMS.QUERY passes a threshold. One sketch per time window, with an expiry, gives a rolling picture.

Trending lists. One Top-K per time window (trending:2026-10-02T14), fed with TOPK.ADD on each event and read with TOPK.LIST.

Bloom filter, Count-min sketch and Top-K commands match the Redis 8 commands of the same names. HotKV does not have Cuckoo filters (CF.*) or t-digest (TDIGEST.*). DUMP payloads of these three types restore into HotKV only, not into Redis.

Command What it does
BF.RESERVE Create a Bloom filter with an error rate and capacity
BF.ADD / BF.MADD Add items
BF.INSERT Add items, creating the filter with given options
BF.EXISTS / BF.MEXISTS Test items
BF.CARD Number of items added
BF.INFO Capacity, size, layers, items, expansion
BF.SCANDUMP / BF.LOADCHUNK Copy a filter out and in, in chunks
CMS.INITBYDIM / CMS.INITBYPROB Create a sketch
CMS.INCRBY Count items
CMS.QUERY Estimated counts
CMS.MERGE Add sketches together
CMS.INFO Width, depth, total count
TOPK.RESERVE Create a Top-K list
TOPK.ADD / TOPK.INCRBY Count items
TOPK.LIST Current top items
TOPK.QUERY Whether items are in the list
TOPK.COUNT Estimated counts
TOPK.INFO k, width, depth, decay

Send these commands with each client’s generic command method.

import hotkv
client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)
client.execute_command("BF.RESERVE", "users:taken", 0.001, 1000000)
client.execute_command("BF.ADD", "users:taken", "ada")
print(client.execute_command("BF.EXISTS", "users:taken", "ada"))
client.execute_command("TOPK.RESERVE", "trending", 10)
client.execute_command("TOPK.ADD", "trending", "shoes", "hats")
print(client.execute_command("TOPK.LIST", "trending"))
client.close()