بکاپ شبانه دیتابیس، پاک‌سازی فایل‌های موقت، تمدید گواهی SSL و اجرای صف‌های اپلیکیشن، همگی به زمان‌بندی نیاز دارند و برای دهه‌ها پاسخ پیش‌فرض یک کلمه بود: cron. اما در توزیع‌های مدرن لینوکس گزینه دومی وجود دارد که بسیاری از مدیران سرور آن را نادیده می‌گیرند: systemd timer. این مکانیزم با همان systemd ای که سرویس‌های شما را مدیریت می‌کند یکپارچه است و قابلیت‌هایی مانند لاگ کامل، جلوگیری از اجرای همزمان، جبران کارهای ازدست‌رفته و محدودیت منابع را بدون اسکریپت اضافی فراهم می‌کند. در این مقاله هر دو ابزار را از نظر نحو، قابلیت‌ها و رفتار در شرایط خطا مقایسه می‌کنیم، یک timer واقعی می‌سازیم و مشخص می‌کنیم کجا cron هنوز انتخاب بهتری است.

cron چگونه کار می‌کند و کجا کم می‌آورد؟

دیمن cron هر دقیقه فایل‌های crontab کاربران (/var/spool/cron)، فایل /etc/crontab و پوشه /etc/cron.d را بررسی می‌کند و هر خطی را که الگوی زمانی‌اش با دقیقه فعلی مطابقت دارد، با شل /bin/sh اجرا می‌کند. سادگی همین مدل دلیل ماندگاری آن است: یک خط متن، بدون فایل اضافی، قابل فهم برای هر کسی که یک بار آن را دیده باشد.

نحو crontab در یک نگاه

# m h dom mon dow command
30 2 * * *   /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1
*/5 * * * *  /usr/bin/php /var/www/app/artisan schedule:run
0 4 * * 0    /usr/sbin/logrotate -f /etc/logrotate.conf

محدودیت‌های ساختاری cron

مشکلات cron زمانی خود را نشان می‌دهند که کارها پیچیده‌تر از یک اسکریپت ساده می‌شوند. اول، هیچ محافظتی در برابر همپوشانی وجود ندارد: اگر بکاپ ساعت ۲ بامداد سه ساعت طول بکشد، اجرای بعدی روی همان دیتابیس شروع می‌شود، مگر اینکه خودتان با flock قفل بگذارید. دوم، cron فقط «شروع شد» را لاگ می‌کند و خروجی و کد خطا را به ایمیل محلی می‌فرستد که در عمل هیچ‌کس نمی‌خواند. سوم، PATH محیط اجرا معمولاً فقط /usr/bin و /bin است و اسکریپتی که در ترمینال کار می‌کند، در cron با «command not found» شکست می‌خورد. چهارم، اگر سرور در زمان مقرر خاموش باشد، آن اجرا به‌کلی از دست می‌رود. و در نهایت، امکان محدود کردن حافظه، اولویت CPU یا تعریف وابستگی به سرویس دیگر وجود ندارد.

systemd timer چیست و چگونه با cron فرق دارد؟

در systemd زمان‌بندی از خود کار جدا شده است. هر کار زمان‌بندی‌شده از دو unit تشکیل می‌شود: یک فایل .service که می‌گوید چه چیزی اجرا شود (معمولاً با Type=oneshot) و یک فایل .timer با همان نام که می‌گوید چه زمانی. وقتی timer فعال می‌شود، در زمان مقرر سرویس هم‌نام خود را start می‌کند؛ بنابراین همه امکانات سرویس‌ها از جمله User=، MemoryMax=، لاگ journald و وابستگی‌ها به‌طور خودکار در اختیار کار زمان‌بندی‌شده قرار می‌گیرد.

جفت .timer و .service

# /etc/systemd/system/db-backup.service
[Unit]
Description=Nightly database backup
After=mariadb.service

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/db-backup.sh
Nice=10
IOSchedulingClass=idle

زمان‌بندی تقویمی در برابر زمان‌بندی یکنواخت

timer دو نوع زمان‌بندی دارد. OnCalendar معادل الگوی cron است و با نحوی خواناتر زمان‌های مطلق را تعریف می‌کند: OnCalendar=*-*-* 02:30:00 یعنی هر روز ساعت ۲:۳۰، OnCalendar=Mon..Fri 09:00 یعنی روزهای کاری ساعت ۹ و عبارت‌های آماده daily، weekly و hourly هم پذیرفته می‌شوند. نوع دوم زمان‌بندی یکنواخت (monotonic) است که cron اصلاً ندارد: OnBootSec=10min یعنی ده دقیقه پس از بوت، و OnUnitActiveSec=1h یعنی یک ساعت پس از آخرین اجرای سرویس. ترکیب این دو برای کارهایی مانند پاک‌سازی کش که باید «هر ساعت، اما نه در لحظه بوت» اجرا شوند ایده‌آل است.

