Concurrent Requests များကို ဘယ်လို ကိုင်တွယ်ဖြေရှင်းမလဲ?
Source: Dev.to
Race Condition အကြောင်းအရာ
System တစ်ခုကို User အများအပြားက တစ်ချိန်တည်း (Exact same time) မှာ အသုံးပြုတဲ့အခါ Data တွေ မှားယွင်းသွားတတ်တဲ့ Race Condition ပြဿနာကို ကြုံရလေ့ရှိပါတယ်။
ဥပမာ – E‑commerce မှာ တစ်ခုတည်း ကျန်တဲ့ ပစ္စည်းကို User နှစ်ယောက်က ပြိုင်တူဝယ်တဲ့အခါ၊ Online ticket များကို အများအပြားက တစ်ပြိုင်တည်းဝယ်တဲ့အခါ စသည့် အခြေအနေများမှာ Race Condition ဖြစ်ပြီး ပြသာနာတွေ ဖြစ်ပေါ်တတ်ပါတယ်။
ဒီပြသာနာတွေကို ဖြေရှင်းနိုင်တဲ့ နည်းလမ်းများက အများကြီးရှိပြီး၊ မည်သည့်နည်းလမ်းကို သုံးမလဲဆိုတာကတော့ project အပေါ် မူတည်ပါတယ်။
Database Level Locking
Pessimistic Locking
Data ကို ဖတ်ပြီး ပြင်ဆင်နေချိန် (Transaction အလုပ်လုပ်နေချိန်) မှာ အခြားသူများ ဝင်ပြင်မရအောင် Lock ချထားတဲ့ စနစ်ဖြစ်ပါတယ်။
-- SQL ဥပမာ
SELECT * FROM orders WHERE id = 123 FOR UPDATE;
Data အရည်အသွေး အရေးကြီးတဲ့နေရာများတွင် အထောက်အကူပြုသော်လည်း၊ User အများကြီးဝင်သုံးတဲ့အခါ Performance ကို ထိခိုက်စေနိုင်ပါတယ်။
Optimistic Locking
Lock မချထားဘဲ Table ရဲ့ Record တစ်ခုချင်းစီမှာ Version number (သို့) Timestamp ထည့်သွားပြီး၊ Update လုပ်ချိန်တွင် Version ကို စစ်ဆေးပါတယ်။ Version မကိုက် (တခြားသူက အရင်ပြင်ထား) ဆိုရင် Exception ပစ်ပြီး Retry လုပ်ပါသည်။
// C# ဥပမာ – EF Core Optimistic Concurrency
var order = context.Orders.Find(id);
order.RowVersion = fetchedRowVersion; // fetched from DB
order.Quantity += 1;
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
// Retry logic here
}
Redis Atomic Operations
Redis သည် single‑threaded architecture ဖြစ်သောကြောင့် INCR, DECR ကဲ့သို့သော Atomic Command များကို အသုံးပြု၍ Race Condition ကို ထိရောက်စွာ ကာကွယ်နိုင်ပါတယ်။
# Counter တိုးခြင်း (Atomic)
INCR ticket_counter
Redis ကို Counter, တစ်ခုတည်းကျန်တဲ့ Ticket အရေအတွက် လျှော့ခြင်း, Unique Barcode/ID ထုတ်ပေးခြင်း စသည့် အလုပ်များတွင် အသုံးပြုသင့်ပါတယ်။
Application Level Locking
C# Locking Mechanisms
Code အတွင်း Critical Section ကို တစ်ချိန်တည်း Thread တစ်ခုတည်းက ဝင်ရောက်နိုင်အောင် lock, Mutex, SemaphoreSlim စသည့် synchronization primitives များကို အသုံးပြုနိုင်ပါတယ်။
private static readonly object _lockObj = new object();
public void ProcessOrder()
{
lock (_lockObj)
{
// Critical section – only one thread at a time
UpdateInventory();
}
}
သတိပြုရန် – ဤနည်းလမ်းသည် Single Instance Server အတွက်သာ အကျိုးရှိပြီး၊ Server များစွာဖြင့် Load Balancing လုပ်ထားသော အခြေအနေတွင် အလုပ်မဖြစ်နိုင်ပါ။
Message Queue (Broker) အားဖြင့် ဖြေရှင်းခြင်း
User များထံမှ လာသော Request များကို တိုက်ရိုက် Process မလုပ်ဘဲ RabbitMQ သို့မဟုတ် Kafka ကဲ့သို့သော Message Broker (Queue) ထဲသို့ ထည့်ပြီး၊ Background Worker တစ်ခုက Queue မှ Message တစ်ခုချင်းစီကို အစဉ်လိုက် ဆွဲယူကာ Database အလုပ်လုပ်စေသည်။
// RabbitMQ consumer example
var factory = new ConnectionFactory() { HostName = "localhost" };
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
// Process message (e.g., update DB)
};
channel.BasicConsume(queue: "order_queue",
autoAck: true,
consumer: consumer);
Message Queue ကို အသုံးပြုခြင်းဖြင့် အလုပ်များကို asynchronous အနေဖြင့် ခွဲထုတ်နိုင်ပြီး၊ Database level locking မလိုအပ်သလို, အချိန်အခါအခါတွင် အလုပ်များကို စီမံနိုင်သည့် scalability ကိုလည်း ပေးစွမ်းပါသည်။
สรุป (Conclusion)
- Database Level – Pessimistic နှင့် Optimistic Locking ကို အခြေအနေအလိုက် ရွေးချယ်သုံးပါ။
- Redis – Atomic command များဖြင့် Counter, Ticket, Unique ID စသည့် အလုပ်များကို အလွယ်တကူ ကာကွယ်နိုင်သည်။
- Application Level –
lock,Mutex,SemaphoreSlimတို့ကို Single Instance အတွက်သာ အသုံးပြုပါ။ - Message Queue – RabbitMQ/Kafka စသည့် broker များဖြင့် request များကို queue ထဲသို့ ထည့်ပြီး background worker ဖြင့် asynchronous အလုပ်လုပ်ခြင်းဖြင့် Race Condition ကို အကောင်းဆုံး ဖြေရှင်းနိုင်သည်။