Skip to content

Repository files navigation

Distributed API Idempotency Engine

A distributed API idempotency middleware built with Go, Redis, Lua, and Next.js to prevent duplicate processing of retryable POST, PUT, and PATCH requests.

The engine coordinates concurrent requests using an atomic Redis-backed state machine. Requests sharing the same tenant + idempotency key are coordinated so that concurrent duplicates do not execute the downstream operation simultaneously. Once a request completes, its response can be replayed for subsequent retries.

Project goal: Explore reliable idempotency, concurrency control, atomic state transitions, and failure handling in distributed REST APIs.

Architecture

                         Client
                           |
                           | HTTP Request
                           | Idempotency-Key
                           v
                  +-------------------+
                  |    Go API/Proxy   |
                  +---------+---------+
                            |
                            | Atomic state transition
                            v
                  +-------------------+
                  |   Redis + Lua     |
                  |                   |
                  | Started           |
                  | Completed        |
                  | Failed           |
                  | Response Cache    |
                  +---------+---------+
                            |
                            | First request only
                            v
                  +-------------------+
                  | Downstream API    |
                  | / Database /      |
                  | Payment Service   |
                  +-------------------+

                  +-------------------+
                  | Next.js Dashboard |
                  |                   |
                  | Request replay    |
                  | Race simulation   |
                  | Redis inspection  |
                  +-------------------+

Why Idempotency?

Distributed clients commonly retry requests when they experience:

  • Network timeouts
  • Connection failures
  • HTTP 5xx responses
  • Load-balancer retries
  • Client-side retry policies

For state-mutating APIs, blindly retrying a request can cause duplicate side effects.

For example:

Client
  |
  | POST /payment
  |
  v
Payment Service
  |
  | Payment succeeds
  |
  X Network timeout
  |
Client does not know whether payment succeeded
  |
  | Retry
  v
Payment Service

Without idempotency protection, the second request may create a duplicate operation.

This project uses an idempotency key to coordinate retries and duplicate requests.

Core Design

Each request is identified using:

tenant + idempotency key

The request payload is also fingerprinted so that the same idempotency key cannot silently be reused with a different request payload.

A simplified request lifecycle is:

                 +-----------+
                 |  Missing  |
                 +-----+-----+
                       |
                       | First request
                       v
                 +-----------+
                 |  Started  |
                 +-----+-----+
                       |
              +--------+--------+
              |                 |
              | Success         | Failure
              v                 v
       +-------------+    +-------------+
       |  Completed  |    |   Failed    |
       +-------------+    +-------------+
              |
              |
              v
       Replay cached
          response

The actual state transitions are implemented using Redis Lua scripts so the coordination logic executes atomically.

Request Scenarios

1. First request

Request
  |
  | tenant=A
  | key=abc
  v
Redis
  |
  | key does not exist
  v
STARTED
  |
  v
Downstream operation
  |
  v
COMPLETED

The resulting response can be stored for subsequent retries.

2. Duplicate request after completion

Request
  |
  | tenant=A
  | key=abc
  v
Redis
  |
  | COMPLETED
  v
Cached response
  |
  v
Client

The downstream operation does not need to execute again.

3. Concurrent duplicate requests

Request 1 ───────┐
                 |
Request 2 ───────┤
                 |
Request 3 ───────┼──> Redis + Lua
                 |
Request 4 ───────┤
                 |
Request 5 ───────┘
                       |
                       v
                One request owns
                the operation
                       |
                       v
                Downstream API
                       |
                       v
                Cached response

The dashboard includes a race-condition simulation that sends multiple concurrent requests using the same idempotency key.

4. Payload mismatch

The same idempotency key should not represent different operations.

For example:

Request 1:
key = payment-123
amount = 100

Request 2:
key = payment-123
amount = 500

The request fingerprint allows the engine to detect this mismatch instead of treating the second request as the same operation.

5. Downstream failure

If the downstream operation fails, the request can be transitioned out of the active state so that a later retry can attempt the operation again.

Redis + Lua

The critical coordination operations are implemented as Redis Lua scripts.

This avoids a fragile sequence such as:

GET
  |
  v
check state
  |
  v
SET

where multiple workers can observe the same state between operations.

Instead, the state transition is performed atomically inside Redis.

Conceptually:

Request
   |
   v
Lua Script
   |
   +-- key missing? ------> acquire request
   |
   +-- request running? --> coordinate duplicate
   |
   +-- completed? --------> replay response
   |
   +-- failed? -----------> allow retry

