Home / Notebooks / Database
Database
intermediate

Redis Essentials

Essential Redis concepts and commands for caching and data storage

March 10, 2024
Updated regularly

Redis Essentials

Quick reference guide for Redis fundamentals.

What is Redis?

Redis is an in-memory data structure store used as:

  • Cache: Fast data retrieval
  • Database: Persistent data storage
  • Message Broker: Pub/Sub messaging
  • Session Store: User session management
  • Queue: Task/job queuing
  • Key Features:

  • Extremely fast (sub-millisecond latency)
  • In-memory with optional persistence
  • Rich data types (strings, hashes, lists, sets, sorted sets)
  • Atomic operations
  • Built-in replication and clustering
  • Installation and Connection

    Install Redis

    # macOS
    brew install redis
    
    # Ubuntu/Debian
    sudo apt-get install redis-server
    
    # Start Redis Server
    redis-server
    
    # Start Redis CLI
    redis-cli
    

    Connect with Node.js

    // Using ioredis (recommended)
    import Redis from 'ioredis';
    
    const redis = new Redis({
      host: 'localhost',
      port: 6379,
      password: 'your-password', // if authentication enabled
      db: 0 // database number
    });
    
    // Test connection
    redis.ping().then(() => {
      console.log('Connected to Redis');
    });
    

    Connect with Python

    import redis
    
    # Create connection
    r = redis.Redis(
        host='localhost',
        port=6379,
        password='your-password',
        db=0,
        decode_responses=True
    )
    
    # Test connection
    r.ping()
    

    Basic Commands

    Keys and Basic Operations

    # ========== Set and Get ==========
    SET key value
    GET key
    
    # Example
    SET user:1:name "Yudi Nugraha"
    GET user:1:name
    # Returns: "Yudi Nugraha"
    
    # ========== Set with Expiration ==========
    SET key value EX seconds
    SETEX key seconds value
    
    # Example
    SETEX session:abc123 3600 "user_data"
    # Expires after 1 hour
    
    # ========== Set if Not Exists ==========
    SETNX key value
    
    # ========== Multiple Set/Get ==========
    MSET key1 value1 key2 value2
    MGET key1 key2
    
    # ========== Check if Key Exists ==========
    EXISTS key
    # Returns: 1 if exists, 0 if not
    
    # ========== Delete Key ==========
    DEL key
    
    # ========== Get All Keys (use with caution in production) ==========
    KEYS pattern
    # Example: KEYS user:*
    
    # ========== Set Expiration ==========
    EXPIRE key seconds
    TTL key          # Time to live in seconds
    PTTL key         # Time to live in milliseconds
    
    # ========== Remove Expiration ==========
    PERSIST key
    
    # ========== Rename Key ==========
    RENAME old_key new_key
    

    Data Types

    1. Strings

    Simple key-value pairs:

    # ========== Set and Get ==========
    SET counter 0
    GET counter
    # Returns: "0"
    
    # ========== Increment/Decrement ==========
    INCR counter           # Increment by 1
    INCRBY counter 5       # Increment by 5
    DECR counter           # Decrement by 1
    DECRBY counter 3       # Decrement by 3
    
    # ========== Append ==========
    APPEND key value
    
    # ========== Get String Length ==========
    STRLEN key
    

    Node.js Example:

    // Set value
    await redis.set('user:1:name', 'Yudi Nugraha');
    
    // Get value
    const name = await redis.get('user:1:name');
    
    // Set with expiration (60 seconds)
    await redis.set('session:abc', 'data', 'EX', 60);
    
    // Increment counter
    await redis.incr('page:views');
    

    2. Hashes

    Field-value pairs (like objects):

    # ========== Set Hash Fields ==========
    HSET user:1 name "Yudi" age 30 email "yudi@example.com"
    
    # ========== Get Hash Field ==========
    HGET user:1 name
    # Returns: "Yudi"
    
    # ========== Get All Hash Fields ==========
    HGETALL user:1
    # Returns: name "Yudi" age "30" email "yudi@example.com"
    
    # ========== Get Multiple Fields ==========
    HMGET user:1 name email
    
    # ========== Check if Field Exists ==========
    HEXISTS user:1 name
    
    # ========== Delete Hash Field ==========
    HDEL user:1 age
    
    # ========== Get All Field Names ==========
    HKEYS user:1
    
    # ========== Get All Values ==========
    HVALS user:1
    
    # ========== Increment Hash Field ==========
    HINCRBY user:1 age 1
    

    Node.js Example:

    // Set hash fields
    await redis.hset('user:1', {
      name: 'Yudi Nugraha',
      email: 'yudi@example.com',
      age: 30
    });
    
    // Get specific field
    const name = await redis.hget('user:1', 'name');
    
    // Get all fields
    const user = await redis.hgetall('user:1');
    // Returns: { name: 'Yudi Nugraha', email: 'yudi@example.com', age: '30' }
    

    3. Lists

    Ordered collections (like arrays):

    # ========== Push to List ==========
    LPUSH list value        # Push to left (beginning)
    RPUSH list value        # Push to right (end)
    
    # Example
    RPUSH tasks "task1" "task2" "task3"
    
    # ========== Pop from List ==========
    LPOP list              # Pop from left
    RPOP list              # Pop from right
    
    # ========== Get List Range ==========
    LRANGE list start stop
    LRANGE tasks 0 -1      # Get all items
    
    # ========== Get List Length ==========
    LLEN list
    
    # ========== Get Element by Index ==========
    LINDEX list index
    
    # ========== Set Element by Index ==========
    LSET list index value
    
    # ========== Remove Elements ==========
    LREM list count value
    

    Node.js Example:

    // Add items to queue
    await redis.rpush('queue:tasks', 'task1', 'task2', 'task3');
    
    // Get all items
    const tasks = await redis.lrange('queue:tasks', 0, -1);
    // Returns: ['task1', 'task2', 'task3']
    
    // Process queue (FIFO)
    const task = await redis.lpop('queue:tasks');
    

    4. Sets

    Unordered unique collections:

    # ========== Add to Set ==========
    SADD set member1 member2
    
    # Example
    SADD tags:post:1 "javascript" "nodejs" "redis"
    
    # ========== Get All Members ==========
    SMEMBERS tags:post:1
    
    # ========== Check if Member Exists ==========
    SISMEMBER tags:post:1 "javascript"
    # Returns: 1 if exists, 0 if not
    
    # ========== Remove from Set ==========
    SREM set member
    
    # ========== Get Set Size ==========
    SCARD set
    
    # ========== Random Member ==========
    SRANDMEMBER set
    
    # ========== Pop Random Member ==========
    SPOP set
    
    # ========== Set Operations ==========
    SUNION set1 set2           # Union
    SINTER set1 set2           # Intersection
    SDIFF set1 set2            # Difference
    

    Node.js Example:

    // Add tags
    await redis.sadd('tags:post:1', 'javascript', 'nodejs', 'redis');
    
    // Check if tag exists
    const hasTag = await redis.sismember('tags:post:1', 'javascript');
    // Returns: 1
    
    // Get all tags
    const tags = await redis.smembers('tags:post:1');
    // Returns: ['javascript', 'nodejs', 'redis']
    
    // Get common tags between posts
    const commonTags = await redis.sinter('tags:post:1', 'tags:post:2');
    

    5. Sorted Sets

    Ordered unique collections with scores:

    # ========== Add to Sorted Set ==========
    ZADD sortedset score member
    
    # Example (Leaderboard)
    ZADD leaderboard 100 "Alice"
    ZADD leaderboard 85 "Bob"
    ZADD leaderboard 92 "Charlie"
    
    # ========== Get Range by Rank ==========
    ZRANGE leaderboard 0 -1              # Ascending
    ZREVRANGE leaderboard 0 -1           # Descending (highest first)
    
    # ========== Get Range with Scores ==========
    ZRANGE leaderboard 0 -1 WITHSCORES
    
    # ========== Get Range by Score ==========
    ZRANGEBYSCORE leaderboard 80 100
    
    # ========== Get Member Score ==========
    ZSCORE leaderboard "Alice"
    
    # ========== Get Member Rank ==========
    ZRANK leaderboard "Alice"            # Ascending rank
    ZREVRANK leaderboard "Alice"         # Descending rank
    
    # ========== Increment Score ==========
    ZINCRBY leaderboard 10 "Alice"
    
    # ========== Get Set Size ==========
    ZCARD leaderboard
    
    # ========== Remove Member ==========
    ZREM leaderboard "Bob"
    

    Node.js Example:

    // Add scores
    await redis.zadd('leaderboard', 100, 'Alice', 85, 'Bob', 92, 'Charlie');
    
    // Get top 10 players (highest scores)
    const topPlayers = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
    // Returns: ['Alice', '100', 'Charlie', '92', 'Bob', '85']
    
    // Get player rank
    const rank = await redis.zrevrank('leaderboard', 'Alice');
    // Returns: 0 (highest rank)
    
    // Increment score
    await redis.zincrby('leaderboard', 5, 'Alice');
    

    Caching Patterns

    Cache-Aside (Lazy Loading)

    async function getUser(userId) {
      const cacheKey = `user:${userId}`;
      
      // Try to get from cache
      let user = await redis.get(cacheKey);
      
      if (user) {
        // Cache hit
        return JSON.parse(user);
      }
      
      // Cache miss - fetch from database
      user = await db.findUser(userId);
      
      // Store in cache with 1 hour expiration
      await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600);
      
      return user;
    }
    

    Write-Through

    async function updateUser(userId, data) {
      const cacheKey = `user:${userId}`;
      
      // Update database
      const user = await db.updateUser(userId, data);
      
      // Update cache
      await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600);
      
      return user;
    }
    

    Write-Behind (Write-Back)

    async function updateUserScore(userId, score) {
      const cacheKey = `user:${userId}:score`;
      
      // Update cache immediately
      await redis.set(cacheKey, score);
      
      // Queue database update (async)
      await queue.add('updateScore', { userId, score });
      
      return score;
    }
    

    Cache Invalidation

    async function deleteUser(userId) {
      // Delete from database
      await db.deleteUser(userId);
      
      // Invalidate cache
      await redis.del(`user:${userId}`);
    }
    
    // Pattern-based invalidation
    async function invalidateUserCache(userId) {
      // Delete all keys matching pattern
      const keys = await redis.keys(`user:${userId}:*`);
      if (keys.length > 0) {
        await redis.del(...keys);
      }
    }
    

    Session Management

    // Store session
    async function createSession(userId, data) {
      const sessionId = generateId();
      const sessionKey = `session:${sessionId}`;
      
      await redis.hset(sessionKey, {
        userId,
        createdAt: Date.now(),
        ...data
      });
      
      // Set expiration (24 hours)
      await redis.expire(sessionKey, 86400);
      
      return sessionId;
    }
    
    // Get session
    async function getSession(sessionId) {
      const sessionKey = `session:${sessionId}`;
      return await redis.hgetall(sessionKey);
    }
    
    // Extend session
    async function extendSession(sessionId) {
      const sessionKey = `session:${sessionId}`;
      await redis.expire(sessionKey, 86400);
    }
    
    // Delete session
    async function deleteSession(sessionId) {
      const sessionKey = `session:${sessionId}`;
      await redis.del(sessionKey);
    }
    

    Rate Limiting

    Fixed Window

    async function isRateLimited(userId, limit = 100, window = 60) {
      const key = `rate:${userId}:${Math.floor(Date.now() / 1000 / window)}`;
      
      const current = await redis.incr(key);
      
      if (current === 1) {
        // First request in window, set expiration
        await redis.expire(key, window);
      }
      
      return current > limit;
    }
    

    Sliding Window

    async function isRateLimitedSlidingWindow(userId, limit = 100, window = 60) {
      const key = `rate:${userId}`;
      const now = Date.now();
      const windowStart = now - (window * 1000);
      
      // Remove old entries
      await redis.zremrangebyscore(key, 0, windowStart);
      
      // Count requests in window
      const count = await redis.zcard(key);
      
      if (count < limit) {
        // Add current request
        await redis.zadd(key, now, `${now}`);
        await redis.expire(key, window);
        return false;
      }
      
      return true;
    }
    

    Pub/Sub (Messaging)

    Publisher

    // Publish message
    await redis.publish('notifications', JSON.stringify({
      type: 'user_registered',
      userId: 123,
      timestamp: Date.now()
    }));
    

    Subscriber

    const subscriber = new Redis();
    
    subscriber.subscribe('notifications', (err, count) => {
      console.log(`Subscribed to ${count} channel(s)`);
    });
    
    subscriber.on('message', (channel, message) => {
      console.log(`Received message from ${channel}:`, message);
      const data = JSON.parse(message);
      // Handle notification
    });
    

    Transactions

    Execute multiple commands atomically:

    // Using MULTI/EXEC
    const multi = redis.multi();
    
    multi.set('key1', 'value1');
    multi.set('key2', 'value2');
    multi.incr('counter');
    
    const results = await multi.exec();
    // All commands executed atomically
    
    // Watch keys for changes
    await redis.watch('balance:1');
    const balance = await redis.get('balance:1');
    
    const multi = redis.multi();
    multi.set('balance:1', balance - 100);
    multi.set('balance:2', balance + 100);
    
    try {
      await multi.exec();
      // Transaction succeeded
    } catch (error) {
      // Transaction failed (key was modified)
    }
    

    Lua Scripts

    Execute complex operations atomically:

    // Rate limiting script
    const script = `
      local key = KEYS[1]
      local limit = tonumber(ARGV[1])
      local window = tonumber(ARGV[2])
      local current = redis.call('INCR', key)
      
      if current == 1 then
        redis.call('EXPIRE', key, window)
      end
      
      if current > limit then
        return 0
      end
      
      return 1
    `;
    
    const allowed = await redis.eval(
      script,
      1,
      `rate:user:123`,
      100,
      60
    );
    
    if (allowed) {
      // Request allowed
    } else {
      // Rate limited
    }
    

    Best Practices

    Naming Conventions

    // Use colons to create hierarchy
    user:1234:profile
    user:1234:sessions
    order:5678:items
    
    // Use descriptive names
    cache:product:1234
    session:abc123def
    rate:user:5678:api
    

    Memory Management

    // Set expiration on keys
    await redis.set('cache:key', value, 'EX', 3600);
    
    // Use appropriate data types
    // Hash for objects (more memory efficient than JSON string)
    await redis.hset('user:1', { name: 'Yudi', age: 30 });
    
    // Monitor memory usage
    const info = await redis.info('memory');
    

    Error Handling

    try {
      await redis.get('key');
    } catch (error) {
      console.error('Redis error:', error);
      // Fallback to database or return error
    }
    
    // Connection error handling
    redis.on('error', (error) => {
      console.error('Redis connection error:', error);
    });
    
    redis.on('connect', () => {
      console.log('Connected to Redis');
    });
    

    Common Use Cases

    1. Session Store

    // Express.js with Redis session
    import session from 'express-session';
    import RedisStore from 'connect-redis';
    
    app.use(session({
      store: new RedisStore({ client: redis }),
      secret: 'your-secret',
      resave: false,
      saveUninitialized: false,
      cookie: { maxAge: 86400000 } // 24 hours
    }));
    

    2. Job Queue

    // Using Bull (Redis-based queue)
    import Bull from 'bull';
    
    const emailQueue = new Bull('email', {
      redis: { host: 'localhost', port: 6379 }
    });
    
    // Add job
    await emailQueue.add({ to: 'user@example.com', subject: 'Welcome' });
    
    // Process jobs
    emailQueue.process(async (job) => {
      await sendEmail(job.data);
    });
    

    3. Real-time Leaderboard

    // Add score
    await redis.zadd('leaderboard:daily', score, userId);
    
    // Get top 10
    const top10 = await redis.zrevrange('leaderboard:daily', 0, 9, 'WITHSCORES');
    
    // Get user rank
    const rank = await redis.zrevrank('leaderboard:daily', userId);
    

    4. Distributed Lock

    async function acquireLock(lockKey, timeout = 10) {
      const lockValue = generateId();
      const acquired = await redis.set(
        lockKey,
        lockValue,
        'EX',
        timeout,
        'NX'
      );
      
      return acquired ? lockValue : null;
    }
    
    async function releaseLock(lockKey, lockValue) {
      const script = `
        if redis.call("get", KEYS[1]) == ARGV[1] then
          return redis.call("del", KEYS[1])
        else
          return 0
        end
      `;
      
      return await redis.eval(script, 1, lockKey, lockValue);
    }
    

    Performance Tips

  • Use pipelining for multiple commands
  • Use connection pooling in production
  • Set appropriate TTL on cached data
  • Use hashes instead of multiple keys
  • Avoid KEYS command in production (use SCAN instead)
  • Monitor memory usage regularly
  • Use Redis Cluster for horizontal scaling
  • Enable persistence for critical data
  • Resources

  • Redis Official Documentation
  • Redis Commands Reference
  • Redis Best Practices
  • Redis University
  • ioredis Documentation
  • Topics

    RedisCachingNoSQLDatabaseBackend

    Found This Helpful?

    If you have questions or suggestions for improving these notes, I'd love to hear from you.