לדלג לתוכן

משימות רקע ותורי עבודה - Background Jobs מוסבר

תארו לעצמכם שמשתמש נרשם לאתר שלכם, ואתם רוצים לשלוח לו מייל ברוכים הבאים. נשמע פשוט, אבל שליחת מייל יכולה לקחת כמה שניות טובות - התחברות לשרת המייל, בניית התוכן, המתנה לאישור שליחה. אם תעשו את זה בתוך אותה בקשת HTTP שמטפלת בהרשמה, המשתמש יושב ומחכה למסך שמסתובב, רק כדי שהחשבון שלו כבר נוצר מזמן. זו בדיוק הבעיה שמשימות רקע פותרות.

הבעיה - בקשה שנתקעת בגלל עבודה איטית

שרת ווב טוב אמור לענות לבקשה תוך זמן קצר. אבל לא כל פעולה שקורית בעקבות בקשה היא מהירה - שליחת מייל, שינוי גודל לתמונה שהועלתה, יצירת דוח PDF, קריאה לשירות חיצוני איטי. אם כל הפעולות האלה קורות בתוך אותה בקשת HTTP שהמשתמש ממתין לה, כל הזרימה נעצרת עד שהעבודה האיטית מסתיימת. וזה לא רק חוויית משתמש גרועה - זה גם תופס משאבי שרת יקרים למשך זמן ארוך מהנדרש, ומגביל כמה בקשות אתם יכולים לשרת בו זמנית.

הרעיון - להפריד בין קבלת הבקשה לביצוע העבודה

הפתרון הוא לא לבצע את העבודה האיטית בזמן הבקשה עצמה, אלא רק "לרשום" שצריך לבצע אותה, ולהחזיר תשובה למשתמש מיד. ה-handler שמטפל בבקשה דוחף משימה לתור - "שלח מייל ברוכים הבאים למשתמש הזה" - ומיד עונה למשתמש שההרשמה הצליחה. בצד אחר, לגמרי נפרד מהבקשה המקורית, יש process ייעודי שנקרא worker, שכל תפקידו הוא לשלוף משימות מהתור ולבצע אותן בקצב שלו. המשתמש כבר קיבל תשובה וממשיך הלאה, בזמן שהמייל נשלח ברקע כמה שניות אחר כך.

זה בדיוק הרעיון שמאחורי כלים כמו Celery בפייתון, Sidekiq ברובי, או BullMQ בעולם Node.js - ספריות שנועדו במיוחד לנהל את התהליך הזה של דחיפת משימות לתור, והרצתן על ידי workers נפרדים מהשרת שמטפל בבקשות.

שלושת החלקים של כל מערכת משימות רקע

כל מערכת כזו בנויה מכמה חלקים בסיסיים. יש את היוזם - הקוד בתוך ה-handler שמזהה שצריך לבצע עבודה איטית, ודוחף אותה לתור במקום להריץ אותה בעצמו. יש את התור עצמו, מקום שמחזיק את המשימות הממתינות עד שמישהו יטפל בהן - לרוב זה נשען על broker כמו Redis או כלי דומה שתפקידו רק להחזיק את התור בצורה אמינה. ויש את ה-workers, תהליכים נפרדים שרצים במקביל לשרת הראשי, מושכים משימות מהתור אחת אחרי השנייה, ומבצעים אותן.

חשוב להבהיר - זה שונה מהותית מתקשורת בין כמה שירותים נפרדים בארכיטקטורת מיקרוסרביסים, שזה נושא שדיברנו עליו בפוסט על קפקה. כאן מדובר באפליקציה אחת, שרק רוצה להוציא עבודה איטית מחוץ למחזור החיים של בקשת HTTP שלה, לא בתקשורת בין שירותים עצמאיים.

מה קורה כשמשימה נכשלת

תארו לעצמכם ש-worker מנסה לשלוח מייל, אבל שרת המייל לא זמין באותו רגע. אם המשימה פשוט נעלמת, המשתמש לעולם לא יקבל את המייל שלו, ואף אחד לא ידע. מערכות משימות רקע טובות תומכות במנגנון retry - אם משימה נכשלת, היא חוזרת לתור ומנסים אותה שוב, בדרך כלל עם המתנה גדלה בין ניסיון לניסיון, כדי לא להציף שירות שכבר מתקשה. אחרי כמה ניסיונות כושלים, המשימה בדרך כלל עוברת לרשימה נפרדת של משימות שנכשלו לגמרי, כדי שמישהו יוכל לבדוק ידנית מה השתבש.

למה זה שומר על השרת שלכם מגיב

התוצאה הסופית של כל זה היא ששרת ה-HTTP שלכם נשאר קליל ומגיב - הוא לא מבזבז זמן וזיכרון על עבודה שיכולה לקרות כמה שניות מאוחר יותר. זה הופך את המערכת לעמידה יותר בעומס, כי בקשות רגילות ממשיכות להיענות מהר גם כשיש הרבה עבודה ברקע ממתינה לביצוע.

בקורס צד שרת בונים תהליך משימות רקע אמיתי, כולל תור ו-worker נפרד, ורואים בעיניים איך שרת שמוציא עבודה איטית החוצה מרגיש שונה לגמרי למשתמש הקצה.

לסיכום

משימות רקע פותרות בעיה מאוד יומיומית - בקשת HTTP לא צריכה לחכות לעבודה שלא ממש דחוף שתסתיים באותו רגע. handler דוחף משימה לתור, worker נפרד מבצע אותה, ומנגנון retry דואג שמשימה שנכשלה לא פשוט תיעלם. זה שינוי קטן בארכיטקטורה שעושה הבדל ענק בחוויית המשתמש ובעמידות של המערכת.

יש לכם פעולה איטית שתקועה כרגע בתוך בקשה סינכרונית ולא בטוחים איך להוציא אותה לרקע? זו בדיוק סוג השאלה שמתאימה לדיסקורד שלנו.

הצטרפו לקהילה בדיסקורד