From Two-Phase Commit to Saga: Simplifying Distributed Transactions

From Two-Phase Commit to Saga: Simplifying Distributed Transactions

What is a Distributed Transaction?

A distributed transaction involves multiple operations spread across different systems, which all need to either complete successfully or fail as a unit. Think of it as coordinating a team spread across different cities to work seamlessly on a single project.

Two-Phase Commit Protocol

As the name suggests, this protocol runs distributed transactions in two phases

  1. Prepare Phase: Every part of the transaction prepares to commit and promises to do so unless told otherwise.

2. Commit Phase: If all systems are ready, the coordinator asks all nodes to commit simultaneously. If any system cannot commit or returns negative, the transaction is rolled back across all systems.

Two-Phase Commit Protocol

Challenges with Two-Phase Commit

Performance Bottlenecks: The 2PC protocol locks resources during the commit process, waiting for all involved nodes to agree before proceeding. This locking can lead to significant delays.

Fault Tolerance Issues: If any participant in the transaction fails during the second phase (the commit phase), the entire transaction must be rolled back. This makes 2PC highly vulnerable to system failures, leading to potential data inconsistencies if not managed correctly.

Why Move to Saga?

Independent Operations: Unlike 2PC, Saga allows each transaction step to be handled independently, with each service managing its own local transactions. This significantly reduces the need for global locks and improves system performance.

Compensation Logic: Saga introduces a method to handle failures through compensating transactions. If a step fails, the Saga orchestrates a series of compensatory actions to undo the impact of prior transactions, maintaining data integrity without the need for locks.

Flexibility and Resilience: By decoupling transaction steps, Saga enhances system resilience and adaptability. Each service can recover from failures autonomously, making the system more robust against individual service downtimes.

Let's write some code now.

We'll explore an example of an order processing system designed with the Saga pattern. Here's a glimpse of the orchestration class setup:

public class OrderProcessingSagaOrchestrator
    {
        private readonly OrderProcessingSaga _saga;
        private readonly InventoryService _inventoryService;
        private readonly PaymentService _paymentService;

        public OrderProcessingSagaOrchestrator()
        {
            _saga = new OrderProcessingSaga();
            _inventoryService = new InventoryService();
            _paymentService = new PaymentService();
        }

        public async Task<SagaStepResult> ProcessOrder(Order order)
        {
            try
            {
                // Step 1: Initiate Order
                var initiateOrderResult = _saga.InitiateOrderStep(order);
                if (!initiateOrderResult.IsSuccess)
                {
                    return initiateOrderResult;
                }

                // Step 2: Reserve Inventory
                var reserveInventoryResult = await _inventoryService.ReserveInventoryStepAsync(order);
                if (!reserveInventoryResult.IsSuccess)
                {
                    // Step 2 failed, revert actions for previous steps
                    await _saga.RevertInitiateOrderStepAsync(order);
                    return reserveInventoryResult;
                }

                // Step 3: Process Payment
                var processPaymentResult = await _paymentService.ProcessPaymentStepAsync(order);
                if (!processPaymentResult.IsSuccess)
                {
                    // Step 3 failed, revert actions for previous steps
                    await _inventoryService.RevertReserveInventoryStepAsync(order);
                    await _saga.RevertInitiateOrderStepAsync(order);
                }

                return processPaymentResult;

            }
            catch (Exception ex)
            {
                return new SagaStepResult { IsSuccess = false, ErrorMessage = ex.Message };
            }
        }
    }        

Here's my order class and SagaStepResult

public class Order
    {
        public int OrderId { get; set; }
        public int ProductId { get; set; }
        public int Quantity { get; set; }
        public string Status { get; set; }
    }

    public class SagaStepResult
    {
        public bool IsSuccess { get; set; }
        public string ErrorMessage { get; set; }
    }        

Order processing Saga :

    public class OrderProcessingSaga
    {
        public SagaStepResult InitiateOrderStep(Order order)
        {
            // First step of the saga
            Console.WriteLine($"Order initiated for OrderId: {order.OrderId}");
            // Update the order status to "Created"
            order.Status = "Created";
            return new SagaStepResult { IsSuccess = true };
        }

        // Compensating action for Initial step step
        public async Task RevertInitiateOrderStepAsync(Order order)
        {
            // Release the Order
            await Task.Delay(1000);
            Console.WriteLine($"Order initiated for OrderId: {order.OrderId} needs to be released");
        }
    }        

Inventory Service :

    public class InventoryService
    {
        public async Task<SagaStepResult> ReserveInventoryStepAsync(Order order)
        {
            try
            {
                // Reservation is successful
                await Task.Delay(1000);
                Console.WriteLine($"Inventory reserved for OrderId: {order.OrderId}");
                // Update the order status to "InventoryReserved"
                order.Status = "InventoryReserved";
                return new SagaStepResult { IsSuccess = true };
            }
            catch (Exception ex)
            {
                return new SagaStepResult { IsSuccess = false, ErrorMessage = ex.Message };
            }
        }

        public async Task RevertReserveInventoryStepAsync(Order order)
        {
            // Release the reserved inventory
            await Task.Delay(1000);
            Console.WriteLine($"Inventory reservation for OrderId: {order.OrderId} needs to be released");
        }
    }        

Payment Service:

    public class PaymentService
    {
        public async Task<SagaStepResult> ProcessPaymentStepAsync(Order order)
        {
            try
            {
                // Simulate a payment failure
                await Task.Delay(1000);
                Console.WriteLine($"Processing payment for OrderId: {order.OrderId}");
                // Update the order status to "PaymentProcessed"
                order.Status = "PaymentProcessed";
                return new SagaStepResult { IsSuccess = false, ErrorMessage = "Payment failed" };
            }
            catch (Exception ex)
            {
                return new SagaStepResult { IsSuccess = false, ErrorMessage = ex.Message };
            }
        }

        public async Task RevertProcessPaymentStepAsync(Order order)
        {
            // Refund the payment
            await Task.Delay(1000);
            Console.WriteLine($"Payment for OrderId: {order.OrderId} needs to be refunded");
        }
    }        

PostProcessing Step:

public class PostProcessingService
    {
        public async Task<SagaStepResult> ExecutePostProcessingAsync(Order order)
        {
            try
            {
                // Update relevant databases or data stores
                // Send notifications via email or messaging services
                // Log the transaction details
                // Monitor for any anomalies or performance issues
                Console.WriteLine($"Post-processing activities for OrderId: {order.OrderId} completed successfully.");
                return new SagaStepResult { IsSuccess = true };
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error during post-processing: " + ex.Message);
                return new SagaStepResult { IsSuccess = false, ErrorMessage = ex.Message };
            }
        }
    }        

Final Thoughts on the Saga Design Pattern:

As our technology gets more complicated and spread out, managing everything efficiently becomes really important. The Saga design pattern is a great way to handle complex tasks that involve many steps and different systems. It lets each part of the task take care of itself, which means if something goes wrong, it’s easier to fix without affecting everything else.







要查看或添加评论,请登录

Saurabh Singh的更多文章

  • Enhancing Resilience in Applications-Circuit Breaker

    Enhancing Resilience in Applications-Circuit Breaker

    In my previous article, we delved into the world of handling transient failures using the Retry pattern. Today, we're…

  • Enhancing Resilience in Applications

    Enhancing Resilience in Applications

    Today, we're delving into two game-changing concepts that can enhance the reliability of the applications: the Retry…

社区洞察

其他会员也浏览了