Key Namespacing

Idempotency state is namespaced using the tenant and idempotency key.

Conceptually:

idempotency:{tenant}:{key}

This prevents unrelated tenants from accidentally sharing idempotency state.

Request Fingerprinting

The request payload is fingerprinted before processing.

This allows the engine to distinguish:

Same key + same payload

from:

Same key + different payload

The second case is rejected rather than silently reusing the result of a different request.

Dashboard

The project includes a Next.js dashboard for demonstrating the engine.

The dashboard supports:

  • Proxy request execution
  • Replaying the same request
  • Payload mismatch simulation
  • Concurrent race-condition simulation
  • Downstream failure simulation
  • Redis key inspection
  • TTL inspection
  • Manual key eviction

Project Structure

.
├── api/
│   ├── proxy.go
│   └── inspect.go
│
├── app/
│   └── page.tsx
│
├── pkg/
│   ├── engine/
│   │   ├── state machine
│   │   ├── Lua scripts
│   │   ├── hashing
│   │   └── key namespacing
│   │
│   └── store/
│       └── Redis client
│
├── public/
├── package.json
├── go.mod
└── README.md

Technology Stack

Component Technology
Backend Go
State Store Redis / Upstash Redis
Atomic Coordination Redis Lua
Dashboard Next.js / TypeScript
Deployment Vercel + Upstash Redis
Testing Go test
Static Analysis go vet

Requirements

  • Go version declared in go.mod
  • Node.js 20+
  • npm
  • Upstash Redis database

Configuration

Create an Upstash Redis database and configure:

UPSTASH_REDIS_URL=rediss://default:<password>@<host>:<port>

For local development, place the variable in .env.local or export it in the shell.

Do not commit credentials or .env.local.

Local Development

Install frontend dependencies:

npm install

Start the development server:

npm run dev

Open:

http://localhost:3000

Validation

Run the Go tests:

go test ./...

Run static analysis:

go vet ./...

Run frontend linting:

npm run lint

Run TypeScript validation:

npx tsc --noEmit

Build the Next.js application:

npm run build

Vercel Deployment

The project can be deployed as a Next.js application with the Go API handlers deployed as serverless functions.

  1. Push the repository to GitHub.
  2. Import the repository into Vercel.
  3. Configure:
UPSTASH_REDIS_URL=<your-upstash-redis-url>
  1. Use the repository root as the project root.
  2. Build using:
npm run build

Failure Handling

The project is designed to explore several distributed failure scenarios:

Client retry

A client retries the same request after a timeout.

The idempotency key allows the engine to determine whether the request is new, already executing, or already completed.

Concurrent duplicate requests

Multiple clients send the same request concurrently.

Redis Lua-based coordination prevents the requests from independently executing the same operation.

Downstream failure

If downstream processing fails, the request can be retried instead of permanently remaining in an active state.

Cached response replay

Once a request reaches a terminal successful state, subsequent retries can receive the previously stored response.

Important Security Considerations

This repository is intended as a technical demonstration, not a production-ready public proxy.

The Redis inspection endpoint can read and delete idempotency state and should therefore be protected or removed before public deployment.

The proxy also supports a client-supplied X-Target-URL for demonstration purposes. In a production system, downstream destinations should be restricted using an allowlist or configured server-side rather than accepting arbitrary client-provided URLs.

Design Trade-offs

Why Redis?

Redis provides:

  • Low-latency key/value operations
  • Atomic operations
  • TTL support
  • Lua scripting
  • Shared state across multiple application instances

Why Lua?

The idempotency state transition often requires multiple logical checks and updates.

A Lua script allows the coordination logic to execute atomically within Redis rather than relying on multiple client-side commands.

Why cache completed responses?

A client may retry after a successful downstream operation but before receiving the response.

Returning the stored response allows the retry to receive the original result without repeating the downstream operation.

What This Project Demonstrates

  • Distributed request coordination
  • API idempotency
  • Redis state machines
  • Redis Lua scripting
  • Atomic state transitions
  • Race-condition handling
  • Retry semantics
  • Response caching
  • Request fingerprinting
  • Multi-tenant key namespacing
  • REST API middleware design
  • Go backend development

Disclaimer

This project is an educational implementation focused on distributed-systems concepts and failure handling. Production systems should additionally consider authentication, authorization, downstream transaction semantics, Redis availability, observability, rate limiting, persistence requirements, and deployment security.

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages