Grokking Redis
Vote

0% completed

​

2.1 Strings

What a String Is

Core Commands

GET and SET

SETEX, SETNX, and Two-Step Writes

Multiple Keys

INCR and the Numeric Family

Other Useful Commands

Pitfalls

Using This in an Interview

FAQs

Key Takeaways

A single key that holds a sequence of bytes is the simplest value Redis stores, and Redis calls it a string. In most production deployments, strings are also the majority of keys: cached API responses, session tokens, rate limit counters, feature flags, locks, and serialized objects are all strings. The type looks simple, but the commands around it provide atomic counters, conditional writes, and expiry in the same step as the write.

This lesson follows one example service, a product catalog, through the commands. The catalog caches product 981 as a string and counts its page views in another string, and the next lesson uses the same catalog for rate limits, cached objects, and locks.

What a String Is

A Redis string is binary safe, which means Redis stores the bytes exactly as given and never interprets them unless you call a command that treats them as a number or as bits. A string can therefore hold text, a number, JSON, a protobuf message, a compressed blob, or an image thumbnail. The maximum size is 512 MB by default (the proto-max-bulk-len setting caps a single request at the same size). Values anywhere near that size cause problems, because a large value is slow to send over the network and the main thread is busy with it while it is copied in or out.

Internally, Redis picks one of three encodings, and OBJECT ENCODING key shows which one a key uses. A value that is a valid 64-bit signed integer, such as a view counter, uses the int encoding and is stored directly in the object header with no separate allocation. A short string uses the embstr encoding, where the object header and the bytes share one allocation, which is cheaper to create and keeps related bytes close together in memory. Redis 8 stores the key name in that same allocation, so the limit depends on the key and the value together: on Redis 8.10, a value stays embstr while the key name and the value add up to about 41 bytes or less. A 4-byte key name such as name allows a value of 37 bytes, and a 10-byte key name allows 31 bytes. Older versions used a fixed limit of 44 bytes for the value alone. Longer strings use the raw encoding, a separate dynamic string with its own length header.

You rarely choose an encoding yourself. Knowing them explains two facts you will meet when sizing memory: small numeric values are cheap, and for a tiny value the per-key overhead can be larger than the value itself.

Redis String Encodings: INT vs. EMBSTR vs. RAW
Redis String Encodings: INT vs. EMBSTR vs. RAW

Core Commands

GET and SET

SET key value stores a value and replaces any existing value of any type. GET key returns the value, or (nil) if the key does not exist. SET does more than it first appears, because its options combine a condition, an expiry, and the write into one atomic command:

  • EX seconds and PX milliseconds set a relative expiry, and EXAT and PXAT set an absolute Unix time.
  • NX writes only if the key does not exist, and XX writes only if it does.
  • GET returns the old value while storing the new one.
  • KEEPTTL keeps the key's current expiry.
  • Since Redis 8.4, IFEQ value writes only if the current value equals the given one, and IFNE only if it differs. IFDEQ and IFDNE compare against a hash of the value (its digest) instead of the value itself.

Here the catalog caches product 981 for five minutes and then checks the remaining time to live (TTL):

redis> SET product:981 '{"id":981,"name":"Desk Lamp","price_cents":4999}' EX 300 OK redis> TTL product:981 (integer) 300

One detail causes many memory leaks. A plain SET on an existing key removes its TTL, so a temporary key overwritten without KEEPTTL or a new EX silently becomes permanent:

redis> SET product:981 '{"id":981,"name":"Desk Lamp","price_cents":4499}' OK redis> TTL product:981 (integer) -1

The reply -1 means the key exists but has no expiry. Writing SET product:981 <json> KEEPTTL, or passing a new EX, would have kept the cached copy temporary.

Redis SET Command: NX, XX, EX, and KEEPTTL Explained
Redis SET Command: NX, XX, EX, and KEEPTTL Explained

SETEX, SETNX, and Two-Step Writes

SETEX key seconds value sets the value and the expiry together, and PSETEX is the millisecond form. SETNX key value sets the value only if the key is absent. All three are deprecated since Redis 2.6.12 in favor of SET with EX, PX, or NX, and they still work, so you will see them in older code.

SETNX also has a real weakness: it cannot set an expiry. Setting a value and its TTL as two separate commands is unsafe, because a crash or an error between them leaves a key that never expires. SET key value NX EX 30 does all three things in one command, so that failure cannot happen.

Multiple Keys

