Chapter 03 · Strings, Counters, Bitmaps, Bitfields, and Binary Values
INCR / DECR / INCRBYFLOAT: Counters, Quotas, Sequencing, and Overflow/Precision Awareness
Build atomic counters with exact 64-bit integer boundaries, understand floating-point normalization and precision, and reason about quotas, sequencing, retries, persistence, and failover.
Learning outcomes
AtlasMart needs page-view counters, inventory reservation
attempts, per-customer quotas, and monotonic-looking internal
sequence numbers. Teams often reach for
INCR because it is atomic and fast, then
accidentally assume the stored value has a numeric data type,
that counters cannot overflow, that retries cannot double-count,
or that INCRBYFLOAT is exact enough for money.
Redis provides precise scalar operations; the application still
owns the meaning.
Explain how INCR/DECR/INCRBY interpret String bytes as base-10 signed 64-bit integers and what happens for missing, non-numeric, wrong-type, and overflow cases.
Prove that in-place numeric mutation keeps an existing key TTL while replacement with SET can remove it.
Use INCRBYFLOAT with an explicit model of double parsing, normalized stored output, and precision limits; reject it for exact financial arithmetic.
Distinguish an atomic counter from a gapless sequence, an idempotent event count, a durable monotonic identifier, or a globally ordered distributed log.
Build bounded quota/counter fixtures and design retry, persistence, replication, Cluster, ACL, and observability requirements around them.
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. No
financial/customer production data is used. Boundary tests use
disposable keys; integer overflow commands are expected to
error without changing the stored value.
1. Redis has no separate integer type
INCR, DECR, INCRBY, and
DECRBY are String commands. If a key is absent,
Redis treats its starting numeric value as zero. If a String
cannot be represented as a signed 64-bit integer, Redis returns
an error. If the key contains another Redis data type, the
command also errors. The successful result is stored back in the
String value and returned as an integer reply.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:counter:ordersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCR atlasmart:ch03:counter:ordersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCRBY atlasmart:ch03:counter:orders 9docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DECR atlasmart:ch03:counter:ordersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GET atlasmart:ch03:counter:ordersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TYPE atlasmart:ch03:counter:orders# Expected: 1, 10, 9, "9", and type "string".
This is useful because the server performs read-modify-write atomically for one key. Two clients issuing INCR do not both read the same old value and overwrite each other. The property belongs to the Redis command; it does not make the calling business workflow atomic with an external database, payment provider, or message broker.
2. Existing TTL survives INCR; SET replacement does not
Redis expiration is attached to the key. Commands that mutate
the value in place, such as INCR, leave the timeout
intact. A replacement with plain SET removes it.
That difference is central for fixed-window counters and
temporary quotas.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch03:counter:ttl 5 EX 120docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch03:counter:ttldocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCR atlasmart:ch03:counter:ttldocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch03:counter:ttl# -> still positive.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch03:counter:ttl 99docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch03:counter:ttl# -> -1 because SET replaced the value/lifecycle.
A counter policy must therefore state which commands are allowed. “This counter has a TTL” is not a permanent property if arbitrary writers may replace the key.
3. Signed 64-bit boundaries are observable, and overflow is an error
Integer operations are limited to signed 64-bit values: from −9,223,372,036,854,775,808 through 9,223,372,036,854,775,807. Redis does not silently wrap INCR/DECR at these boundaries. An overflowing command errors and leaves the existing value unchanged.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch03:counter:max 9223372036854775807docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCR atlasmart:ch03:counter:maxdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GET atlasmart:ch03:counter:max# -> error, then the maximum value is still stored.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch03:counter:min -9223372036854775808docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app DECR atlasmart:ch03:counter:mindocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GET atlasmart:ch03:counter:min# -> error, then the minimum value is still stored.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch03:counter:notnum tendocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCR atlasmart:ch03:counter:notnum# -> integer/parsing error; the String "ten" remains.
This failure injection is safe because it allocates only tiny values. In a real system, alert before a counter approaches a domain limit; do not rely on the final overflow error as a capacity plan.
4. INCRBYFLOAT is convenient arithmetic, not exact money
INCRBYFLOAT parses the existing String and
increment as floating-point numbers, stores a normalized decimal
String, and returns that String. Current Redis documentation
states that output precision is fixed at 17 digits after the
decimal point regardless of internal computation precision. This
is not a decimal-currency contract.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch03:metric:ratio 10.50docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCRBYFLOAT atlasmart:ch03:metric:ratio 0.1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCRBYFLOAT atlasmart:ch03:metric:ratio -5docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GET atlasmart:ch03:metric:ratio# Redis stores a normalized decimal representation with trailing zeroes removed.
For exact monetary totals, use an integer minor unit such as
cents only if the currency/business domain truly fits that
model, or use an authoritative decimal-capable
datastore/application arithmetic with explicit scale and
rounding. A counter named
revenue_dollars incremented by binary floating
values is vulnerable to representation and policy errors even
when every Redis command succeeds.
Redis documents that successful INCRBYFLOAT operations are propagated to replication and the Append Only File as a SET operation, reducing cross-platform floating-point divergence in replay. That implementation detail does not make the arithmetic suitable for financial invariants.
5. Atomic counter does not mean exactly-once event count
Suppose a client sends
INCR atlasmart:orders:accepted, Redis executes it,
but the TCP connection breaks before the reply reaches the
caller. The client cannot infer from the timeout whether the
increment happened. Blindly retrying may produce a second
increment. This is the classic
ambiguous outcome of a non-idempotent
operation.
Likewise, INCR can generate unique values on one
current primary, but gaps are normal when a client reserves an
ID and aborts, and rollback/failover/durability behavior depends
on persistence and replication state. Do not advertise it as a
gapless sequence or a globally durable order unless your
architecture proves those stronger properties.
| Need | Can INCR help? | What else is required |
|---|---|---|
| Approximate operational event counter | Yes | Retry/error accounting; reconciliation if exactness matters. |
| Per-key atomic quota count | Yes | Window/lifecycle policy and idempotency of caller actions. |
| Unique local sequence value | Often | Gap tolerance, persistence/failover analysis, overflow budget. |
| Exact money balance | No as sole primitive | Decimal/ledger invariants and transactional domain design. |
| Exactly-once cross-system event count | No | Deduplication/idempotency and authoritative event/state reconciliation. |
6. Deliberately wrong quota: floating money and retry-blind increments
An AtlasMart prototype charges a customer by running
INCRBYFLOAT balance 0.10 and retries on every
timeout. Two distinct mistakes are mixed together:
floating-point arithmetic is being used as an exact ledger, and
a non-idempotent increment is retried without an operation
identity.
The repair is architectural. Keep financial truth in a ledger designed for exact decimal/transactional semantics. If Redis mirrors a derived metric, label it as derived and rebuildable. For event counters that must not double-apply, pair the event identity with an idempotency mechanism rather than assuming retry means “the first command definitely failed.” Chapter 13 introduces WATCH/transactions and Chapter 23 revisits idempotency patterns.
7. Hands-on lab: counters, quota state, and boundary evidence
This lab creates a fixed-lifetime synthetic API counter and separate integer/floating boundary fixtures. It does not simulate payment state. Because an exact “increment and create TTL only on first increment” primitive varies with newer Redis features and scripting choices, this lesson uses a pre-created counter with an explicit TTL so the core semantics remain transparent.
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:l2:quota atlasmart:ch03:l2:seq atlasmart:ch03:l2:ratiodocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SET atlasmart:ch03:l2:quota 0 EX 300 NXdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCR atlasmart:ch03:l2:quotadocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCRBY atlasmart:ch03:l2:quota 4docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app TTL atlasmart:ch03:l2:quotadocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCR atlasmart:ch03:l2:seqdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCR atlasmart:ch03:l2:seqdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app INCRBYFLOAT atlasmart:ch03:l2:ratio 0.125docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app MGET atlasmart:ch03:l2:quota atlasmart:ch03:l2:seq atlasmart:ch03:l2:ratiodocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch03:l2:quota# Cleanup only this lesson.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:l2:quota atlasmart:ch03:l2:seq atlasmart:ch03:l2:ratio atlasmart:ch03:counter:max atlasmart:ch03:counter:min atlasmart:ch03:counter:notnum atlasmart:ch03:counter:ttl atlasmart:ch03:metric:ratio atlasmart:ch03:counter:orders
Verification checklist:
- The integer counter is a Redis String and the quota TTL remains positive after INCR/INCRBY.
- Overflow and non-numeric tests produce errors without silently wrapping or replacing the stored value.
- You can state the signed 64-bit integer range and explain why capacity planning must stay far inside it.
- You can explain why INCRBYFLOAT is not an exact currency primitive and why retries make non-idempotent increments ambiguous.
- You recorded persistence mode and standalone topology before making any durability/ordering claim.
8. Production judgment
Use Redis counters when atomic scalar mutation is a good fit and when the counter's business semantics tolerate Redis's persistence, replication, retry, and failover model. Track counter cardinality, update rate, hot-key concentration, value range, TTL distribution, command error rates, client timeouts/retries, AOF/replication bandwidth, and p50/p95/p99 latency under realistic concurrency. A single hot global counter may serialize a large fraction of workload even though each INCR is O(1).
In Cluster, a single counter is one key in one hash slot, so all traffic for that counter reaches its owning shard. Sharding the metric can reduce hot-key pressure but changes read/aggregation semantics. ACLs should grant only the commands/key patterns the application needs. Rollback/migration plans must preserve the numeric encoding and bounds expected by old and new clients.
9. Summary and next step
Redis integer counters are String values interpreted as signed 64-bit base-10 numbers and changed atomically per command. Overflow and parse errors are explicit. In-place increments preserve TTL. INCRBYFLOAT stores normalized floating-point results but is not exact decimal money. Atomic increments do not make retries exactly once or sequences gapless/durable by themselves. Next, you will treat Strings as larger byte arrays: fetching many keys, slicing ranges, appending data, and storing arbitrary binary payloads while measuring memory and network consequences.
Check your understanding
- What type does TYPE report for a key modified with INCR?
- Does INCR remove an existing TTL?
- What happens when INCR would exceed 9,223,372,036,854,775,807?
- Why is INCRBYFLOAT unsuitable as an exact money ledger?
- Why can retrying INCR after a timeout double-count?
Review the answers
1. string; Redis has no separate integer data type.
2. No. It mutates the value in place and leaves the existing timeout intact.
3. Redis returns an overflow error and does not silently wrap the integer counter.
4. It uses floating-point parsing/computation/output semantics rather than an explicit decimal scale/rounding ledger contract.
5. The server may have executed the first increment even if the reply was lost, so the retry is a second non-idempotent operation.
Authoritative references
- INCR command — signed 64-bit parsing and atomic increment semantics
- INCRBY command — bounded integer addition
- DECR command — integer decrement semantics
- INCRBYFLOAT command — floating-point parsing, normalization, output precision, and propagation detail
- EXPIRE command — timeout behavior and commands that preserve/remove TTL