Chapter 03 · Strings, Counters, Bitmaps, Bitfields, and Binary Values

BITFIELD for Packed Integers, Overflow Modes, and Memory-Efficient Counters

Pack bounded signed and unsigned integers into Redis strings with BITFIELD, choose bit widths and offsets explicitly, and make WRAP, SAT, and FAIL overflow behavior observable.

Intermediate125–155 minutesPacked integer labRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart has a telemetry record containing several tiny bounded counters: quality score 0–15, retry count 0–7, temperature delta −32–31, and a few boolean flags. One Redis key per counter is easy to read but expensive in metadata/cardinality. A plain bitmap stores only booleans. BITFIELD lets one String hold packed integers, but correctness now depends on signedness, bit width, offsets, and overflow policy.

01

Define BITFIELD signed/unsigned encodings, supported widths, bit offsets, #index addressing, and bit ordering before packing values.

02

Use GET, SET, and INCRBY subcommands and interpret their return values in the context of one atomic Redis command.

03

Prove WRAP, SAT, and FAIL overflow/underflow behavior for both signed and unsigned fields.

04

Demonstrate how wrong width, signedness, or offset silently changes interpretation even when Redis accepts the command.

05

Compare packed counters with separate keys using payload/memory/cardinality, concurrency, Cluster, durability, observability, schema-versioning, and migration tradeoffs.

Exact lab baseline

All Chapter 03 labs reuse the disposable Chapter 01 environment: Redis Open Source 8.10.1 from Docker Official Image redis:8.10.1, container atlasmart-redis-ch01, standalone topology, host publication 127.0.0.1:6379, TLS disabled only because traffic stays on loopback, default user disabled, named ACL users atlasmart-app and academy-admin, logical database 0, AOF with appendfsync everysec plus RDB snapshots, persistent /data volume, and no explicit Redis maxmemory limit/eviction policy. The application ACL is restricted to ~atlasmart:* and normal read/write/connection categories. Search, JSON, vector, time-series, and probabilistic features are not required. All packed schemas are synthetic and versioned in key names. The lab keeps fields byte-aligned when possible, then introduces one deliberately non-byte-aligned example to make bit-order dependence visible.

1. BITFIELD interprets selected bits as integers

BITFIELD is a String/bitmap-family command that can chain integer GET, SET, and INCRBY subcommands. An encoding starts with i for signed or u for unsigned, followed by a width in bits. Current Redis supports signed widths through 64 bits and unsigned widths through 63 bits because RESP integer replies cannot represent a full unsigned 64-bit range.

Encoding Range Example use
u1 0..1 Boolean flag
u4 0..15 Small bounded score
u8 0..255 Compact unsigned counter
i8 −128..127 Signed small delta
i16 −32768..32767 Larger signed sensor delta
redis-cli · pack and read two byte-aligned fields
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:bf:recorddocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:record SET u8 '#0' 12 SET i8 '#1' -5docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:record GET u8 '#0' GET i8 '#1'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app STRLEN atlasmart:ch03:bf:record# First SET values return previous values (0,0 on a new zero-filled String).# GET returns 12 and -5; #0/#1 mean field indexes, so length is 2 bytes.

2. Raw bit offsets and #field indexes are different

A plain numeric offset is a zero-based bit offset. Prefixing the offset with # multiplies it by the current encoding width. Therefore GET u8 #1 reads bits 8–15, while GET u8 1 reads an 8-bit window beginning at bit 1. Both are valid commands and can produce very different values.

redis-cli · same bytes, different offset interpretation
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:record GET u8 '#0' GET u8 '#1'docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:record GET u8 0 GET u8 1# # syntax indexes fields; bare offsets index bits.

BITFIELD numbers bit 0 as the most significant bit of the first byte. Byte-aligned fields therefore resemble big-endian bit order, while non-aligned fields require careful schema documentation. Persist the schema version outside tribal knowledge—for example in the key prefix or adjacent metadata—because the bytes do not self-describe their field widths.

3. One BITFIELD command can chain operations in order

Redis applies subcommands in the order given and returns one array entry per subcommand. Because the whole BITFIELD invocation is one Redis command, another client does not interleave a command halfway through it. This can implement compact multi-field updates, but longer command payloads still consume server execution time and should remain bounded.

redis-cli · ordered packed-field mutation
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:record GET u8 '#0' INCRBY u8 '#0' 3 GET u8 '#0'# Expected array shape: old 12, new 15, then current 15.

Do not confuse this with SQL rollback semantics. Choose encodings and overflow policy so each packed update has a defined result; validate application invariants before sending the command.

4. Overflow policy is part of the data model

The default overflow mode is WRAP. SAT clamps at the encoding's minimum/maximum. FAIL performs no write for the overflowing subcommand and returns null for that result. An OVERFLOW directive affects following SET/INCRBY subcommands until another overflow directive changes the mode.

redis-cli · WRAP, SAT, and FAIL with u8
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:bf:overflowdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:overflow SET u8 '#0' 250# Default WRAP: 250 + 10 -> 4 modulo 256.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:overflow INCRBY u8 '#0' 10 GET u8 '#0'# Reset, then SAT: clamp at 255.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:overflow SET u8 '#0' 250 OVERFLOW SAT INCRBY u8 '#0' 10 GET u8 '#0'# Reset, then FAIL: increment returns nil and stored value remains 250.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:bf:overflow SET u8 '#0' 250 OVERFLOW FAIL INCRBY u8 '#0' 10 GET u8 '#0'

For signed i8, WRAP turns 127 + 1 into −128. SAT keeps it at 127. FAIL leaves 127 unchanged and returns null for the overflowing increment. Pick the mode from domain semantics: wrap is useful for modular arithmetic, saturation for bounded meters, and fail for invariants that must reject overflow.

5. Deliberately wrong schema: signedness changes the same bits

The bit pattern 11111111 is 255 as u8 and −1 as i8. Redis cannot infer which meaning AtlasMart intended. A client that writes u8 and a later client that reads i8 can disagree without any Redis error.

redis-cli · same byte, incompatible schema interpretation
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:wrong:signednessdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:wrong:signedness SET u8 '#0' 255docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:wrong:signedness GET u8 '#0' GET i8 '#0'# Expected: 255 and -1 from the same eight bits.

The repair is schema governance: define field order, signedness, width, unit, scale, overflow policy, and schema version. Add deterministic encode/decode tests in every client language. If the packed schema evolves incompatibly, write a new key version and migrate/dual-read deliberately rather than reinterpreting old bytes in place.

6. Packed counters save metadata but concentrate contention

One String containing 1,000 u8 counters needs roughly 1,000 payload bytes plus one key/object's metadata. One thousand independent String keys need at least 1,000 key/object metadata structures before their tiny values. Packing can therefore be dramatically more memory-efficient. But all 1,000 counters now live in one key, share one TTL, one Cluster slot, one persistence/replication unit, and one hot-key contention point.

Many keys Packed BITFIELD key
Independent TTLs and ownership One lifecycle unless extra metadata/segmentation is added
More key/cardinality metadata Very low key cardinality
Can distribute across Cluster slots One packed key maps to one slot/shard
Simple per-value inspection Requires schema-aware tooling
Independent deletion/migration Schema migration can rewrite/rebuild packed bytes

7. Hands-on lab: versioned warehouse telemetry record

Pack four fields into a two-byte schema: u4 quality at bits 0–3, u4 retries at bits 4–7, i8 temp_delta at bits 8–15. Use SAT for retries so a counter cannot wrap back to zero and mislead operators. The key name contains v1 so future incompatible layouts can coexist during migration.

shell · pack, update, inspect, and verify
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:l5:telemetry:v1:warehouse-7# quality=12, retries=2, temp_delta=-7docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:l5:telemetry:v1:warehouse-7 SET u4 0 12 SET u4 4 2 SET i8 8 -7docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:l5:telemetry:v1:warehouse-7 GET u4 0 GET u4 4 GET i8 8# Increment retries with saturation at 15.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITFIELD atlasmart:ch03:l5:telemetry:v1:warehouse-7 OVERFLOW SAT INCRBY u4 4 1 GET u4 4docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app STRLEN atlasmart:ch03:l5:telemetry:v1:warehouse-7docker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch03:l5:telemetry:v1:warehouse-7docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch03:l5:telemetry:v1:warehouse-7# Expected: initial fields 12,2,-7; retries becomes 3; STRLEN is 2 bytes; TTL -1 unless policy adds one.# Cleanup:docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:l5:telemetry:v1:warehouse-7 atlasmart:ch03:bf:record atlasmart:ch03:bf:overflow atlasmart:ch03:wrong:signedness

Verification checklist:

  • You can calculate each field bit range and distinguish bare bit offsets from #field indexes.
  • You can explain u8 versus i8 interpretation and the signed/unsigned width limits in the current Redis command.
  • You observed WRAP, SAT, and FAIL rather than treating overflow as a universal behavior.
  • You can state why packing reduces key metadata but creates a shared TTL, hot key, one Cluster slot, and schema-tooling requirement.
  • You versioned the packed schema and defined a migration/rollback path instead of silently reinterpreting old bytes.

8. Production judgment

BITFIELD fits compact, bounded numeric fields that are updated/read frequently and whose shared lifecycle/locality is acceptable. Avoid it when fields need independent TTLs, rich query/indexing, frequent schema evolution, or human inspection. Bound packed-key size and update rate; measure MEMORY USAGE, command latency distributions, key hotness, replication/AOF traffic, fork/copy-on-write effects, and migration cost. Far offsets have the same allocation risk as SETBIT.

Choose overflow behavior explicitly and test boundary values in every client. Cluster places the packed record in one slot, which is useful for atomic locality but can create shard skew. ACLs can restrict access to the key pattern but cannot enforce field-level authorization inside a packed String. Backups and restore tests must preserve the bytes and the application schema version needed to interpret them.

9. Summary and next step

BITFIELD views slices of a String as signed or unsigned integers. Width, bit offset, # indexing, bit order, and overflow mode are part of the schema. WRAP is default; SAT clamps; FAIL rejects the overflowing subcommand. Packing reduces metadata and can be extremely memory-efficient, but concentrates lifecycle, routing, contention, and schema complexity into one key. Chapter 04 now moves from byte-packed records to Redis Hashes, where fields become named and object-like rather than positional bits.

Check your understanding

  1. What is the difference between BITFIELD offset 1 and #1 for u8?
  2. How can the same byte represent both 255 and -1?
  3. What does OVERFLOW FAIL do?
  4. Why might 1,000 u8 counters in one key save memory?
  5. What is the major operational cost of packing many counters into one key?
Review the answers

1. Offset 1 starts at bit 1; #1 multiplies by the u8 width and starts at bit 8.

2. Reading 11111111 as u8 gives 255; reading the same bits as signed i8 gives -1.

3. The overflowing SET/INCRBY subcommand is not performed and its result is null.

4. They need roughly 1,000 payload bytes plus one key/object instead of 1,000 separate key/object metadata structures.

5. They share one lifecycle and Cluster slot and can become a hot, schema-dependent big key.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.