Upstash Redis2026-07-03

[Performance] Implementing Distributed Locks in Serverless Microservices: Ensuring Data Consistency with Upstash Redis


The Difficulty of "Data Consistency" Unique to Serverless Environments

In traditional monolithic web applications, it was relatively easy to prevent multiple processes from simultaneously rewriting the same data (e.g., product inventory, account balances, user registration slots) using database transaction locks or mutual exclusion (Mutex) within the application's memory.

However, in cloud-native serverless architectures (Next.js API Routes, AWS Lambda), data conflicts occur very easily due to the following characteristics:

  1. Fully Distributed Execution: Each API request is processed in parallel in completely isolated containers (instances) geographically and systematically. Therefore, variable locking in local memory does not work at all.
  2. Rapid Scale-Out: Within milliseconds, tens to hundreds of containers spin up simultaneously and execute write queries concurrently against the same database.
  3. Retry Mechanisms: When a network error occurs midway, automatic retries are triggered, increasing the risk of "double execution" where an already completed process is called again.

In this distributed environment, the mechanism to correctly protect the order and exclusivity of all processes is the "Distributed Lock". Here, we explain the implementation of a lightweight and robust locking algorithm (a simplified version of Redlock) using Upstash Redis, an ultra-fast, globally shareable memory data store.


2. Before & After: Resolving the Challenges

Item No Lock Control (Before) Upstash Redis Distributed Lock (After)
Simultaneous Access Inventory goes negative when orders come in at almost the same moment Safely queues subsequent processes until the first one completes
API Response Speed Overall response degrades due to table locks on the database side Processing completes in milliseconds via memory-based fast validation
Stuck on Error If a crash occurs, the lock remains unreleased Automatically released and recovered by setting a Time-To-Live (TTL)
Development Cost Requires adjusting complex RDB Isolation Levels Can be implemented with a simple exclusive control API based on SETNX

3. Concrete Implementation Example: Atomic Distributed Locks Using Redis

Below is an example implementation of a helper class that can be reused in Next.js API Routes to temporarily lock a resource and reliably unlock it after processing is complete.

import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

export class DistributedLock {
  private lockKey: string;
  private lockValue: string;
  private ttlSeconds: number;

  constructor(resourceName: string, ttlSeconds = 10) {
    this.lockKey = `lock:dist:${resourceName}`;
    // Use a random value to prove ownership of the lock (prevents deadlocks and accidental releases)
    this.lockValue = Math.random().toString(36).substring(2) + Date.now().toString(36);
    this.ttlSeconds = ttlSeconds;
  }

  // Attempt to acquire the lock
  async acquire(): Promise<boolean> {
    // NX: Set only if the key does not exist, EX: Automatically expire after the specified seconds
    const result = await redis.set(this.lockKey, this.lockValue, {
      nx: true,
      ex: this.ttlSeconds,
    });
    return result !== null;
  }

  // Safely release the lock (only delete the lock if we are the ones who acquired it)
  async release(): Promise<boolean> {
    // Use a Lua script to atomically execute "check value and delete"
    const luaScript = `
      if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
      else
        return 0
      end
    `;

    const result = await redis.eval(luaScript, [this.lockKey], [this.lockValue]);
    return result === 1;
  }
}

Usage Example: Check Flow to Prevent Double Billing

export async function POST(req: Request) {
  const { paymentId } = await req.json();
  
  const lock = new DistributedLock(`payment:${paymentId}`, 15); // Lock for 15 seconds
  const isAcquired = await lock.acquire();

  if (!isAcquired) {
    return NextResponse.json({
      error: "Conflict",
      message: "Payment is currently processing. Please wait a moment to prevent duplicate submissions.",
    }, { status: 409 });
  }

  try {
    // Execute payment processing...
    return NextResponse.json({ success: true });
  } finally {
    // Ensure the lock is released
    await lock.release();
  }
}

4. HadayaLab's Distributed System Architecture Design

HadayaLab provides the latest cloud infrastructure designs for companies struggling with data conflicts and performance degradation typical of serverless environments.

  • Implementing Highly Scalable Distributed Locks: A robust design where data does not break down even when thousands of requests converge simultaneously.
  • Ensuring Idempotency: Safe API designs where a request is executed only once, even if it is sent twice due to network conditions.
  • Hybrid Design with Upstash Redis / Cloud Database: An optimal data layout where static information is cached and writes are transactionally controlled.

If you are an engineering manager who wants to "eliminate data inconsistencies in reservation systems or cart payments" or "learn the correct data exclusive control for serverless infrastructure," please contact HadayaLab.

Get Your Free Workflow Audit