مقایسه ویژگی به ویژگی

ویژگیcronsystemd timer
تعریف کاریک خط در crontabدو فایل unit (.timer و .service)
لاگ خروجی و کد خطاایمیل محلی یا ریدایرکت دستیخودکار در journald با journalctl -u
جلوگیری از اجرای همزمانندارد، نیازمند flockذاتی؛ تا پایان سرویس، اجرای بعدی صف می‌شود
جبران اجرای ازدست‌رفتهفقط با anacron و فقط روزانهPersistent=true با هر دقتی
تأخیر تصادفیبا sleep $RANDOMRandomizedDelaySec
محدودیت منابعنداردMemoryMax، CPUQuota، Nice، IOSchedulingClass
وابستگی به سرویس‌هانداردAfter، Requires، Wants
دقت زمانییک دقیقهثانیه، با AccuracySec قابل تنظیم
در دسترس بودنهمه‌جا، از جمله هاست اشتراکی و کانتینرفقط سیستم‌هایی با systemd و دسترسی root یا user session

ساخت یک timer عملی: بکاپ شبانه دیتابیس

فرض کنید فایل سرویس بالا را ساخته‌اید. مراحل زیر آن را به یک کار زمان‌بندی‌شده کامل تبدیل می‌کنند:

  1. فایل /etc/systemd/system/db-backup.timer را با محتوای زیر بسازید.
  2. با systemctl daemon-reload پیکربندی را بارگذاری کنید.
  3. با systemctl enable --now db-backup.timer زمان‌بند را فعال کنید؛ دقت کنید سرویس را enable نمی‌کنید، فقط timer را.
  4. برای تست فوری بدون انتظار، خود سرویس را با systemctl start db-backup.service اجرا کنید و نتیجه را در journalctl -u db-backup ببینید.
# /etc/systemd/system/db-backup.timer
[Timer]
OnCalendar=*-*-* 02:30:00
RandomizedDelaySec=15min
Persistent=true
Unit=db-backup.service

[Install]
WantedBy=timers.target

بررسی وضعیت با list-timers

دستور systemctl list-timers --all همه timer ها را با ستون‌های NEXT (اجرای بعدی)، LEFT، LAST (آخرین اجرا) و PASSED نشان می‌دهد؛ مزیتی بزرگ نسبت به cron که برای فهمیدن «آخرین بار کی اجرا شد» باید لاگ‌ها را جستجو کنید. اگر timer در فهرست نیست، احتمالاً enable نشده یا نام سرویس در خط Unit= اشتباه است.

اعتبارسنجی عبارت زمانی

یکی از اشتباهات رایج، نوشتن الگوی OnCalendar به شکلی است که معنای متفاوتی با تصور شما دارد. دستور systemd-analyze calendar "Mon..Fri 09:00" الگو را تفسیر می‌کند و زمان دقیق اجرای بعدی را نشان می‌دهد؛ با --iterations=5 پنج اجرای بعدی را می‌بینید. این ابزار را پیش از فعال کردن هر timer اجرا کنید.

قابلیت‌هایی که فقط systemd timer دارد

Persistent=true و جبران کارهای ازدست‌رفته

با Persistent=true زمان آخرین اجرا روی دیسک ذخیره می‌شود و اگر سرور در لحظه مقرر خاموش یا در حال ریبوت بوده باشد، بلافاصله پس از بوت بعدی سرویس اجرا می‌شود. برای بکاپ، تمدید گواهی و پاک‌سازی این رفتار دقیقاً همان چیزی است که می‌خواهید؛ اما برای کارهایی مثل ارسال گزارش روزانه که تکرار آن مطلوب نیست، این گزینه را فعال نکنید.

RandomizedDelaySec و AccuracySec

وقتی ده سرور مجازی همزمان ساعت ۲ بامداد بکاپ می‌گیرند، فشار ناگهانی روی ذخیره‌ساز شبکه و لینک اینترنت ایجاد می‌شود. RandomizedDelaySec=30min هر سرور را در بازه‌ای تصادفی تا نیم ساعت بعد اجرا می‌کند. از سوی دیگر AccuracySec که به‌طور پیش‌فرض یک دقیقه است، به systemd اجازه می‌دهد اجراهای نزدیک به هم را با هم گروه کند تا CPU کمتر از خواب بیدار شود؛ اگر زمان‌بندی دقیق تا سطح ثانیه‌ می‌خواهید، آن را روی 1s تنظیم کنید.

کنترل منابع و ایزوله‌سازی

چون کار در قالب سرویس اجرا می‌شود، می‌توانید با Nice=19 و IOSchedulingClass=idle بکاپ را طوری اجرا کنید که سایت در ساعت اوج کند نشود، با MemoryMax=512M مانع کشته شدن دیتابیس توسط OOM Killer شوید و با ProtectSystem=strict اسکریپت را از نوشتن در مسیرهای ناخواسته بازدارید. هیچ‌یک از این‌ها در cron بدون ابزار جانبی ممکن نیست.

