Skip to content

Instantly share code, notes, and snippets.

@yomexzo
Last active October 23, 2017 17:50
Show Gist options
  • Save yomexzo/c525df2e63d74f9c58ab68f856cc8747 to your computer and use it in GitHub Desktop.
Save yomexzo/c525df2e63d74f9c58ab68f856cc8747 to your computer and use it in GitHub Desktop.
Cart

Problem

Generally when checking out in an ecommerce environment, 3 things happen when selling products.

  1. You check if the quantity customer is buying is available
  2. You perform a series of operation like logging, sending emails and then deducting the quantity being sold from the available quantity.
  3. You write back the remaining quantity to the database.

When multiple requests occur while on is in the middle of (2), it is likely you would run into a concurrency issue where parallel requests think that there is enough quantity available to sell and then potentially selling things that are not in the inventory.

Approach

This problem can be solved at two different points depending on what you want as results and your context:

  1. Ensure customers add ONLY available items to cart
  2. Allow customers to add items to cart BUT check availability only at Checkout.

Either ways, concurrency issue may still exist. However, the architecture I present below can be used with any of the approaches above to address the concurrency issue. You just need to adapt it.

For illustration purposes, I present Approach 1.

Here, I made a couple of assumptions:

  1. The request to add to cart is received over an API and the client (browsers, mobile phones etc) making the request current receives response immediately. The concurrency problem happens when multiple requests are received and processed in parallel for the same product.

  2. The API that receives a request from clients to add to cart and the logic that actually adds to cart are currently coupled together.

  3. Presently, a NEW server process is spawned to handle each request.

Proposed solution:

  1. Separate the API into two. The part that receives a request from clients to add to cart remains an API while the logic that actually adds to cart is separated into a component. ReceiveRequestAPI and ProcessRequestComponent

  2. A messaging queue is placed between ReceiveRequestAPI and ProcessRequestComponent. When the ReceiveRequestAPI receives a request from the client, it generates a UUID and sends it the back to the client. It then pushes the request onto a queue. The ProcessRequestComponent then picks from the queue one after the other ensuring only one request is processed at a time.

  3. The ProcessRequestComponent exists in a LONG RUNNING server process where it continually checks if there is a new request on the queue to process.

  4. The ProcessRequestComponent picks a request, checks available quantity (can hit the cache first), and then inserts the request into the database. The insert would include whether the item is still available or not.

  5. A new API is then introduced - RequestStatusAPI. The client will poll this endpoint to check whether the item successfully added to cart. It would respond with either it processed successfully, it is pending (hasn't been processed) or product inventory is depleted. An alternative design to this is to use SocketIO for the ProcessRequestComponent to inform the client subscribed to events of the status of the processing. Whoever is implementing the client can choose however they want to design the user experience. They may continually poll the RequestStatusAPI before taking the customer to cart. Or they take the customer to cart and then update the customer later that the item is no more available if things did not go well. It is up to the UX owner.

Scaling issues

An organization fast growing and quickly acquiring customers and possibly products may fill up the Queue faster than it is being processed. To address this, I introduce the ability to have multiple queues and multiple job servers.

Since concurrency problem happens only per product, we would ensure a product can be assigned to only one queue at any point in time. A ProductQueueMapper is introduced. It converts a product id into one of the queue names/numbers in a deterministic way. A new job server (ProcessRequestComponent process) can then be added to handle the new queue.

Another possibility would be the need to kill or abandon some queues. This may not be much of a concern or need but I address it in my architecture. To do this,

  1. Each time ProductQueueMapper assigns a product to a queue, it would cache set("preresize:oldest:", uuid). Once it is informed of the queue change, it would stop setting that value.

  2. When a new ProcessRequestComponent comes online, it sniffs the next request to be handled and gets the UUID for the product before the resize using get(preresize:oldest:). If it comes up with empty, then it would continue processing the requests.

  3. Each time ProcessRequestComponent processes a requests, it checks get("preresize:oldest:") to see if it is processing the last inserted UUID before a resize. If it is, it would invalidate that cache so that parallel processes can move on / continue processing.

If we need to kill a job server (one of ProcessRequestComponent), without reducing queues, it is safe to assign it's queue to another job server already running.

NB: A Queue can not be handled by more than one LONG RUNNING process at a time. However, A LONG RUNNING process can handle more than one queues.

Technology recommendations

ProcessRequestComponent: Can be written in most server side programming languages. Can be deployed within a process manager or set up in services like Amazon API Gateway

ProductQueueMapper: A utility function that can be exposed as an API as well. Receives a product id and responds with a queue name / id

ReceiveRequestAPI: Part of existing system can remain with the process bit removed and logic extended to integrate with ProductQueueMapper and the messaging queue.

Message Queue: Products like Kafka or RabbitMQ can be used

Cache: Redis can be used

Databse: MySQL or any other preferred data storage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment