الگوی Write-Behind و Queue-Based Caching در سیستم‌های مدرن

در بسیاری از سیستم‌های مقیاس‌پذیر، مدیریت به‌روزرسانی داده‌ها میان Cache و Database یکی از چالش‌های مهم محسوب می‌شود. یکی از الگوهای مهم در این زمینه، Write-Behind Caching (یا Deferred Write) است که با ترکیب یک صف (Queue-Based Caching) عملکرد سیستم را بهبود می‌بخشد و فشار بر دیتابیس را کاهش می‌دهد.

مقدمه‌ای بر Write-Behind Cache

در الگوی Write-Behind، تغییرات داده ابتدا در Cache ثبت می‌شود و عملیات ذخیره در پایگاه داده با تاخیر و به‌صورت غیرهم‌زمان انجام می‌گیرد. این روش باعث افزایش سرعت پاسخ‌دهی به کاربر می‌شود، چون درخواست‌ها نیازی به انتظار برای نوشتن در دیتابیس ندارند.

Cache.Set("user:105", updatedUser);
WriteQueue.Enqueue(updatedUser);
  

در این مثال، داده‌ی کاربر در Cache ذخیره و همزمان در صف WriteQueue قرار می‌گیرد تا در زمان مناسب به دیتابیس منتقل شود.

مقایسه با Write-Through

  • Write-Through: هر به‌روزرسانی ابتدا در Cache و همزمان در Database نوشته می‌شود (هم‌زمان و قابل اطمینان‌تر اما کندتر).
  • Write-Behind: به‌روزرسانی فقط در Cache ثبت و در پس‌زمینه به‌صورت غیرهم‌زمان در Database ذخیره می‌شود (سریع‌تر اما نیازمند مکانیزم اطمینان).
ویژگی Write-Through Write-Behind
زمان پاسخ‌دهی کندتر سریع‌تر
ریسک از دست دادن داده کم بیشتر
نوع نوشتن هم‌زمان (Synchronous) غیرهم‌زمان (Asynchronous)

Queue-Based Caching در Write-Behind

برای مدیریت بهتر عملیات نوشتن، معمولاً از یک صف مانند RabbitMQ، Kafka یا حتی Redis Stream استفاده می‌شود. در این مدل، هر تغییر به‌صورت پیام در صف قرار می‌گیرد و یک سرویس پس‌زمینه (Background Worker) مسئول ثبت تدریجی این پیام‌ها در دیتابیس است.

public class WriteBehindWorker : BackgroundService
{
    private readonly IQueueService _queue;
    private readonly IUserRepository _repo;

    public WriteBehindWorker(IQueueService queue, IUserRepository repo)
    {
        _queue = queue;
        _repo = repo;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var user = await _queue.DequeueAsync();
            if (user != null)
                await _repo.UpdateAsync(user);
        }
    }
}
  

در این مثال، صف حاوی داده‌هایی است که باید در دیتابیس ذخیره شوند. با این رویکرد، بار نوشتن روی دیتابیس بین لحظات مختلف پخش می‌شود و از ایجاد ترافیک شدید جلوگیری می‌کند.

کاربرد واقعی در Web API + Redis

در یک سیستم واقعی، API ابتدا داده را در Cache (مثلاً Redis) ذخیره می‌کند و سپس در یک صف Redis یا Kafka پیام به‌روزرسانی را قرار می‌دهد. سرویس پس‌زمینه با خواندن از صف، داده‌ها را با تاخیر کم در دیتابیس ثبت می‌کند.

[HttpPost("update-user")]
public async Task UpdateUser([FromBody] UserDto user)
{
    await _cache.SetAsync($"user:{user.Id}", user);
    await _queue.EnqueueAsync("user_updates", user);
    return Ok("Cached and queued successfully.");
}
  

در این حالت، سرعت پاسخ‌دهی API بسیار بالا می‌رود زیرا منتظر عملیات I/O دیتابیس نیست. در عین حال، داده‌ها به صورت قابل اطمینان در نهایت در دیتابیس ثبت می‌شوند (Eventually Consistent).

مزایا و چالش‌ها

  • مزایا: بهبود سرعت پاسخ‌دهی، کاهش فشار بر دیتابیس، امکان پردازش دسته‌ای (Batching).
  • چالش‌ها: احتمال از دست رفتن داده در صورت Crash، نیاز به مکانیزم Retry و Monitoring صف.

الگوی ترکیبی (Hybrid)

در برخی سیستم‌ها، برای داده‌های حساس از Write-Through و برای داده‌های غیرحیاتی از Write-Behind استفاده می‌شود. این ترکیب بهترین تعادل بین کارایی و اطمینان را فراهم می‌کند.

جمع‌بندی

الگوی Write-Behind همراه با Queue-Based Caching یکی از قدرتمندترین روش‌ها برای افزایش کارایی سیستم‌های داده‌محور است. با این حال، پیاده‌سازی آن نیازمند طراحی دقیق صف‌ها، تضمین تحویل پیام، مدیریت خطا و مکانیزم بازیابی (Recovery) است.