تسکهای پسزمینه دورهای (Periodic) ⏰
گاهی اوقات میخواهیم یک تسک پسزمینه را به طور مداوم اجرا کنیم و آن را برای انجام عملیاتی به صورت تکراری واداریم. برای مثال، میخواهیم هر ده ثانیه پیامها را از یک صف مصرف کنیم. چگونه این را بسازیم؟
در اینجا یک مثال از PeriodicBackgroundTask برای شروع آمده است:
public class PeriodicBackgroundTask : BackgroundService
{
private readonly TimeSpan _period = TimeSpan.FromSeconds(5);
private readonly ILogger<PeriodicBackgroundTask> _logger;
public PeriodicBackgroundTask(ILogger<PeriodicBackgroundTask> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using PeriodicTimer timer = new PeriodicTimer(_period);
while (!stoppingToken.IsCancellationRequested &&
await timer.WaitForNextTickAsync(stoppingToken))
{
_logger.LogInformation("Executing PeriodicBackgroundTask");
}
}
}
ما از یک PeriodicTimer ⏳ برای انتظار غیرهمزمان برای یک دوره زمانی معین، قبل از اجرای تسک پسزمینه خود استفاده میکنیم.
اگر به راهحل قویتری نیاز داشتید چه؟ 🚀
تا الان باید واضح باشد که IHostedService زمانی مفید است که به تسکهای پسزمینه سادهای نیاز دارید که تا زمانی که اپلیکیشن شما در حال اجراست، فعال هستند.
چه میشود اگر بخواهید یک تسک پسزمینه زمانبندیشده داشته باشید که هر روز ساعت ۲ بامداد اجرا شود؟
شما احتمالاً میتوانید چیزی شبیه این را خودتان بسازید، اما راهحلهای موجودی وجود دارد که باید ابتدا آنها را در نظر بگیرید.
در اینجا دو راهحل محبوب برای اجرای تسکهای پسزمینه آمده است:
Quartz 🔥
Hangfire 🔥
📌ادامه دارد...
🔖 هشتگها:
#CSharp #DotNet #BackgroundTask #HostedService #WorkerService #SystemDesign #TaskScheduling
پیکربندی Job ⚙️
ما قبلاً ProcessOutboxMessagesJob را پیادهسازی کردیم.
کتابخانه Quartz.NET مسئولیت scheduler را بر عهده خواهد گرفت.
و این ما را با پیکربندی trigger برای ProcessOutboxMessagesJob تنها میگذارد.
services.AddQuartz(configure =>
{
var jobKey = new JobKey(nameof(ProcessOutboxMessagesJob));
configure
.AddJob<ProcessOutboxMessagesJob>(jobKey)
.AddTrigger(
trigger => trigger.ForJob(jobKey).WithSimpleSchedule(
schedule => schedule.WithIntervalInSeconds(10).RepeatForever()));
configure.UseMicrosoftDependencyInjectionJobFactory();
});
ما باید background job خود را با یک JobKey به طور منحصر به فرد شناسایی کنیم.
فراخوانی AddJob، ProcessOutboxMessagesJob را با DI و همچنین با Quartz ثبت میکند.
پس از آن ما یک تریگر برای این job با فراخوانی AddTrigger پیکربندی میکنیم. در این مثال، من job را طوری زمانبندی میکنم که هر ده ثانیه اجرا شود و تا زمانی که سرویس میزبانی شده در حال اجراست، برای همیشه تکرار شود.
Quartz
همچنین از پیکربندی تریگرها با استفاده از عبارات cron پشتیبانی میکند.
نکات پایانی 💡
Quartz.NET
اجرای background jobها در NET. را آسان میکند و شما میتوانید از تمام قدرت DI در background jobهای خود استفاده کنید. همچنین برای نیازمندیهای مختلف زمانبندی با پیکربندی از طریق کد یا استفاده از عبارات cron انعطافپذیر است.
چند مورد برای بهبود وجود دارد تا زمانبندی jobها آسانتر و boilerplate کمتر شود:
🔹 افزودن یک متد توسعه برای سادهسازی پیکربندی jobها با زمانبندی ساده.
🔹 افزودن یک متد توسعه برای سادهسازی پیکربندی jobها با عبارات cron از تنظیمات اپلیکیشن.
🔖 هشتگها:
#CSharp #DotNet #QuartzNet #BackgroundJobs #TaskScheduling #HostedService #SystemDesign
زمانبندی Jobهای تکرارشونده 🔄
برای jobهای پسزمینه تکرارشونده، میتوانید از زمانبندی cron استفاده کنید:
app.MapPost("/api/reminders/schedule/recurring", async (
ISchedulerFactory schedulerFactory,
RecurringReminderRequest request) =>
{
// ... (Job creation is the same) ...
var trigger = TriggerBuilder.Create()
.WithIdentity($"recurring-trigger-{Guid.NewGuid()}", "recurring-reminders")
.WithCronSchedule(request.CronExpression)
.Build();
await scheduler.ScheduleJob(job, trigger);
return Results.Ok();
});تریگرهای Cron قدرتمندتر از تریگرهای ساده هستند. آنها به شما اجازه میدهند زمانبندیهای پیچیدهای مانند "هر روز کاری ساعت ۱۰ صبح" یا "هر ۱۵ دقیقه" را تعریف کنید.
راهاندازی پایداری Job (Job Persistence) 💾
بهطور پیشفرض، Quartz از ذخیرهسازی درون-حافظهای استفاده میکند، که یعنی jobهای شما با ریاستارت شدن اپلیکیشن از بین میروند. برای محیطهای پروداکشن، شما به یک فروشگاه پایدار (persistent store) نیاز دارید.
بیایید ببینیم چگونه ذخیرهسازی پایدار را با ایزولهسازی اسکیمای مناسب راهاندازی کنیم:
builder.Services.AddQuartz(options =>
{
options.AddJob<EmailReminderJob>(c => c
.StoreDurably()
.WithIdentity(EmailReminderJob.Name));
options.UsePersistentStore(persistenceOptions =>
{
persistenceOptions.UsePostgres(cfg =>
{
cfg.ConnectionString = connectionString;
cfg.TablePrefix = "scheduler.qrtz_";
});
persistenceOptions.UseNewtonsoftJsonSerializer();
});
});
تنظیم TablePrefix به سازماندهی جداول Quartz در دیتابیس شما کمک میکند - در این مورد، آنها را در یک اسکیمای اختصاصی scheduler قرار میدهد.
جاب های بادوام (Durable Jobs) 📌
توجه کنید که ما EmailReminderJob را با StoreDurably پیکربندی میکنیم. این یک الگوی قدرتمند است که به شما اجازه میدهد jobهای خود را یک بار تعریف کرده و با تریگرهای مختلف از آنها استفاده مجدد کنید.
public async Task ScheduleReminder(string userId, string message, DateTime scheduledTime)
{
var scheduler = await _schedulerFactory.GetScheduler();
// Reference the stored job by its identity
var jobKey = new JobKey(EmailReminderJob.Name);
var trigger = TriggerBuilder.Create()
.ForJob(jobKey) // Reference the durable job
.WithIdentity($"trigger-{Guid.NewGuid()}")
.UsingJobData("userId", userId)
.UsingJobData("message", message)
.StartAt(scheduledTime)
.Build();
await scheduler.ScheduleJob(trigger); // Note: just passing the trigger
}
خلاصه ✅
راهاندازی صحیح Quartz در NET. شامل موارد بیشتری از صرفاً افزودن پکیج NuGet است.
به این موارد توجه کنید:
🔹 تعریف صحیح job و مدیریت داده با JobDataMap
🔹 راهاندازی زمانبندی jobهای یکباره و تکرارشونده
🔹 پیکربندی ذخیرهسازی پایدار با ایزولهسازی اسکیمای مناسب
🔹 استفاده از jobهای بادوام برای حفظ تعاریف ثابت job
هر یک از این عناصر به یک سیستم پردازش پسزمینه قابل اعتماد کمک میکند که میتواند با نیازهای اپلیکیشن شما رشد کند.
🔖 هشتگها:
#CSharp #DotNet #ASPNETCore #QuartzNet #BackgroundJobs #TaskScheduling #Observability #SystemDesign
SQL تولید شده:
💡توجه کنید که هیچ OFFSET در کوئری وجود ندارد. ما مستقیماً بر اساس cursor به دنبال ردیفها میگردیم که کارآمدتر است.
• اگر کاربران نیاز به تغییر داینامیک فیلدهای مرتبسازی داشته باشند، پیادهسازی به شدت پیچیده میشود.
• کاربران نمیتوانند به یک شماره صفحه خاص بپرند.
• پیادهسازی صحیح آن پیچیدهتر است.
من پلنهای اجرا را برای هر دو مقایسه کردم. برای صفحهای در عمق دیتابیس (آفست ۹۰۰,۰۰۰):
زمان اجرای Offset Pagination: 704.217 ms 🐢
زمان اجرای Cursor Pagination: 40.993 ms 🚀
یک بهبود عملکرد ۱۷ برابری با cursor pagination!
من همچنین تأثیر ایندکسها را روی cursor pagination تست کردم. یک ایندکس ترکیبی روی فیلدهای Date و Id ایجاد کردم.
نتیجه اولیه کندتر بود! اما با استفاده از مقایسه تاپل (tuple comparison) در SQL:
زمان اجرا به 0.668 ms کاهش یافت! ⚡️
برای ترجمه این به EF Core، میتوانید از EF.Functions.LessThanOrEqual که یک ValueTuple را به عنوان آرگومان میپذیرد، استفاده کنید.
با اینکه offset pagination سادهتر است، در مقیاس بالا دچار افت عملکرد شدید میشود. Cursor pagination عملکرد ثابتی را حفظ میکند و برای فیدهای real-time و infinite scroll عالی است.
🔹 برای APIهای حساس به عملکرد، فیدهای real-time یا infinite scroll از ⟵ Cursor pagination
🔹 برای اینترفیسهای ادمین، مجموعه دادههای کوچک، یا زمانی که به تعداد کل صفحات نیاز دارید ⟵ Offset pagination.
SELECT u.id, u.date, u.note, u.user_id
FROM user_notes AS u
WHERE u.date < @date OR (u.date = @date AND u.id <= @lastId)
ORDER BY u.date DESC, u.id DESC
LIMIT @limit;
💡توجه کنید که هیچ OFFSET در کوئری وجود ندارد. ما مستقیماً بر اساس cursor به دنبال ردیفها میگردیم که کارآمدتر است.
محدودیتهای Cursor Pagination: ❌
• اگر کاربران نیاز به تغییر داینامیک فیلدهای مرتبسازی داشته باشند، پیادهسازی به شدت پیچیده میشود.
• کاربران نمیتوانند به یک شماره صفحه خاص بپرند.
• پیادهسازی صحیح آن پیچیدهتر است.
بررسی پلنهای اجرای SQL 📊
من پلنهای اجرا را برای هر دو مقایسه کردم. برای صفحهای در عمق دیتابیس (آفست ۹۰۰,۰۰۰):
زمان اجرای Offset Pagination: 704.217 ms 🐢
زمان اجرای Cursor Pagination: 40.993 ms 🚀
یک بهبود عملکرد ۱۷ برابری با cursor pagination!
افزودن ایندکس برای Cursor Pagination 🔑
من همچنین تأثیر ایندکسها را روی cursor pagination تست کردم. یک ایندکس ترکیبی روی فیلدهای Date و Id ایجاد کردم.
نتیجه اولیه کندتر بود! اما با استفاده از مقایسه تاپل (tuple comparison) در SQL:
WHERE (u.date, u.id) <= (@date, @lastId)
زمان اجرا به 0.668 ms کاهش یافت! ⚡️
برای ترجمه این به EF Core، میتوانید از EF.Functions.LessThanOrEqual که یک ValueTuple را به عنوان آرگومان میپذیرد، استفاده کنید.
خلاصه 📝
با اینکه offset pagination سادهتر است، در مقیاس بالا دچار افت عملکرد شدید میشود. Cursor pagination عملکرد ثابتی را حفظ میکند و برای فیدهای real-time و infinite scroll عالی است.
🤔چه زمانی از کدام استفاده کنیم؟
🔹 برای APIهای حساس به عملکرد، فیدهای real-time یا infinite scroll از ⟵ Cursor pagination
🔹 برای اینترفیسهای ادمین، مجموعه دادههای کوچک، یا زمانی که به تعداد کل صفحات نیاز دارید ⟵ Offset pagination.
🔖 هشتگها:
#CSharp #DotNet #Pagination #Performance #SystemDesign
پردازش Outbox ⚙️
پردازشگر Outbox کامپوننت بعدی است که به آن نیاز داریم. این میتواند یک فرآیند فیزیکی جداگانه یا یک background worker در همان فرآیند باشد.
من از Quartz برای زمانبندی background jobها برای پردازش Outbox استفاده خواهم کرد.
[DisallowConcurrentExecution]
public class OutboxProcessorJob(...) : IJob
{
public async Task Execute(IJobExecutionContext context)
{
// پیامهای پردازش نشده را از دیتابیس واکشی میکند
var messages = await connection.QueryAsync<OutboxMessage>(...);
foreach (var message in messages)
{
try
{
// پیام را deserialize کرده و به message broker منتشر میکند
await publishEndpoint.Publish(deserializedMessage);
// پیام را به عنوان پردازش شده علامتگذاری میکند
await connection.ExecuteAsync(
"UPDATE outbox_messages SET processed_on_utc = @ProcessedOnUtc WHERE id = @Id",
...);
}
catch (Exception ex)
{
// خطا را لاگ میکند
}
}
await transaction.CommitAsync();
}
}
یک راه جایگزین برای پردازش پیامهای Outbox، استفاده از Transaction log tailing است. ما میتوانیم این را با استفاده از Postgres logical replication پیادهسازی کنیم.
ملاحظات و مزایا و معایب 🧐
الگوی Outbox، ضمن کارآمدی، پیچیدگی و نوشتنهای اضافی در دیتابیس را به همراه دارد.
🔹 توصیه میکنم مکانیزمهای تلاش مجدد (retry) را در پردازشگر Outbox برای بهبود قابلیت اطمینان پیادهسازی کنید.
🔹 ضروری است که مصرفکنندگان پیام idempotent را پیادهسازی کنید.
🔹 با گذشت زمان، جدول Outbox میتواند به طور قابل توجهی رشد کند. مهم است که یک استراتژی آرشیو از همان ابتدا پیادهسازی کنید.
مقیاسپذیری پردازش Outbox 🚀
با رشد سیستم شما، ممکن است یک پردازشگر Outbox نتواند حجم پیامها را مدیریت کند.
🔹 یک رویکرد ساده، افزایش فرکانس اجرای job پردازشگر Outbox است.
🔹 یک استراتژی مؤثر دیگر، افزایش اندازه بچ (batch size) هنگام واکشی پیامهای پردازش نشده است.
🔹 برای سیستمهای با حجم بالا، پردازش Outbox به صورت موازی میتواند بسیار مؤثر باشد. یک مکانیزم قفلگذاری برای ادعای بچهای پیام پیادهسازی کنید.
جمعبندی ✅
الگوی Outbox یک ابزار قدرتمند برای حفظ سازگاری داده در سیستمهای توزیعشده است. با جداسازی عملیات دیتابیس از انتشار پیام، الگوی Outbox تضمین میکند که سیستم شما حتی در مواجهه با شکستها، قابل اعتماد باقی بماند.
به یاد داشته باشید که مصرفکنندگان خود را idempotent نگه دارید، استراتژیهای مقیاسپذیری مناسب را پیادهسازی کنید، و رشد جدول Outbox خود را مدیریت کنید.
🔖 هشتگها:
#SoftwareArchitecture #SystemDesign #Microservices #DistributedSystems #OutboxPattern #EventDrivenArchitecture #DataConsistency
📌مزایا و معایب
✅️مزایا:
🔹️ امکان ارسال ناگهانی چند درخواست (burst) را فراهم میکند، ولی میانگین نرخ را محدود نگه میدارد.
🔹️ باعث شکلدهی روانتر به ترافیک میشود (smooth traffic shaping).
🔹️ بهطور گسترده در شبکهها و APIها استفاده میشود.
❌️معایب:
🔹️کمی پیچیدهتر از روش Fixed Window Counter است.
🔹️در سیستمهای توزیعشده، نیاز به همگامسازی دقیق دارد.
مثال کد :
using System;
public class TokenBucket
{
private readonly int _capacity; // حداکثر تعداد توکنها
private readonly double _refillRate; // نرخ پر شدن (توکن در هر ثانیه)
private double _tokens; // تعداد توکنهای فعلی
private DateTime _lastRefill; // آخرین زمان پر شدن
public TokenBucket(int capacity, double refillRate)
{
_capacity = capacity;
_refillRate = refillRate;
_tokens = capacity;
_lastRefill = DateTime.UtcNow;
}
private void Refill()
{
var now = DateTime.UtcNow;
var elapsedSeconds = (now - _lastRefill).TotalSeconds;
var addedTokens = elapsedSeconds * _refillRate;
if (addedTokens >= 1)
{
_tokens = Math.Min(_capacity, _tokens + addedTokens);
_lastRefill = now;
}
}
public bool AllowRequest()
{
Refill();
if (_tokens >= 1)
{
_tokens -= 1;
return true;
}
return false;
}
}
public class Program
{
public static void Main()
{
var bucket = new TokenBucket(10, 1); // 10 توکن، پر شدن 1 توکن در هر ثانیه
// شبیهسازی درخواستها در بازههای 200 میلیثانیه
var timer = new System.Timers.Timer(200);
timer.Elapsed += (s, e) =>
{
Console.WriteLine($"Request allowed? {bucket.AllowRequest()}");
};
timer.Start();
Console.WriteLine("Press any key to stop...");
Console.ReadKey();
}
}
🚀 کاربردهای واقعی Token Bucket Algorithm
🔸 دروازههای API مثل AWS API Gateway، Nginx، Envoy از نسخههای مختلف این الگوریتم برای کنترل نرخ درخواستها استفاده میکنند.
🔸 روترهای شبکه (Network Routers) برای Traffic Shaping یا همون تنظیم و کنترل جریان ترافیک به کار میره تا از شلوغی شبکه جلوگیری بشه.
🔸 صفهای پیام (Message Queues) برای جلوگیری از بار بیشازحد روی مصرفکنندهها (Consumers) استفاده میشه.
⚖️ مقایسه با سایر روشها
🪟 Fixed Window Counter →
سادهتره، ولی در مرز بازهها ممکنه رفتار غیرمنصفانه نشون بده و اجازهی Burst زیاد بده.
💧 Leaky Bucket →
نرخ خروج داده رو ثابت نگه میداره، اما انعطافپذیری کمتری برای Burst داره.
🎯 Token Bucket →
ترکیبیه از نرخ ثابت و پشتیبانی از Burst کوتاهمدت — یعنی بهترین تعادل برای کنترل ترافیک در APIها.
🏁 نتیجهگیری
🔹 الگوریتم Token Bucket یکی از کاربردیترین و مؤثرترین روشها برای پیادهسازی Rate Limiting در سیستمهاست.
🔹 پیادهسازی اون سادهه، از ترافیک ناگهانی پشتیبانی میکنه و استفادهی منصفانه از منابع رو تضمین میکنه.
🔹 اگر در حال ساخت API یا سیستم توزیعشده هستی،
حتماً این الگوریتم باید یکی از گزینههای اصلی تو باشه.
🔖هشتگها:
#TokenBucket #RateLimiting #SystemDesign #API #Networking
🚀 طراحی و پیادهسازی URL Shortener در NET. — بخش نهایی
در این بخش، بعد از آمادهسازی منطق اصلی سیستم، میخواهیم دو Endpoint اصلی را پیادهسازی کنیم:
1️⃣ کوتاهسازی لینک (URL Shortening)
2️⃣ ریدایرکت کاربر به لینک اصلی (URL Redirection)
🔗 کوتاهسازی لینک (URL Shortening)
در ابتدا، با استفاده از یک Minimal API ساده، یک Endpoint برای کوتاهسازی URL ایجاد میکنیم.
این Endpoint یک URL را از کاربر میگیرد، آن را اعتبارسنجی میکند، سپس با کمک سرویس UrlShorteningService، یک کد منحصربهفرد تولید کرده و لینک کوتاهشده را در دیتابیس ذخیره میکند. در نهایت، لینک کوتاهشده به کاربر بازگردانده میشود.
public record ShortenUrlRequest(string Url);
app.MapPost("shorten", async (
ShortenUrlRequest request,
UrlShorteningService urlShorteningService,
ApplicationDbContext dbContext,
HttpContext httpContext) =>
{
if (!Uri.TryCreate(request.Url, UriKind.Absolute, out _))
{
return Results.BadRequest("The specified URL is invalid.");
}
var code = await urlShorteningService.GenerateUniqueCode();
var httpRequest = httpContext.Request;
var shortenedUrl = new ShortenedUrl
{
Id = Guid.NewGuid(),
LongUrl = request.Url,
Code = code,
ShortUrl = $"{httpRequest.Scheme}://{httpRequest.Host}/{code}",
CreatedOnUtc = DateTime.UtcNow
};
dbContext.ShortenedUrls.Add(shortenedUrl);
await dbContext.SaveChangesAsync();
return Results.Ok(shortenedUrl.ShortUrl);
});
📌 نکته:
در اینجا احتمال اندکی از Race Condition وجود دارد — چون ابتدا کد تولید میشود و بعد در دیتابیس ذخیره میگردد. ممکن است دو درخواست همزمان، یک کد مشابه تولید کنند.
اما از آنجا که در دیتابیس Unique Index تعریف کردهایم، جلوی درج مقادیر تکراری گرفته میشود.
🔁 ریدایرکت به URL اصلی (URL Redirection)
در سناریوی دوم، وقتی کاربر روی لینک کوتاه کلیک میکند، سیستم باید او را به لینک اصلی هدایت کند.
برای این کار یک Endpoint دیگر ایجاد میکنیم که کد کوتاهشده را از مسیر (route parameter) میگیرد و در دیتابیس جستوجو میکند.
اگر لینک پیدا شد، کاربر به آدرس اصلی Redirect میشود.
app.MapGet("{code}", async (string code, ApplicationDbContext dbContext) =>
{
var shortenedUrl = await dbContext
.ShortenedUrls
.SingleOrDefaultAsync(s => s.Code == code);
if (shortenedUrl is null)
{
return Results.NotFound();
}
return Results.Redirect(shortenedUrl.LongUrl);
});📨 در صورت موفقیت، پاسخ HTTP با کد وضعیت 302 (Found) بازگردانده میشود که نشاندهندهٔ ریدایرکت موقت است.
⚡️ نقاط قابل بهبود در سیستم کوتاهکننده لینک
اگرچه پیادهسازی فعلی کاملاً قابلاستفاده است، اما میتوان با چند بهبود ساده آن را مقیاسپذیرتر و حرفهایتر کرد:
1️⃣ Caching 🧠
استفاده از Redis برای کش کردن لینکها و کاهش بار دیتابیس.
2️⃣ Horizontal Scaling ⚙️
طراحی سیستم برای مقیاسپذیری افقی و مدیریت ترافیک بالا.
3️⃣ Data Sharding 🧩
تقسیم دادهها بین چند دیتابیس برای بهبود کارایی و توزیع بار.
4️⃣ Analytics 📊
افزودن تحلیلها برای رصد تعداد کلیکها، موقعیت کاربران و نرخ استفاده.
5️⃣ User Accounts 👤
امکان ایجاد حساب کاربری برای مدیریت لینکهای کوتاهشده توسط هر کاربر.
✅ حالا شما یک سیستم کامل URL Shortener با NET. دارید!
میتوانید این پروژه را گسترش دهید و با افزودن قابلیتهای بالا، آن را به یک راهکار مقیاسپذیر و قدرتمند در سطح تولید (Production) تبدیل کنید.
🔖هشتگها:
#URLShortener #SystemDesign
Forwarded from Sonora.Dev
🛠 حل مشکل Double Booking در سیستمهای رزرو
تمام پلتفرمهای رزرو مدرن با چالش Double Booking روبرو هستند: وقتی دو یا چند کاربر بهطور همزمان تلاش میکنند یک منبع محدود را رزرو کنند.
این مشکل، یک race condition است که میتواند اعتماد کاربر را نابود کند و برای سیستمهای پرترافیک، بحرانی است.
1️⃣ Pessimistic Locking
مکانیزم: قفل روی رکورد دیتابیس (SELECT ... FOR UPDATE)
مزایا: تضمین Consistency، جلوگیری از race condition
معایب: Throughput محدود، Deadlock Risk، مقیاسپذیری پایین
مناسب برای: Low-traffic / کمرقابت (مثل Web Check-in هواپیما)
2️⃣ Optimistic Locking
مکانیزم: بدون قفل، با استفاده از Versioning
مزایا: عملکرد خواندن بالا، افزایش concurrency
معایب: Conflict و Retry در High Contention، افزایش load روی DB
مناسب برای: Moderate traffic و منابع کمرقابت (رزرو هتل، رستوران)
3️⃣ In-Memory Distributed Locking
مکانیزم: Lock توزیعشده در Redis / In-Memory Cache
مزایا: کاهش فشار روی دیتابیس، High Concurrency، Low Latency
معایب: پیچیدگی زیرساخت، مدیریت crash و expiration، ریسک Lock ناتمام
مناسب برای: Popular events با 1K–10K RPS
4️⃣ Virtual Waiting Queue
مکانیزم: Async Queue + Backpressure + FIFO
مزایا:
محافظت از دیتابیس و cache در برابر surge
بهبود تجربه کاربری و fairness
مقیاسپذیری بسیار بالا (High Throughput)
معایب: پیچیدگی عملیاتی، نیاز به SSE یا WebSocket برای اطلاعرسانی
مناسب برای: Ultra High Traffic events (کنسرتها، فیلمهای بلاکباستر)
✅ جمعبندی فنی
هیچ راهحل واحدی برای همه سناریوها وجود ندارد
انتخاب معماری به الگوی ترافیک، سطح رقابت و محدودیت منابع وابسته است
سیستمهای High-Traffic باید Lock-free + Async + Fair Queue داشته باشند تا Tail Latency و double booking کنترل شود
Monitoring، Retry Policies و Backpressure، اجزای کلیدی در طراحی سیستم رزرو مقیاسپذیر هستند
#SystemDesign #DistributedSystems #Scalability #Concurrency #BackendArchitecture #HighTraffic #BookingSystems #Microservices #Queueing
تمام پلتفرمهای رزرو مدرن با چالش Double Booking روبرو هستند: وقتی دو یا چند کاربر بهطور همزمان تلاش میکنند یک منبع محدود را رزرو کنند.
این مشکل، یک race condition است که میتواند اعتماد کاربر را نابود کند و برای سیستمهای پرترافیک، بحرانی است.
1️⃣ Pessimistic Locking
مکانیزم: قفل روی رکورد دیتابیس (SELECT ... FOR UPDATE)
مزایا: تضمین Consistency، جلوگیری از race condition
معایب: Throughput محدود، Deadlock Risk، مقیاسپذیری پایین
مناسب برای: Low-traffic / کمرقابت (مثل Web Check-in هواپیما)
2️⃣ Optimistic Locking
مکانیزم: بدون قفل، با استفاده از Versioning
مزایا: عملکرد خواندن بالا، افزایش concurrency
معایب: Conflict و Retry در High Contention، افزایش load روی DB
مناسب برای: Moderate traffic و منابع کمرقابت (رزرو هتل، رستوران)
3️⃣ In-Memory Distributed Locking
مکانیزم: Lock توزیعشده در Redis / In-Memory Cache
مزایا: کاهش فشار روی دیتابیس، High Concurrency، Low Latency
معایب: پیچیدگی زیرساخت، مدیریت crash و expiration، ریسک Lock ناتمام
مناسب برای: Popular events با 1K–10K RPS
4️⃣ Virtual Waiting Queue
مکانیزم: Async Queue + Backpressure + FIFO
مزایا:
محافظت از دیتابیس و cache در برابر surge
بهبود تجربه کاربری و fairness
مقیاسپذیری بسیار بالا (High Throughput)
معایب: پیچیدگی عملیاتی، نیاز به SSE یا WebSocket برای اطلاعرسانی
مناسب برای: Ultra High Traffic events (کنسرتها، فیلمهای بلاکباستر)
✅ جمعبندی فنی
هیچ راهحل واحدی برای همه سناریوها وجود ندارد
انتخاب معماری به الگوی ترافیک، سطح رقابت و محدودیت منابع وابسته است
سیستمهای High-Traffic باید Lock-free + Async + Fair Queue داشته باشند تا Tail Latency و double booking کنترل شود
Monitoring، Retry Policies و Backpressure، اجزای کلیدی در طراحی سیستم رزرو مقیاسپذیر هستند
#SystemDesign #DistributedSystems #Scalability #Concurrency #BackendArchitecture #HighTraffic #BookingSystems #Microservices #Queueing