Parottasalna

AI, Backend Engineering & Architecture Guides

Learning Notes #47 – Idempotent Post Requests

Today, i learnt about how idempotent post api can prevent us from processing duplicate request. In this blog i jot down notes on how to handle Idempotent Post Requests for future reference.

Building an idempotent POST API ensures that making the same request multiple times produces the same outcome, without unintended side effects. This behavior is not default for POST requests, which are designed to create resources. However, idempotence can be achieved through careful design.

What is Idempotency?

Idempotency means that a request can be made multiple times, and the result will remain consistent. For example:

  • A POST request to create a resource should not create duplicate entries when retried.
  • The response for a repeated request should be identical to the first response.

Idempotent POST APIs are crucial in scenarios like

  • Payment processing (to avoid duplicate charges).
  • Retry mechanisms for unreliable networks.
  • APIs handling asynchronous operations.

Steps to Build an Idempotent POST API

Step 1: Include an Idempotency Key

  • The idempotency key is a unique identifier for the request, typically generated by the client (e.g., a UUID or unique transaction ID).
  • The server uses this key to determine whether the request has already been processed.
{
    "idempotencyKey": "123e4567-e89b-12d3-a456-426614174000",
    "data": {
        "name": "Alice",
        "email": "alice@example.com"
    }
}

Step 2: Store and Manage Idempotency Keys

The server must store the idempotency key and associate it with the request’s outcome to ensure consistency across retries.

Check for Existing Key

  • When a request is received, check if the idempotency key exists in the database or cache.
  • If the key exists, return the stored response.

Process New Requests

  • If the key is not found, process the request and store the key with the response.

Ensure Atomicity

  • Use transactions to atomically process the request and store the idempotency key.

Step 3: Database Design for Idempotency

The database should store idempotency keys along with their associated responses and metadata.

CREATE TABLE idempotency_keys (
    idempotency_key UUID PRIMARY KEY,
    response_data JSONB,
    created_at TIMESTAMP DEFAULT NOW()
);
  • idempotency_key: A unique identifier for the request.
  • response_data: The stored response.
  • created_at: A timestamp to manage expiry.

Step 4: Implement Expiry for Idempotency Keys

  • Define how long idempotency keys should be stored (e.g., 24 hours).
  • Use a background job to delete expired keys to avoid excessive storage.
DELETE FROM idempotency_keys WHERE created_at < NOW() - INTERVAL '24 hours';

Step 5: Handle Consistent Responses

Ensure that the response for a repeated request with the same idempotency key is identical to the first response.

Step 6: Handle Edge Cases

  • Duplicate Resource Creation: Use unique constraints in the database to prevent duplicates.
  • External Side Effects: For example, if the API sends emails or triggers external services, ensure these operations are also idempotent.

Sample Flask App

from flask import Flask, request, jsonify
import uuid

app = Flask(__name__)

# In-memory storage for simplicity
idempotency_store = {}

@app.route('/create', methods=['POST'])
def create_resource():
    idempotency_key = request.json.get('idempotencyKey')
    data = request.json.get('data')

    if not idempotency_key:
        return jsonify({"error": "Idempotency key is required"}), 400

    # Check if the key already exists
    if idempotency_key in idempotency_store:
        return jsonify(idempotency_store[idempotency_key]), 200

    # Process the request
    resource_id = str(uuid.uuid4())
    response_data = {"id": resource_id, "data": data}

    # Store the key and response
    idempotency_store[idempotency_key] = response_data

    return jsonify(response_data), 201

if __name__ == '__main__':
    app.run(debug=True)

Enhancements for Production

  • Persistent Storage: Use a database like PostgreSQL or Redis for storing idempotency keys .
  • Concurrency Handling: Use locks or unique constraints to handle concurrent requests with the same key.
  • Scalability: Distribute idempotency key storage across multiple nodes using a shared cache or distributed database.
  • Security: Validate and sanitize the idempotency key to prevent injection attacks.

Discover more from Parottasalna

Subscribe now to keep reading and get access to the full archive.

Continue reading