AI & Software Solution
Backend

Redis: Master In-Memory Data Store for Modern Applications

GE

Guvantic Engineering

Guvantic Technical Publication

15 min
Redis: Master In-Memory Data Store for Modern Applications
Executive Architectural Overview (AEO Direct Answer)

Deep dive into Redis architecture, data structures, persistence, caching patterns, and real-world use cases with production code examples.

Redis delivers sub-millisecond latency by keeping data in memory. This guide covers everything from basic caching to advanced patterns like rate limiting, distributed locking, and real-time pub/sub.

The Problem: Database Bottlenecks

Your application is slow. Database queries take 200-500ms, and you're hitting connection limits. Users are leaving. Here's the solution.

Architectural Topology Specification
Application Without Redis — Shows: User → App → Database (slow path on every request)
Architectural Topology Specification
Application With Redis — Shows: User → App → Redis Cache (fast path, 1ms) → Database (only on cache miss)

Redis Architecture Explained

Architectural Topology Specification
Redis Internal Architecture — Shows:
Event loop (single-threaded)
In-memory data store with data structures
Persistence: RDB (snapshot) and AOF (append-only log)
Replication: Master-Slave
Sentinel for high availability
Cluster for sharding

Key Architecture Decisions:

1.Single-threaded event loop: No locking overhead, simple and fast
2.In-memory: All data in RAM for sub-millisecond latency
3.Data structures optimized: Specialized memory layouts
4.Persistence optional: Trade durability for performance

Data Structures and Their Use Cases

Strings

javascriptProduction snippet
// Cache a JSON object
await redis.setex("user:123", 300, JSON.stringify(user));

// Counter for rate limiting
await redis.incr("api:user:123:calls");

// TTL-based expiry
await redis.set("session:token-xyz", userId, "EX", 3600);

Hashes

javascriptProduction snippet
// Store user profile fields
await redis.hset("user:123", "name", "John", "email", "john@example.com");
await redis.hset("user:123", "lastLogin", new Date().toISOString());

// Get specific fields
const user = await redis.hgetall("user:123");

Lists (Queue Pattern)

javascriptProduction snippet
// Push to queue
await redis.rpush("email-queue", JSON.stringify({ to: "user@example.com", subject: "Welcome" }));

// Pop from queue (blocking)
const job = await redis.blpop("email-queue", 5);

Sorted Sets (Leaderboards)

javascriptProduction snippet
// Add score for a user
await redis.zadd("leaderboard", 1500, "user-123");

// Get top 10 scores
const top10 = await redis.zrevrange("leaderboard", 0, 9, "WITHSCORES");

// Get user rank
const rank = await redis.zrevrank("leaderboard", "user-123");

Sets (Unique Items)

javascriptProduction snippet
// Track unique visitors
await redis.sadd("page:home:visitors", ipAddress);

// Get unique count
const count = await redis.scard("page:home:visitors");

// Check if user has liked
const hasLiked = await redis.sismember("post:456:likes", "user-123");

Caching Patterns with Code

Cache-Aside Pattern

javascriptProduction snippet
import { Redis } from "ioredis";
import { db } from "./database";

class CacheAside {
  constructor(private redis: Redis) {}

  async getProduct(productId: string) {
    const cacheKey = `product:${productId}`;
    
    // 1. Check cache
    const cached = await this.redis.get(cacheKey);
    if (cached) {
      return JSON.parse(cached);
    }

    // 2. Cache miss - query database
    const product = await db.query("SELECT * FROM products WHERE id = ?", [productId]);

    // 3. Write to cache
    await this.redis.setex(cacheKey, 300, JSON.stringify(product));

    return product;
  }

  // Invalidate on update
  async updateProduct(productId: string, data: any) {
    // 1. Update database
    await db.query("UPDATE products SET ... WHERE id = ?", [productId, data]);

    // 2. Invalidate cache
    await this.redis.del(`product:${productId}`);
  }
}

Write-Through Pattern

javascriptProduction snippet
class WriteThrough {
  constructor(private redis: Redis, private db: any) {}

  async write(key: string, data: any) {
    // 1. Write to database
    await this.db.write(key, data);
    
    // 2. Write to cache
    await this.redis.setex(key, 300, JSON.stringify(data));
    
    // 3. Return (both completed)
    return data;
  }
}

Cache Stampede Prevention

javascriptProduction snippet
class CacheStampedeProtection {
  private mutexes = new Map<string, Promise<any>>();

  async getOrSet<T>(
    key: string,
    fetchFn: () => Promise<T>,
    ttl = 300
  ): Promise<T> {
    // Check cache first
    const cached = await this.redis.get(key);
    if (cached) {
      return JSON.parse(cached);
    }

    // Check if another request is already fetching
    if (this.mutexes.has(key)) {
      return this.mutexes.get(key);
    }

    // Only one request will fetch data
    const promise = this.fetchAndCache(key, fetchFn, ttl);
    this.mutexes.set(key, promise);

    try {
      return await promise;
    } finally {
      this.mutexes.delete(key);
    }
  }

  private async fetchAndCache<T>(
    key: string,
    fetchFn: () => Promise<T>,
    ttl: number
  ) {
    const data = await fetchFn();
    await this.redis.setex(key, ttl, JSON.stringify(data));
    return data;
  }
}

Rate Limiting with Redis

Sliding Window Rate Limiter

javascriptProduction snippet
class SlidingWindowRateLimiter {
  constructor(private redis: Redis, private maxRequests: number, private windowSeconds: number) {}

  async allow(userId: string): Promise<boolean> {
    const key = `rate_limit:${userId}`;
    const now = Date.now();
    const windowStart = now - this.windowSeconds * 1000;

    // Remove old requests
    await this.redis.zremrangebyscore(key, 0, windowStart);

    // Count current requests in window
    const count = await this.redis.zcard(key);

    if (count >= this.maxRequests) {
      return false; // Rate limited
    }

    // Add current request
    await this.redis.zadd(key, now, `${now}:${Math.random()}`);

    // Set expiry to clean up
    await this.redis.expire(key, this.windowSeconds + 10);

    return true; // Allowed
  }
}

// Usage
const limiter = new SlidingWindowRateLimiter(redis, 100, 60); // 100 requests per minute

app.use(async (req, res, next) => {
  const userId = req.user.id;
  if (!await limiter.allow(userId)) {
    return res.status(429).json({ error: "Rate limit exceeded" });
  }
  next();
});

Distributed Locking (Redlock)

Redlock Algorithm Implementation

javascriptProduction snippet
import { Redis } from "ioredis";
import { randomBytes } from "crypto";

class DistributedLock {
  constructor(private redis: Redis, private ttl = 30000) {} // 30 seconds

  async acquire(key: string): Promise<string> {
    const lockId = randomBytes(16).toString("hex");
    const acquired = await this.redis.set(
      `lock:${key}`
      lockId,
      "PX",
      this.ttl,
      "NX", // Only set if not exists
    );

    if (!acquired) {
      throw new Error("Could not acquire lock");
    }

    return lockId;
  }

  async release(key: string, lockId: string) {
    // Use Lua script for atomic release
    const script = `
      if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
      else
        return 0
      end
    `;
    
    const result = await this.redis.eval(script, 1, `lock:${key}`, lockId);
    if (result === 0) {
      throw new Error("Lock was already released or expired");
    }
  }

  async withLock<T>(key: string, fn: () => Promise<T>): Promise<T> {
    const lockId = await this.acquire(key);
    try {
      return await fn();
    } finally {
      await this.release(key, lockId);
    }
  }
}

// Usage
const lock = new DistributedLock(redis);

await lock.withLock("inventory:product-123", async () => {
  // Critical section - only one instance can execute this
  const currentStock = await getStock("product-123");
  await updateStock("product-123", currentStock - 1);
});

Persistence: RDB vs AOF

Architectural Topology Specification
Redis Persistence Options — Shows:
RDB: Point-in-time snapshots (every N seconds)
AOF: Append-only log of every write operation
Both: Best of both worlds

RDB Configuration (redis.conf)

confProduction snippet
# Save snapshots every 900s if at least 1 key changed
save 900 1
# Every 300s if 10 keys changed
save 300 10
# Every 60s if 10000 keys changed
save 60 10000

# Compression
rdbcompression yes
rdbchecksum yes

AOF Configuration

confProduction snippet
# Enable AOF
appendonly yes

# Always fsync (safest, slowest)
appendfsync always

# Fsync every second (default, balanced)
appendfsync everysec

# Never fsync (OS handles, fastest, riskiest)
appendfsync no

# Auto-rewrite
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

Production Checklist

✅Memory Management: Set maxmemory and eviction policy
✅Persistence: Configure RDB and/or AOF based on needs
✅Replication: Setup master-slave for high availability
✅Sentinel: Configure sentinel for automatic failover
✅Cluster: Use Redis Cluster for sharding
✅Monitoring: Track memory usage, hit rate, evictions
✅Security: Require authentication, use TLS
✅Backups: Regular backups of RDB files
Keywords:#Redis#in-memory database#caching#session storage#pub/sub#rate limiting#distributed locking
GE

Guvantic Engineering

Engineering & System Architecture Division

Engineering teams at Guvantic deliver mission-critical software, high-concurrency microservices, and international organic positioning strategies for fast-growing companies in USA, UK, and India.

Related Engineering Insights