Queue و Background Job چیست و چه زمانی باید کار را از درخواست اصلی جدا کنیم؟

دکمه «ثبت سفارش» فروشگاه اینترنتی فشرده میشود و در ثانیههای بعد، سرور دست به کارهایی میزند که هیچکدام ربطی به گرفتن سفارش ندارد: ایمیل تأیید را مینویسد و ارسال میکند، فایل فاکتور را تولید میکند، موجودی انبار را در سرویس دیگری همگام میکند، به سرویس پیامک درخواست میدهد و امتیاز باشگاه مشتریان را بهروز میکند. کاربر، پشت صفحه، به چرخش فلش انتظار نگاه میکند. اگر شبکه کند باشد، دوباره کلیک میکند.
مشکل اینجا فقدان تلاش یا کد ضعیف نیست؛ مشکل معماری است: یک درخواست وب که قرار است فقط تصمیم بگیرد و پاسخ بدهد، خودش را به کوهی از کارهای سنگین و کند گره زده است. راهحل این الگوی رایج، جداسازی کار از درخواست با دو ابزار مکمل است: صف (Queue) و کار پسزمینهای (Background Job). در این مقاله مسیر مسئله، پیامد و راهکار را با یک فروشگاه فرضی دنبال میکنیم.
مسئله: درخواستی که همهکاره شده
در الگوی همگام (Synchronous)، مرورگر تا تمامشدن همه کارها منتظر میماند و پاسخ فقط بعد از آخرین گام برمیگردد. نتیجه این است که سرور هنگام پاسخگویی به یک کاربر، عملاً به همه سرویسهای جانبی وابسته است: اگر سرویس ایمیل کند شود، ثبت سفارش هم کند میشود و اگر قطع شود، ثبت سفارش خطا میدهد. این یک وابستگی معکوس عجیب است: کار سطحپایین و تأخیرپذیر مثل ایمیل، سرنوشت کار سطحبالا و فوری مثل سفارش را تعیین میکند.
نکته ظریفتر، رفتار کاربر است. پاسخ کند، دوبارهکلیک میآورد؛ دوبارهکلیک یعنی دو درخواست ثبت سفارش، دو ایمیل و اگر سرور پردازش را همانجا انجام دهد، دو بار تغییر در انبار. سیستم بیدفاع در برابر تکرار، هر لحظه در حال ساختن نسخههای دوقلوی یک واقعیت است و هیچکس متوجه نمیشود تا وقتی حسابداری تفاوت را ببیند.
پیامد: هزینههایی که دیر پیدایشان میکنید
- تجربه کاربری تلخ در ساعات اوج: هر چه بار سنگینتر شود، صف سرویسهای جانبی طولانیتر میشود و هر درخواست کندتر؛ دقیقاً همان ساعاتی که فروش حیاتی است.
- تکرار کار و تناقض داده: کاربر بیقرار دوباره کلیک میکند یا مرورگر درخواست را دوباره میفرستد؛ نتیجه فاکتور تکراری، ایمیل دوتایی و رکوردهای نیمهکاره است.
- شکنندگی زنجیره: افتادن هر حلقه، ثبت سفارش را میاندازد؛ یعنی بزرگترین ریسک کسبوکار به کوچکترین سرویس جانبی گره میخورد.
- ناتوانی در تلاش دوباره هوشمند: وقتی همهچیز در یک فراخوانی است، اگر ایمیل میان راه خطا بزند، یا همهچیز را از اول شروع میکنید یا همهچیز را رها میکنید؛ گزینه سومی وجود ندارد.
پیامد پنهان اما مهم، مقیاسپذیری است: برای جبران کندی، تیم سرور را قویتر میکند، اما گلوگاه روی سرویس ایمیل یا تولید فاکتور است، نه روی سرور وب. منبع در جای اشتباه هزینه میشود و مشکل همانجا باقی میماند.
راهکار: صف و کار پسزمینهای
ایده مرکزی ساده است: درخواست اصلی فقط «تصمیم» را ثبت کند — سفارش ساخته شد — و کارهای سنگین و تأخیرپذیر بهصورت پیام در یک صف گذاشته شوند. پردازندههای جداگانه به نام کارگر (Worker) از صف برمیدارند و کار پسزمینهای را در زمان خودشان اجرا میکنند. کاربر پاسخ سریع میگیرد و کارهای سنگین، بیرون از دید او، با ریتمی که سیستم تحمل میکند پیش میروند. به این شیوه، پردازش ناهمگام (Asynchronous) میگویند: فرستنده و گیرنده لازم نیست همزمان در دسترس باشند.
صف چگونه کار میکند؟
صف مثل مانیفست یک انبار پستی است: تولیدکننده (Producer) پیام را مینویسد، صف پیام را تا نوبتش نگه میدارد و کارگرِ آماده، پیام را برمیدارد. نکته کلیدی «تأیید انجام» (Acknowledgment) است: کارگر پس از موفقیت، پیام را تأیید میکند؛ اگر وسط کار سرویس کارگر از دست برود، پیام تأییدنشده به صف برمیگردد و کارگر دیگری دوباره برمیدارد. درخواست کاربر به این چرخه هیچ ربطی ندارد؛ او مدتها پیش پاسخ «سفارش ثبت شد» را گرفته است.
چه کارهایی به صف تعلق دارند؟
- ارتباطات خروجی: ایمیل، پیامک و اعلان داخل اپلیکیشن — مهم، اما نه آنقدر که کاربر رویش معطل بماند.
- پردازش فایل: تولید فاکتور، بهینهسازی تصویر محصول، استخراج متن از فایل آپلودشده.
- گزارش و همگامسازی: گزارش فروش ماهانه، بهروزرسانی ایندکس جستوجو، هماهنگکردن موجودی انبار با فروش.
- کارهای زمانبندیشده: یادآوری سبد خرید رهاشده، پاکسازی دادههای موقت، بایگانی سفارشهای قدیمی.
چه چیزهایی در صف جا نمیشوند؟
هر جا پاسخ باید همان لحظه ساخته شود، کار باید در درخواست بماند: اعتبارسنجی فرم، احراز هویت، اخذ مجوز پرداخت و خواندن نتیجهای که کاربر منتظرش است. اگر کاربر روی «صدور کارت عضویت» کلیک میکند تا همان لحظه کارت را ببیند، تولید کارت تأخیرپذیر نیست. معیار خوب این پرسش است: اگر این کار ده ثانیه طول بکشد، کاربر چه میبیند؟ اگر پاسخ «فلش انتظار بیتوضیح» است، کار جای صفنشینی دارد؛ اگر پاسخ «صفحه پیشرفت با خبر بعدی» است، کار پسزمینهای است.
مثال کاربردی: بازسازی فرایند ثبت سفارش
حالا فروشگاه را با معماری تازه بازنویسی کنیم. مسیر تازه اینگونه پیش میرود:
- کاربر «ثبت سفارش» را میزند؛ سرور در یک تراکنش کوتاه، سفارش را با وضعیت «در انتظار پردازش» ذخیره میکند و پیام «ایمیل بفرست» و «فاکتور بساز» را در صف میگذارد.
- پاسخ بلافاصله برمیگردد: سفارش شما ثبت شد؛ کاربر به صفحه پیگیری میرود و روند کار را میبیند.
- کارگر ایمیل، پیام را برمیدارد و ارسال میکند؛ اگر سرویس ایمیل موقتاً خطا بدهد، با فاصله کوتاه دوباره تلاش میکند و پس از چند تلاش ناموفق، کار به صف مخصوص بررسی دستی میرود.
- کارگر فاکتور را میسازد و وضعیت سفارش را به «آماده» تغییر میدهد؛ صفحه پیگیری که هر بار وضعیت را میپرسد، پیشرفت را نشان میدهد.
دو نکته مهندسی در این طراحی پررنگ است. اول، هر کار باید تکرارپذیر (Idempotent) باشد: چون صف ممکن است پیام را دوباره تحویل دهد، کارگر باید تشخیص دهد کار قبلاً انجام شده و از دوبارهکاری بپرهیزد؛ در غیر این صورت ایمیل تأیید دوباره به صندوق کاربر میرود. دوم، ردیابی لازم است: هر پیام شناسه سفارش را حمل میکند تا بتوان پرسید «فاکتور سفارش شماره فلان کجاست؟» و پاسخ دقیقی گرفت.
مزایا و محدودیتها
مزایا روشن است: پاسخ سریع و ثابت درخواست اصلی، جدایی خرابیها — قطعی سرویس ایمیل دیگر سفارش را نمیاندازد — مقیاسکردن مستقل کارگرها در ساعات اوج و تلاش دوباره ساختاریافته. صف همچنین حافظه اوج است: در ساعات شلوغی پیامها در صف میمانند و بهتدریج پردازش میشوند، بهجای آنکه موج فشار مستقیم به سرور وب و سرویسهای جانبی برسد.
محدودیتها هم جدیاند. صف یعنی اجزای بیشتری برای نگهداری و پایش: عمق صف، طول تأخیر و کارهای گیرکرده باید زیر نظر باشند. ترتیب اجرا تضمین نمیشود مگر اینکه خودتان مدیریتش کنید؛ ممکن است کار B پیش از A اجرا شود. اشکالزدایی هم سختتر است، چون یک عملیات کاربر حالا میان چند جزء ناهمگام پخش شده است. و مهمتر از همه: صف وسیله سرکوب خرابی نیست؛ اگر کارگرها از صف عقب بمانند، تأخیر در ایمیلها به مشکل قابل مشاهده برای کاربران بدل میشود و باید با افزودن کارگر یا مهار بار پاسخ داده شود.
کاربرد عملی
اگر میخواهید این الگو را در پروژه خودتان به کار بگیرید، از کوچک شروع کنید: یک کار تأخیرپذیر و کمریسک مثل ایمیل تأیید را به صف منتقل کنید و زیرساخت را با آن بیازمایید؛ بعد کارهای سنگینتر مثل پردازش فایل و گزارش را اضافه کنید. برای هر کار، سه پرسش را از قبل جواب بدهید: اگر دوباره اجرا شود چه میشود (تکرارپذیری)؟ اگر شکست بخورد، چند بار و با چه فاصلهای تلاش میکنیم (تلاش دوباره)؟ و کجا میتوانیم وضعیتش را ببینیم (ردیابی و پایش)؟ صفی که پاسخ این سه پرسش را نداشته باشد، بهمرور به جعبه سیاهی بدل میشود که هیچکس جرئت نگاه کردن درونش را ندارد.
نکات کلیدی این مقاله
- معیار جداسازی کار از درخواست، اثر تأخیر بر تجربه کاربر است، نه بزرگی فنی کار.
- پاسخ سریع درخواست اصلی، مزیت جانبی نیست؛ خودِ هدف معماری است.
- تلاش دوباره بدون تکرارپذیری، بهجای رفع خطا، تولید خطای تکراری میکند.
- صف بدون پایش عمق، تأخیر و شکستها به جعبه سیاه بدل میشود.
- کارهایی که پاسخشان همان لحظه لازم است باید در درخواست بمانند؛ همهچیز جا صف ندارد.
سوالات متداول
اگر خود صف از کار بیفتد چه؟
صف جزئی از زیرساخت است و باید مانند پایگاه داده پایش و پشتیبانگیری شود. در طراحی درست، شکست صف معمولاً یعنی تأخیر در کارهای پسزمینه، نه توقف ثبت سفارش؛ چون درخواست اصلی فقط پیام مینویسد. با این حال، صف در همان سطح قابلیت اطمینان سرویسهای حیاتی قرار میگیرد و سناریوی جایگزینکردنش باید از قبل تمرین شده باشد.
آیا صف همان معماری رویدادگرا (Event-Driven) است؟
نزدیکاند اما یکسان نیستند. صف کار معمولاً «دستور» حمل میکند: این کار را انجام بده. معماری رویدادگرا «خبر» منتشر میکند: این اتفاق افتاد، هر کس مربوط است خودش اقدام کند. در پروژههای واقعی هر دو شکل کنار هم دیده میشوند و مرزشان به تصمیم طراحی برمیگردد؛ اصل مشترک هر دو، جداسازی زمانی میان فرستنده و گیرنده است.
برای پروژه کوچک هم ارزش دارد؟
اگر پروژه فقط کارهای لحظهای ساده دارد، نه؛ پیچیدگی بیدلیل میشود. اما بهمحض پیدا شدن یک کار تکرارشونده سنگین — تولید گزارش، ایمیل انبوه، پردازش تصویر — حتی یک صف کوچک با یک کارگر، از کند شدن رابط کاربری و شکلگیری الگوهای شکننده جلوگیری میکند. هزینه راهاندازی صف در سالهای اخیر پایین آمده و بار اصلی روی تصمیم معماری است، نه زیرساخت.



