Module F-2·18 min read

Why Redis Strings are not strings — they are binary-safe byte arrays. Integer encoding, atomic INCR/DECR, the GET/SET command family, MGET/MSET for batching, and why storing JSON blobs is the first mistake engineers make.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

F-2 — Strings, Numbers, and Binary Safety

Who this module is for: You have used SET and GET and assumed you understood Redis Strings. This module will show you what is actually happening under the hood — how Redis encodes integers differently from text, why "String" is a misleading name for what is really a binary-safe byte array, and why the seemingly obvious pattern of SET key (JSON.stringify(obj)) creates problems you will only discover under load.


What a Redis String Actually Is

The name "String" is the first thing Redis gets slightly wrong. When most engineers hear "string," they think of text — a sequence of Unicode characters with an encoding like UTF-8.

A Redis String is not that.

A Redis String is a binary-safe byte array — a sequence of raw bytes with no imposed encoding, no null-termination requirement, and no character set assumption. It can hold:

  • Plain text: "Hello, Redis"
  • A serialized JSON object: "{\"id\":1,\"name\":\"Jatin\"}"
  • A serialized Protocol Buffer
  • A JPEG image
  • A packed binary struct
  • An integer: "42"
  • An empty string: ""

The maximum size is 512 MB per key. In practice, values larger than a few kilobytes start to create problems (large values block the event loop during serialization, consume significant memory, and become expensive to transfer over the network), but the hard limit is 512 MB.

The "binary-safe" property matters because many key-value systems from Redis's era (early 2000s Memcached, for example) used C-style null-terminated strings, which meant you could not store arbitrary binary data — a null byte would terminate the string early. Redis stores the length alongside the data, so a null byte in the middle of a value is perfectly valid.


How Redis Encodes Strings Internally

Here is something most Redis users never learn: Redis does not store all String values the same way internally. It uses three different encodings depending on the value:

int encoding

If the value is an integer that fits in a 64-bit signed long (roughly -9.2 × 10¹⁸ to 9.2 × 10¹⁸), Redis stores the actual integer, not a string representation of it.

text

This is significant for two reasons:

  1. Memory efficiency. An integer stored as int encoding takes 8 bytes. The same number stored as a string "42" would take 2 bytes of data plus string header overhead. For large integers like Unix timestamps or IDs, the difference is meaningful at scale.

  2. Atomic arithmetic. INCR, DECR, INCRBY, DECRBY only work on keys with int encoding (or keys whose value is a string representation of an integer). Redis can parse and operate on them atomically.

Additionally, Redis maintains a shared integer pool for the integers 0 through 9999. When you store any of these values, Redis does not allocate new memory — it points to a pre-allocated shared object. This is why OBJECT REFCOUNT on small integers returns a large number.

text

embstr encoding

For strings up to 44 bytes, Redis uses embstr (embedded string) encoding. The string header and the data are allocated in a single contiguous memory block, making it cache-friendly and reducing allocator overhead.

text

embstr objects are immutable — any modification (like APPEND) causes Redis to convert the encoding to raw and reallocate.

raw encoding

For strings longer than 44 bytes, Redis uses raw encoding: a standard dynamic string (SDS — Simple Dynamic String) where the header and data are in separate memory allocations.

text

Why does this matter? Because encoding determines memory usage and performance characteristics. If you are storing millions of short strings (session tokens, feature flags, user IDs), embstr gives you better cache locality. If you are storing large JSON blobs, raw encoding is unavoidable — and that is where the "storing JSON in a String" pattern starts to cost you.


The SET Command in Full

Most engineers know SET key value. The full signature is considerably richer:

SET key value [NX | XX] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]

Let us go through each option:

Expiry options

bash

KEEPTTL is underused. It lets you update a value without accidentally removing the TTL. Without it, SET on a key that already has a TTL will reset the TTL to infinite (persistent) — a common source of session expiry bugs.

Conditional options

bash

NX is the foundation of distributed locking (covered in depth in A-5). SET key value NX EX seconds is the atomic "acquire a lock" primitive — it either sets the key and returns OK (lock acquired) or returns (nil) (lock already held).

The old pattern of SETNX + EXPIRE as two separate commands is broken — if the process crashes between the two commands, the key has no expiry and the lock is never released. SET ... NX EX is atomic. Always use it.

GET option (Redis 6.2+)

bash

This is equivalent to the old GETSET command (which is now deprecated), but integrated into SET itself.


The GET Command Family

Beyond plain GET, Redis provides several atomic read-and-modify commands:

GETDEL

bash

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.