MSET and MGET write and read many keys in one round trip, which is usually much faster than many single calls because the network round trip often costs more than the command. Since Redis 8.4, MSETEX sets several keys and one shared expiry atomically, which plain MSET cannot do.

In Redis Cluster, the keyspace is split into 16,384 hash slots spread across nodes, and every key in a multi-key command must map to the same slot. A hash tag makes related keys share a slot: when a key contains {...}, only the text between the braces is hashed, so {product:981}:views and {product:981}:stock always land together.

INCR and the Numeric Family

INCR adds one to an integer value and returns the result, and DECR subtracts one. INCRBY and DECRBY apply a given amount, and INCRBYFLOAT works with decimals. A missing key is treated as zero, so the first INCR returns 1. If the value is not a valid integer, the command returns an error, and integers are limited to the 64-bit signed range.

These commands are atomic because Redis executes one command at a time on its main thread. If a thousand clients increment views:product:981 at the same moment, the counter grows by exactly one thousand, with no lost updates and no locks in the application:

redis> INCR views:product:981 (integer) 1 redis> INCRBY views:product:981 10 (integer) 11 redis> OBJECT ENCODING views:product:981 "int"

Since Redis 8.8, INCREX adds what INCR lacks: lower and upper bounds, and an expiry set in the same command. It returns two values, the new value and the increment actually applied. If the result would cross a bound, it leaves the key unchanged and returns an applied increment of 0, unless you add SATURATE to cap the value at the bound instead. The rate limiter in the next lesson uses it.

Other Useful Commands

APPEND adds bytes to the end of a string, STRLEN returns its length, and GETRANGE and SETRANGE read or write part of it. GETDEL (Redis 6.2) returns a value and deletes it in one atomic step, which suits one-time tokens. GETEX (Redis 6.2) returns a value while changing its TTL, which suits sliding expiration on sessions. SETBIT, GETBIT, and BITCOUNT treat the string as an array of bits; the Bitmaps and Bitfields lesson covers them.

Pitfalls

  • Overwriting clears the TTL. Use KEEPTTL or set a new expiry on every write.
  • Huge values. Multi-megabyte strings are slow to transfer and keep the main thread busy. Keep values small, compress them, or split them.
  • Type errors. INCR on a non-numeric string returns an error, and a string command on a key of another type returns a WRONGTYPE error. Key names that show the type help.
  • Float precision. INCRBYFLOAT is subject to floating point rounding, so store money in integer units such as cents.

Using This in an Interview

Describe a string as a binary-safe value that Redis can also treat as an integer or a bit array. Then show the atomic building blocks: SET with NX, EX, and KEEPTTL for conditional writes with expiry, INCR for counters without locks, and INCREX on Redis 8.8 or later for a fixed window rate limiter in one command.

Why not GET, change, and SET a counter? Two clients can read the same value, so one increment is lost, while INCR runs as one command and cannot interleave.

Why is SET with NX and EX safer than SETNX followed by EXPIRE? One command cannot stop halfway, so a crash cannot leave the key without an expiry.

FAQs

Is INCR really atomic?

Yes. Each command runs to completion before the next one starts, so concurrent increments never lose updates. Atomicity covers one command, not a sequence such as INCR then EXPIRE, which needs INCREX, a transaction, or a script.

What is the difference between SETEX and SET with EX?

They store the same result. SETEX is deprecated since Redis 2.6.12, while SET with options is the current form and also supports NX, XX, GET, KEEPTTL, and the Redis 8.4 compare options.

What is the maximum size of a string?

512 MB by default. Practical values should be far smaller, ideally a few kilobytes, because large values hurt latency and memory.

Key Takeaways

  • Strings are binary-safe values up to 512 MB. They hold text, numbers, serialized objects, or bits, and integer values use the compact int encoding.
  • SET combines a condition, an expiry, and the write. EX, PX, NX, XX, GET, KEEPTTL, and the Redis 8.4 IFEQ family run as one atomic command, and SETEX and SETNX are deprecated forms.
  • INCR and its relatives are atomic. They support counters, ID generators, and rate limiters without locks, and INCREX (Redis 8.8) adds bounds and expiry in the same command.
  • Watch the traps. Overwriting clears TTLs, and large values keep the main thread busy.

Reading Progress

0%


Vote for new content

On This Page

What a String Is

Core Commands

GET and SET

SETEX, SETNX, and Two-Step Writes

Multiple Keys

INCR and the Numeric Family

Other Useful Commands

Pitfalls

Using This in an Interview

FAQs

Key Takeaways