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.