کجا cron هنوز انتخاب درستی است؟

برتری فنی timer به این معنا نیست که cron را باید کنار گذاشت. در هاست اشتراکی و cPanel شما به systemd دسترسی ندارید و Cron Jobs پنل تنها گزینه است؛ Laravel Scheduler و جایگزینی wp-cron روی هاست وردپرس نیز همچنان با یک خط cron دقیقه‌ای انجام می‌شود. در کانتینرهای Docker مبتنی بر Alpine که systemd ندارند، cron یا ابزارهای سبک‌تر مانند supercronic کاربرد دارند. اسکریپت‌های نصبی که باید روی چند توزیع کار کنند، با crontab قابل حمل‌تر هستند. قاعده سرانگشتی ساده این است: اگر کار مهم است و شکست آن باید دیده شود، timer بسازید؛ اگر کار بی‌اهمیت و کوتاه است، cron کافی است.

مهاجرت از cron به systemd timer: نکات عملی

  • متغیرهای محیطی crontab (مانند PATH و MAILTO) را به بخش [Service] با Environment= یا EnvironmentFile= منتقل کنید.
  • به جای MAILTO، از OnFailure=notify-admin.service استفاده کنید تا در صورت شکست، یک سرویس هشداردهنده (مثلاً ارسال پیام تلگرام) اجرا شود.
  • ریدایرکت‌های >> و 2>&1 را حذف کنید؛ خروجی به‌طور خودکار به journald می‌رود.
  • برای کارهای کاربر غیر root می‌توانید timer را با systemctl --user بسازید، اما فراموش نکنید loginctl enable-linger را اجرا کنید تا بدون ورود کاربر هم اجرا شود.
  • الگوهای پیچیده مانند «هر ۱۵ دقیقه بین ساعت ۸ تا ۱۸» به OnCalendar=*-*-* 08..18:00/15:00 تبدیل می‌شوند؛ نتیجه را با systemd-analyze calendar تأیید کنید.

جمع‌بندی

systemd timer برای هر کار زمان‌بندی‌شده‌ای که شکست آن اهمیت دارد، ابزار برتری است: لاگ کامل، جلوگیری از همپوشانی، جبران اجرای ازدست‌رفته، تأخیر تصادفی و محدودیت منابع را بدون یک خط اسکریپت اضافی فراهم می‌کند. cron همچنان در هاست اشتراکی، کانتینرها و کارهای ساده جایگاه خود را دارد. روی سرور مجازی یا اختصاصی، کارهای حیاتی مانند بکاپ را به timer منتقل کنید و از list-timers به‌عنوان داشبورد سریع سلامت زمان‌بندی‌ها استفاده کنید.

سوالات متداول

آیا systemd timer جایگزین کامل cron است؟

از نظر فنی بله؛ هر الگوی cron با OnCalendar قابل بیان است و timer امکانات بیشتری دارد. اما در محیط‌هایی بدون systemd مانند هاست اشتراکی، cPanel و کانتینرهای Alpine همچنان باید از cron استفاده کنید. بسیاری از سرورها هر دو را در کنار هم دارند.

چگونه بفهمم timer من اجرا شده یا نه؟

دستور systemctl list-timers ستون LAST را نشان می‌دهد و journalctl -u نام‌سرویس خروجی کامل هر اجرا را به همراه کد خطا ثبت می‌کند. اگر ستون LAST خالی است و NEXT زمان مناسبی ندارد، الگوی OnCalendar را با systemd-analyze calendar بررسی کنید.

اگر سرور در زمان اجرای timer خاموش باشد چه می‌شود؟

بدون Persistent=true آن اجرا از دست می‌رود، درست مانند cron. با Persistent=true، systemd پس از بوت بعدی متوجه می‌شود اجرای مقرر انجام نشده و فوراً سرویس را اجرا می‌کند. برای بکاپ و تمدید گواهی این گزینه را همواره فعال کنید.

معادل */5 * * * * در OnCalendar چیست؟

عبارت OnCalendar=*:0/5 یعنی هر پنج دقیقه از هر ساعت. برای هر پنج دقیقه فقط در روزهای کاری از Mon..Fri *:0/5 استفاده کنید. نحو کلی به شکل DayOfWeek Year-Month-Day Hour:Minute:Second است و هر بخش اختیاری است.

آیا می‌توان timer را بدون دسترسی root ساخت؟

بله، با systemctl --user فایل‌های timer و service در ~/.config/systemd/user ساخته می‌شوند. برای اجرا بدون نشست فعال کاربر، باید یک بار loginctl enable-linger توسط root اجرا شود.