LLD: Architecting a Real-Time Inventory Management System for E-Commerce
Low Level Design
System Design

LLD: Architecting a Real-Time Inventory Management System for E-Commerce

S

Shivam Chauhan

14 days ago

Ever wondered how Amazon, Flipkart, or your favourite online store keeps track of millions of items, ensuring they don't sell something they don't have? It's all about a robust, real-time inventory management system. Today, we're diving deep into the low-level design (LLD) of such a system.

Why Real-Time Inventory Matters

In the fast-paced world of e-commerce, accuracy and speed are key. Imagine this: a customer adds the last available item to their cart, but before they can checkout, someone else snatches it up. A frustrating out-of-stock message appears, leading to a lost sale and a potentially unhappy customer.

A real-time inventory system prevents this by:

  • Accurate Stock Levels: Displaying the most up-to-date quantity of each item.
  • Preventing Overselling: Ensuring you don't sell more than what's in stock.
  • Improving Customer Experience: Reducing frustration and building trust.
  • Optimising Operations: Enabling better forecasting and procurement decisions.

So, how do we build such a system? Let's get into the nitty-gritty.

Core Components

Our real-time inventory system will consist of these key components:

  • Inventory Database: Stores the current stock levels for each product. This could be a relational database (like PostgreSQL) or a NoSQL database (like Cassandra), depending on your scaling needs.
  • Inventory Service: Provides APIs for updating and querying inventory levels. This service acts as an intermediary between the application and the database.
  • Message Queue: A messaging system (like RabbitMQ or Amazon MQ) that facilitates asynchronous communication between different services. This is crucial for handling high volumes of updates without slowing down the application.
  • Cache: An in-memory data store (like Redis or Memcached) that stores frequently accessed inventory data for faster retrieval.

Here’s a basic diagram to illustrate the architecture:

Drag: Pan canvas

Detailed Design

Let's break down each component and its responsibilities.

1. Inventory Database

  • Schema: A products table with columns like product_id, name, description, and quantity.
sql
CREATE TABLE products (
    product_id UUID PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    quantity INTEGER NOT NULL
);
  • Considerations: Choose a database that supports ACID transactions to ensure data consistency. For high-volume scenarios, consider using sharding or partitioning to distribute the load.

2. Inventory Service

  • APIs:

    • GET /inventory/{product_id}: Retrieves the current quantity of a product.
    • POST /inventory/{product_id}/add: Adds quantity to a product's inventory.
    • POST /inventory/{product_id}/subtract: Subtracts quantity from a product's inventory.
  • Implementation: The service should handle authentication, authorisation, and input validation. It should also interact with the cache and the message queue.

java
@RestController
@RequestMapping("/inventory")
public class InventoryController {

    @Autowired
    private InventoryService inventoryService;

    @GetMapping("/{productId}")
    public ResponseEntity<Integer> getInventory(@PathVariable UUID productId) {
        int quantity = inventoryService.getInventory(productId);
        return ResponseEntity.ok(quantity);
    }

    @PostMapping("/{productId}/add")
    public ResponseEntity<Void> addInventory(@PathVariable UUID productId, @RequestParam int quantity) {
        inventoryService.addInventory(productId, quantity);
        return ResponseEntity.ok().build();
    }

    @PostMapping("/{productId}/subtract")
    public ResponseEntity<Void> subtractInventory(@PathVariable UUID productId, @RequestParam int quantity) {
        inventoryService.subtractInventory(productId, quantity);
        return ResponseEntity.ok().build();
    }
}

3. Message Queue (RabbitMQ)

  • Purpose: Decouples the inventory updates from the main application flow. When an order is placed or inventory is adjusted, a message is published to the queue.

  • Configuration: Configure the queue for high availability and durability to prevent message loss.

  • Example: Publishing a message when an order is placed:

