Skip to content

Bitmaps

Use bitmaps for dense boolean flags addressed by integer offsets.

import { bitmap } from "benni/schema";
export const dailyActive = bitmap("daily-active");

Bitmaps take no value codec. Each bit is addressed by a non-negative integer offset and exposed as a boolean.

const previous = await redis.bitmap(dailyActive).setbit("2026-07-04", 42, true);
// ^? boolean (the bit's previous value)
const active = await redis.bitmap(dailyActive).getbit("2026-07-04", 42);
const total = await redis.bitmap(dailyActive).bitcount("2026-07-04");
const inFirstKilobyte = await redis.bitmap(dailyActive).bitcount("2026-07-04", {
start: 0,
end: 1023,
unit: "BYTE"
});

The optional range takes start, end, and a unit of "BYTE" (the Redis default) or "BIT".

const first = await redis.bitmap(dailyActive).bitpos("2026-07-04", true);
// ^? number | null

bitpos wraps BITPOS and returns null when no matching bit exists. Pass start, end, and unit to limit the search; end requires start, and unit requires both.

const sizeInBytes = await redis.bitmap(dailyActive).bitop("2026-week-27", "OR", [
"2026-07-01",
"2026-07-02",
"2026-07-03"
]);

bitop(destination, operation, sources) runs BITOP with "AND", "OR", "XOR", or "NOT" and stores the result under the destination ID. "NOT" requires exactly one source ID.

bitfield treats a key as a row of arbitrary-width integers packed at bit offsets, ideal for compact per-entity counters. Chain operations and call exec; the result is a tuple typed to match the chain:

const [views, previous, level] = await redis.bitmap(dailyActive)
.bitfield("2026-07-04")
.get("u32", 0) // number (read a 32-bit unsigned field)
.set("u32", 0, 100) // number | null (returns the previous value)
.overflow("sat") // mode for the ops that follow it
.incrby("u8", "#8", 1) // number | null (the new value)
.exec();
  • Encoding: u1u63 (unsigned) or i1i64 (signed).
  • Offset: an absolute bit offset (0), or #n to address the nth field of that width ("#8" = the 9th u8, i.e. bit offset 64).
  • get yields a number; set and incrby yield number | null.
  • overflow sets the mode for the operations after it: "wrap" (the default, modular), "sat" (clamp to the type’s min/max), or "fail" (leave the field unchanged and return null). That null is why set/incrby are nullable.
// Atomic per-user counters clamped to a byte, no read-modify-write race.
const [clamped] = await redis.bitmap(quotas)
.bitfield(userId)
.overflow("sat")
.incrby("u8", 0, 1)
.exec();
await redis.bitmap(dailyActive).del("2026-07-04");
await nodeRedis.setBit("daily-active:2026-07-04", 42, 1);
const total = await nodeRedis.bitCount("daily-active:2026-07-04");

Use bitmaps for daily-active tracking, feature rollouts keyed by numeric user ID, and any dense set of boolean flags where offsets map to entities. Use sets when members are sparse strings rather than dense integers.