Strings, Lists, and Hashes

[!NOTE] This module explores the core principles of Strings, Lists, and Hashes, deriving solutions from first principles and hardware constraints to build world-class, production-ready expertise.

1. Strings: The Universal Building Block

A Redis String is binary-safe, meaning it can store text, serialized JSON, images, or raw bytes. It’s the most basic but versatile type.

Internals: SDS (Simple Dynamic String)

Redis does not use C-style strings (char*). It uses SDS.

  • O(1) Length: Stores length in a header.
  • Buffer Overflow Protection: Tracks free space.
  • Binary Safe: Can store null bytes \0.

Use Case: Distributed Rate Limiter

Prevent abuse by limiting API calls (e.g., 10 requests per minute).

Java

public boolean allowRequest(String userId) {
  String key = "rate:" + userId;
  long current = jedis.incr(key);

  if (current == 1) {
    jedis.expire(key, 60); // Reset after 1 minute
  }

  return current <= 10;
}

Go

func AllowRequest(ctx context.Context, userID string) bool {
  key := "rate:" + userID

  // Atomically increment
  count, _ := rdb.Incr(ctx, key).Result()

  if count == 1 {
    rdb.Expire(ctx, key, 60*time.Second)
  }

  return count <= 10
}

2. Lists: Queues and Stacks

Redis Lists are Doubly Linked Lists.

  • Push/Pop: O(1) at head or tail.
  • Access: O(N) by index (slow for middle elements).

Internals: Quicklist

To save memory, Redis compresses small lists into a Ziplist (contiguous memory chunk). Large lists become a Quicklist (linked list of ziplists).

Use Case: Job Queue

Producer pushes jobs, Consumer processes them.

[!TIP] Blocking Pop (BLPOP): Instead of polling (looping with LPOP), use BLPOP. It puts the connection to sleep until an item arrives. Zero CPU waste.

Java

// Producer
jedis.lpush("jobs", "job_123");

// Consumer (Blocks for 0 = infinite)
List<String> result = jedis.blpop(0, "jobs");
String job = result.get(1); // [key, value]
process(job);

Go

// Producer
rdb.LPush(ctx, "jobs", "job_123")

// Consumer
res, err := rdb.BLPop(ctx, 0, "jobs").Result()
if err == nil {
  job := res[1] // [key, value]
  Process(job)
}

3. Hashes: The Object Store

Hashes map string fields to string values. Perfect for storing objects (User, Product).

  • Memory Efficient: Small hashes are encoded as Ziplists, consuming very little RAM compared to full overhead of keys.
  • Atomic Field Updates: HINCRBY can increment just one field (e.g., cart_items) without reading the whole object.

4. Interactive: Command Pipeline Visualizer

Visualize how commands affect data structures.

Operations

Memory State

Select a command...