کارهای پسزمینه و زمانبندی در نرمافزار سازمانی

هر سامانهای که مدتی زنده بماند، به یک نقطه میرسد: کاربری دکمهای را میزند و صفحه برای بیست ثانیه میماند. کسی گزارشی میگیرد که باید صد هزار ردیف را جمع بزند، یا فاکتوری صادر میشود که باید همزمان به سامانهٔ مالیاتی برود و پیامک هم بفرستد.
راهحل همیشه یکی است — این کارها را از مسیر درخواست کاربر بیرون بیاورید — و اجرای درستش جایی است که پروژهها معمولاً لنگ میزنند.
چه چیزی باید پسزمینه برود
معیار ساده است: اگر کاربر برای دیدن نتیجه لازم نیست منتظر بماند، نباید منتظر بماند.
مثالهای شایع در سامانههای سازمانی:
- ارسال پیامک و ایمیل. کاربر باید بداند «سفارش ثبت شد»، نه اینکه «پیامک تحویل مخابرات شد».
- تولید فایل خروجی — اکسل، PDF فاکتور، گزارش ماهانه.
- فراخوانی سامانههای بیرونی: درگاه پرداخت در مرحلهٔ تأیید، سامانهٔ مؤدیان، انبار، حسابداری.
- پردازش تصویر و ویدیو، از تغییر اندازهٔ عکس محصول تا استخراج متن از فاکتور اسکنشده.
- محاسبات دورهای: تسویهٔ فروشندگان، بهروزرسانی موجودی، بستن روز.
نکتهٔ مشترک این فهرست این است که هیچکدام سریعتر نمیشوند اگر کاربر پشت خط بماند. فقط ناموفقتر میشوند، چون یک تایماوت در مرورگر یا در بارگذار (load balancer) وسط کار قطعشان میکند.
دو ابزار که یکی نیستند
اینجا دو مفهوم مدام جای هم استفاده میشوند و تفکیکشان تصمیمهای بعدی را روشن میکند.
زمانبندی (scheduling) یعنی «این کار را هر شب ساعت دو اجرا کن». محرکش زمان است. کسی درخواستی نداده؛ تقویم داده.
صف کار (job queue) یعنی «این کار پیش آمده، هر وقت توانستی انجامش بده». محرکش یک رویداد است — سفارشی ثبت شده، فایلی آپلود شده.
سامانهٔ سازمانی معمولاً هر دو را لازم دارد و اشتباه رایج این است که یکی را با دیگری شبیهسازی کنند: کرانی که هر دقیقه اجرا میشود و جدول را نگاه میکند «کار جدیدی هست؟». این کار میکند، تا وقتی که حجم بالا برود. آنوقت تأخیر متوسط سی ثانیه میشود و پایگاه داده در هر دقیقه یک پرسوجوی بیفایده میخورد.
اگر مقیاس کار بالاست و ترتیب و تحویل اهمیت دارد، سراغ صف واقعی بروید. تفاوت سازوکارها و اینکه کجا باید صف پیام اضافه کنید، موضوع مستقلی است که جدا نوشتهایم.
یک قاعده که همهچیز به آن برمیگردد
کار پسزمینه باید قابل اجرای دوباره باشد بدون آنکه اثرش دوبار بیفتد.
این خاصیت را idempotency مینامند و در عمل یعنی: اگر همان کار به هر دلیلی دوبار اجرا شد — سرور وسط کار ریاستارت شد، پیام دوبار تحویل شد، اپراتور دستی دوباره زد — نتیجه با یک بار اجرا فرقی نکند.
چرا اینقدر مهم است؟ چون خرابی حتمی است. سرور میافتد، شبکه قطع میشود، فرایند kill میشود. هر سامانهٔ صفی که جدی گرفته شود، تحویل «حداقل یک بار» میدهد نه «دقیقاً یک بار» — یعنی تکرار را باید خودتان خنثی کنید.
راه عملیاش معمولاً یک کلید یکتا برای هر کار است: شناسهٔ فاکتور، شناسهٔ تسویه، شناسهٔ پیامک. قبل از اثرگذاری بررسی کنید که این کلید قبلاً پردازش نشده باشد و همان را در پایگاه داده ثبت کنید.
کاری که این خاصیت را ندارد، در بدترین شب سال دو بار پول جابهجا میکند.
سرور دوم که اضافه میشود
بیشتر باگهای این حوزه در روز اول دیده نمیشوند. در روزی دیده میشوند که برای مقیاس، نسخهٔ دوم برنامه بالا میآید.
اگر کران داخل خود برنامه تعریف شده باشد، حالا هر دو نسخه ساعت دو شب بیدار میشوند و هر دو تسویهٔ ماهانه را اجرا میکنند. اگر کار idempotent نباشد، هر فروشنده دو بار پول میگیرد.
سه راه برای این وضعیت، به ترتیب سادگی:
- قفل توزیعشده. قبل از شروع، کار قفلی با مهلت انقضا میگیرد؛ هر که گرفت اجرا میکند و بقیه رد میشوند. ساده و برای اکثر سامانهها کافی.
- زمانبند بیرون از برنامه. یک کران در سطح سیستمعامل یا کلاستر که به یک نقطهٔ ورودی میزند. مسئولیت روشنتر است اما یک قطعهٔ زیرساختی به نگهداری اضافه میشود.
- کارگر اختصاصی. فرایندی جدا از وب که فقط کار پسزمینه اجرا میکند و مستقل از وب مقیاس میگیرد. برای سامانههای بزرگ همین درست است.
توصیهٔ ما برای اکثر پروژههای سازمانی: از روز اول کارگر را جدا کنید، حتی اگر فعلاً روی همان ماشین اجرا شود. جداسازی بعدی یک تغییر استقرار است؛ جدا کردن کدی که سالها فرض کرده در بستر درخواست وب اجرا میشود، یک بازنویسی است.
شکست را چطور مدیریت کنیم
کار پسزمینه شکست میخورد و این عادی است. آنچه عادی نیست، نداشتن تصمیم دربارهٔ آن است.
تلاش دوباره با فاصلهٔ فزاینده. بلافاصله دوباره زدن به سامانهای که پایین است فقط فشار را زیاد میکند. فاصلهها را پلهای زیاد کنید: چند ثانیه، بعد چند دقیقه، بعد ساعت.
سقف تلاش. کاری که هزار بار تکرار شود، صف را میبندد و لاگ را پر میکند. بعد از چند تلاش، کار باید کنار گذاشته شود.
صف مردگان. کارهای شکستخوردهٔ نهایی باید جایی بروند که کسی ببیندشان. اسم فنیاش dead letter queue است و کارکردش این است که خطای خاموش را به خطای قابل مشاهده تبدیل کند. صفی که فقط کار موفق را ثبت میکند، به شما آرامش کاذب میدهد.
اعلان. اگر تسویهٔ شبانه سه شب پشت هم شکست بخورد و کسی خبردار نشود، اولین کسی که میفهمد فروشندهای است که پولش نرسیده. این را در پایش و لاگ مفصلتر گفتهایم؛ خلاصهاش این است که سلامت صف باید همانقدر پایش شود که سلامت سرور.
چیزی که در سامانههای ایرانی جدیتر است
دو نکته که در پروژههای داخلی مکرر دیدهایم:
سامانههای حاکمیتی در ساعات خاص کند یا در دسترس نیستند. اتصال به سامانهٔ مؤدیان یا استعلامهای بانکی، در روزهای پایان دوره صف و تأخیر دارد. کاری که این اتصالها را همزمان با درخواست کاربر انجام دهد، در همان روزها میایستد. اینها تقریباً همیشه باید پسزمینه باشند، حتی اگر معمولاً سریع پاسخ بدهند.
پیامک و اعتبار پنل. ارسال پیامک از طریق پنلهای داخلی انجام میشود که اعتبار دارند و تمام میشوند. کار پسزمینهای که در اثر تمامشدن اعتبار شکست میخورد و بیصدا رد میشود، یعنی هفتهای که هیچ مشتریای کد تأیید دریافت نکرده. سطح اعتبار خودش باید پایش شود، نه اینکه از روی خطای ارسال کشف شود.
چند تصمیم عملی
- زمان اجرای کارهای سنگین را پخش کنید. اگر همهٔ کارهای شبانه ساعت صفر تعریف شوند، یک قلهٔ مصرف میسازید و نتیجهاش کندی همزمان همه است.
- کار طولانی را بشکنید. کاری که چهار ساعت طول میکشد، در ساعت سوم شکست میخورد و از اول شروع میشود. اگر به بخشهای قابل ادامه تقسیم شود، شکست فقط همان بخش را برمیگرداند.
- وضعیت را به کاربر نشان دهید. «گزارش شما در حال آمادهسازی است» با یک نشانگر پیشرفت، خیلی بهتر از صفحهای است که هیچ نمیگوید. کاربری که فکر کند کار انجام نشده، دوباره میزند — و شما همانجا به تکرار برمیگردید.
- لاگ کار را نگه دارید. چه کاری، کی، چقدر طول کشید، با چه نتیجه. اولین سؤالی که در بررسی هر مشکل پرسیده میشود همین است.
جمعبندی نهچندان مرتب
اگر سامانهای میسازید که با پول، موجودی یا سامانههای بیرونی کار دارد، کار پسزمینه یک ویژگی جانبی نیست؛ بخشی از معماری است و باید در برآورد اولیه دیده شود.
ما این را در توسعهٔ بکاند از ابتدای طراحی جا میگذاریم، چون تجربه نشان داده اضافهکردنش به سامانهای که همهچیزش را در بستر درخواست وب انجام میدهد، هزینهاش چند برابر گذاشتنش از روز اول است.