# Probabilistic structures

> Bloom filters, Count-min sketches, and Top-K lists for membership, frequency, and heavy-hitter questions in fixed memory.

URL: https://docs.hotkv.com/data-types/probabilistic/


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](#bloom-filters) | "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](#count-min-sketch) | "How often have I seen this item?" | May overestimate a count; never underestimates |
| [Top-K](#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](https://docs.hotkv.com/data-types/hyperloglog/index.md).

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.

## Bloom filters

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](https://docs.hotkv.com/commands/bf-add/index.md) 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](https://docs.hotkv.com/commands/bf-reserve/index.md): 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](https://docs.hotkv.com/commands/bf-insert/index.md) adds items and, if needed, creates the
filter with the options you give; with `NOCREATE` it refuses to create one.
[BF.SCANDUMP](https://docs.hotkv.com/commands/bf-scandump/index.md) and
[BF.LOADCHUNK](https://docs.hotkv.com/commands/bf-loadchunk/index.md) copy a filter out and back in, in
chunks, for example to move a large filter to another HotKV server.

## Count-min sketch

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](https://docs.hotkv.com/commands/cms-initbydim/index.md): width and depth) or from the
accuracy you need ([CMS.INITBYPROB](https://docs.hotkv.com/commands/cms-initbyprob/index.md): 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](https://docs.hotkv.com/commands/cms-incrby/index.md) takes non-negative increments and
  replies with each item's new estimated count. The key must exist; there
  is no automatic creation.
- [CMS.QUERY](https://docs.hotkv.com/commands/cms-query/index.md) returns estimates; an item never added
  reads as 0 unless it shares every cell with busier items.
- [CMS.MERGE](https://docs.hotkv.com/commands/cms-merge/index.md) 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](https://docs.hotkv.com/commands/cms-info/index.md) is the total of all
  increments.

## Top-K

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](https://docs.hotkv.com/commands/topk-reserve/index.md) 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](https://docs.hotkv.com/commands/topk-add/index.md) and [TOPK.INCRBY](https://docs.hotkv.com/commands/topk-incrby/index.md)
  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](https://docs.hotkv.com/commands/topk-list/index.md) returns the current top items, highest
  first, with `WITHCOUNT` for their estimated counts.
- [TOPK.QUERY](https://docs.hotkv.com/commands/topk-query/index.md) tells whether items are in the list
  right now; [TOPK.COUNT](https://docs.hotkv.com/commands/topk-count/index.md) estimates each item's
  count, in the list or not.
- [TOPK.INFO](https://docs.hotkv.com/commands/topk-info/index.md) returns `k`, `width`, `depth` and
  `decay`.
- `TOPK.ADD` and the other commands need an existing list
  (`TopK: key does not exist` otherwise).

## Common patterns

**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`.

## Compatibility

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.

## Commands

| Command | What it does |
| --- | --- |
| [BF.RESERVE](https://docs.hotkv.com/commands/bf-reserve/index.md) | Create a Bloom filter with an error rate and capacity |
| [BF.ADD](https://docs.hotkv.com/commands/bf-add/index.md) / [BF.MADD](https://docs.hotkv.com/commands/bf-madd/index.md) | Add items |
| [BF.INSERT](https://docs.hotkv.com/commands/bf-insert/index.md) | Add items, creating the filter with given options |
| [BF.EXISTS](https://docs.hotkv.com/commands/bf-exists/index.md) / [BF.MEXISTS](https://docs.hotkv.com/commands/bf-mexists/index.md) | Test items |
| [BF.CARD](https://docs.hotkv.com/commands/bf-card/index.md) | Number of items added |
| [BF.INFO](https://docs.hotkv.com/commands/bf-info/index.md) | Capacity, size, layers, items, expansion |
| [BF.SCANDUMP](https://docs.hotkv.com/commands/bf-scandump/index.md) / [BF.LOADCHUNK](https://docs.hotkv.com/commands/bf-loadchunk/index.md) | Copy a filter out and in, in chunks |
| [CMS.INITBYDIM](https://docs.hotkv.com/commands/cms-initbydim/index.md) / [CMS.INITBYPROB](https://docs.hotkv.com/commands/cms-initbyprob/index.md) | Create a sketch |
| [CMS.INCRBY](https://docs.hotkv.com/commands/cms-incrby/index.md) | Count items |
| [CMS.QUERY](https://docs.hotkv.com/commands/cms-query/index.md) | Estimated counts |
| [CMS.MERGE](https://docs.hotkv.com/commands/cms-merge/index.md) | Add sketches together |
| [CMS.INFO](https://docs.hotkv.com/commands/cms-info/index.md) | Width, depth, total count |
| [TOPK.RESERVE](https://docs.hotkv.com/commands/topk-reserve/index.md) | Create a Top-K list |
| [TOPK.ADD](https://docs.hotkv.com/commands/topk-add/index.md) / [TOPK.INCRBY](https://docs.hotkv.com/commands/topk-incrby/index.md) | Count items |
| [TOPK.LIST](https://docs.hotkv.com/commands/topk-list/index.md) | Current top items |
| [TOPK.QUERY](https://docs.hotkv.com/commands/topk-query/index.md) | Whether items are in the list |
| [TOPK.COUNT](https://docs.hotkv.com/commands/topk-count/index.md) | Estimated counts |
| [TOPK.INFO](https://docs.hotkv.com/commands/topk-info/index.md) | `k`, width, depth, decay |

## From your application

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

**Python**

```python
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()
```

**Node.js**

```ts
import { HotKV } from "@hotkv/client";

const client = new HotKV({ url: "hotkv://127.0.0.1:6379" });

await client.command("BF.RESERVE", "users:taken", "0.001", "1000000");
await client.command("BF.ADD", "users:taken", "ada");
console.log(await client.command("BF.EXISTS", "users:taken", "ada"));

await client.command("CMS.INITBYDIM", "page:views", "2000", "5");
await client.command("CMS.INCRBY", "page:views", "/home", "1");
console.log(await client.command("CMS.QUERY", "page:views", "/home"));
await client.close();
```

**Go**

```go
client, err := hotkv.NewClient(&hotkv.Options{Addr: "localhost:6379"})
if err != nil {
    log.Fatal(err)
}
defer client.Close()
ctx := context.Background()

pipe := client.Pipeline()
pipe.Do(ctx, "BF.RESERVE", "users:taken", "0.001", "1000000")
pipe.Do(ctx, "BF.ADD", "users:taken", "ada")
exists := pipe.Do(ctx, "BF.EXISTS", "users:taken", "ada")
if _, err := pipe.Exec(ctx); err != nil {
    log.Fatal(err)
}
fmt.Println(exists.Val())
```

**Java**

```java
import com.hotkv.HotKV;

try (HotKV client = HotKV.create("hotkv://localhost:6379")) {
    client.execute("BF.RESERVE", "users:taken", "0.001", "1000000");
    client.execute("BF.ADD", "users:taken", "ada");
    System.out.println(client.execute("BF.EXISTS", "users:taken", "ada"));

    client.execute("TOPK.RESERVE", "trending", "10");
    client.execute("TOPK.ADD", "trending", "shoes", "hats");
    System.out.println(client.execute("TOPK.LIST", "trending"));
}
```