java
@Service
public class OrderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private Queue inventoryUpdateQueue;

    public void placeOrder(Order order) {
        // ... other order processing logic ...

        // Publish message to update inventory
        rabbitTemplate.convertAndSend(inventoryUpdateQueue.getName(), new InventoryUpdateMessage(order.getProductId(), order.getQuantity()));
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class InventoryUpdateMessage {
    private UUID productId;
    private int quantity;
}

4. Cache (Redis)

  • Strategy: Use a read-through/write-through cache. When data is requested, the cache is checked first. If the data is not present (cache miss), it's retrieved from the database and stored in the cache.

  • Invalidation: Implement a mechanism to invalidate the cache when inventory is updated to ensure data consistency.

java
@Service
public class InventoryService {

    @Autowired
    private RedisTemplate<String, Integer> redisTemplate;

    @Autowired
    private ProductRepository productRepository;

    public int getInventory(UUID productId) {
        String key = "inventory:" + productId.toString();
        Integer quantity = redisTemplate.opsForValue().get(key);
        if (quantity == null) {
            // Cache miss, fetch from database
            Product product = productRepository.findById(productId)
                    .orElseThrow(() -> new IllegalArgumentException("Product not found"));
            quantity = product.getQuantity();
            redisTemplate.opsForValue().set(key, quantity);
        }
        return quantity;
    }

    public void addInventory(UUID productId, int quantity) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new IllegalArgumentException("Product not found"));
        product.setQuantity(product.getQuantity() + quantity);
        productRepository.save(product);

        // Invalidate cache
        String key = "inventory:" + productId.toString();
        redisTemplate.delete(key);
    }

    public void subtractInventory(UUID productId, int quantity) {
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new IllegalArgumentException("Product not found"));
        if (product.getQuantity() < quantity) {
            throw new IllegalArgumentException("Insufficient stock");
        }
        product.setQuantity(product.getQuantity() - quantity);
        productRepository.save(product);

        // Invalidate cache
        String key = "inventory:" + productId.toString();
        redisTemplate.delete(key);
    }
}

Ensuring Consistency

Data consistency is paramount. Here are some strategies:

  • ACID Transactions: Use database transactions to ensure that updates are atomic, consistent, isolated, and durable.
  • Optimistic Locking: Use a version number or timestamp to detect and prevent concurrent updates.
  • Idempotency: Design your update operations to be idempotent, meaning that they can be applied multiple times without changing the result.

Scaling the System

As your e-commerce platform grows, you'll need to scale your inventory system. Here are some techniques:

  • Horizontal Scaling: Add more instances of the Inventory Service and the database.
  • Sharding: Partition the database based on product categories or other criteria.
  • Load Balancing: Distribute traffic evenly across multiple instances of the Inventory Service.
  • Caching: Use a distributed cache like Redis Cluster to handle a large volume of read requests.

FAQs

1. What database should I use?

  • For smaller applications, a relational database like PostgreSQL is a good choice.
  • For high-volume, scalable applications, consider a NoSQL database like Cassandra.

2. How do I handle inventory updates during peak hours?

  • Use a message queue to decouple the updates from the main application flow.
  • Scale the Inventory Service and the database to handle the increased load.

3. How do I monitor the system?

  • Use monitoring tools like Prometheus and Grafana to track key metrics such as request latency, error rates, and database performance.

4. How does Coudo AI help in understanding these concepts?

  • Coudo AI offers various problems related to system design and low-level design, including scenarios that require real-time updates and consistency. Check out Coudo AI to practice and deepen your understanding.

Wrapping Up

Architecting a real-time inventory management system for e-commerce is a complex but crucial task. By understanding the core components, implementing proper consistency mechanisms, and designing for scalability, you can build a system that meets the demands of your growing business. Remember to practice your skills and explore more problems on platforms like Coudo AI to become a proficient system designer.

Keep it real, keep it fresh, and keep it engaging! And remember, the key to mastering LLD is continuous learning and practice.\n\n

About the Author

S

Shivam Chauhan

Sharing insights about system design and coding practices.