פיתוח ב-Elixir: היכן שהבקאנד נתקל בתקרה
כל עוד יש מאות חיבורים במקביל, הבקאנד מגיב באופן יציב. באלפים התמונה נשברת: זמן התגובה גדל באופן לא ליניארי, חלק מהבקשות אינן מגיעות למטפל, והלקוחות חוזרים ושולחים שוב — ובמקום בקשה אחת נוצרות במערכת שתיים-שלוש העתקים. אתחול מחדש של הצומת מחסל את הסשנים החיים: המשתמש חוזר לטופס ריק, והתמיכה לזרם של פניות זהות.
הסיבה לעתים רחוקות נמצאת בחומרה. צוואר הבקבוק של הבקאנד הוא מודל ההרצה: אם כל בקשה תופסת תהליכון נפרד לכל משך ההמתנה למסד הנתונים או לשירות חיצוני, המקביליות נתקלת בגודל המאגר.
התור גדל מהר יותר מהעומס, וההשהיות בעומס מזינות את עצמן: ככל שהתגובה ארוכה יותר, כך יש יותר נסיונות חוזרים.
פיתוח ב-Elixir מסיר את התקרה הזו. התהליכים של המכונה הווירטואלית קלי משקל — מחזיקים מאות אלפים מהם במקביל, והמתזמן בעצמו מפזר את העבודה בין הליבות ללא כוונון ידני של מאגרים. המטפל אינו נחסם בהמתנה: הוא מושהה ומפנה את המשאב לאחרים, ולכן זמן התגובה גדל באופן ליניארי ולא בקריסה.
האפקט השני — סופרוויזרים. הצומת, תת-המערכות והחיבורים מתוארים כעץ: נפילה של תהליך אחד מפעילה מחדש רק אותו, ולא את היישום כולו. עדכון גרסה עובר בלי לנתק חיבורים, מצב הסשן שורד אתחול מחדש — אובדן חיבורים אינו עוד עבודה אבודה של המשתמש.
אנחנו מתחילים מפרופיל העומס: היכן בדיוק הבקאנד העמוס שלכם נתקל בתקרה, אילו בקשות משוכפלות, מה קורה בעת כשל של צומת. בהמשך — תוכנית מעבר, סדר המיגרציה והערכה.
עמידות בעומס: חיבורים, תורים, נסיונות חוזרים
תחת עומס הדבר הראשון שנשבר אינו הקוד, אלא הקשרים בין השירותים: מאגר החיבורים מוצה, התור בכניסה גדל, והנסיונות החוזרים מחסלים צומת שכבר עמוס. כל כשל כזה נראה אחרת, אך המחיר אחד — השהיה גדלה ומפל של פסקי זמן. נפרק מה סוגר את העמידות בעומס ואיך למדוד אותה.
בידוד כשלים נבנה על עץ התהליכים: חיבור אחד — תהליך קל נפרד עם מצב משלו. נפילה של מטפל אינה מפילה את הצומת, הפיקוח מפעיל מחדש רק את הענף שנכשל, והשאר ממשיכים לשרת תעבורה. כאן בדיוק נוצר הפער בזמן ההתאוששות של השירות: שניות להרמת ענף במקום דקות לאתחול כל היישום.
אחזקת חיבורים נפתרת לא בהגדלת מאגרים, אלא בהגבלתם ובירידה מבוקרת בביצועים. הצומת מחזיק עשרות אלפי חיבורים במקביל בלי גידול ליניארי בהשהיה, כי ההמתנה אינה תופסת תהליכון נפרד. כשמגיעים למגבלת התור, השירות מחזיר תוצאה מצומצמת או נתונים מהמטמון במקום לצבור בקשות עד לפסק זמן.
נסיונות חוזרים של בקשות נעשים אידמפוטנטיים: מפתח פעולה אחד, השהיה גדלה, פיזור אקראי, תקרה על מספר הנסיונות. כך נעלמים שלושה סוגי כשלים — מיצוי המאגר, פסקי זמן מדורגים ו"עדר" של נסיונות חוזרים סינכרוניים שמגביר את השיא. ב-Elixir המנגנונים הללו הם בבסיס הפלטפורמה, ולא מוברגים מעל היישום.
את התוצאה מודדים לפי שלושה מדדים: השהיית זנב בשיא, שיעור הבקשות הנדחות וזמן עד התאוששות מלאה לאחר נפילת צומת בודד. הדינמיקה שלהם לפני ואחרי השינויים מראה עמידות אמיתית — בניגוד להבטחות "יעמוד בכל עומס".
אפשרויות מימוש: תהליכים מתמידים, היברידי, שירותים מוכנים
מודל ההרצה קובע היכן חי מצב החיבור ומה קורה בעת כשל של צומת. בפיתוח ב-Elixir תהליכים מתמידים מחזיקים את מצב כל סשן באופן מבודד: נפילה של אחד אינה גוררת את האחרים, והמתזמן בעצמו מפזר את העומס בין הליבות ללא נעילות ידניות.
סטאק קלאסי עם מצב משתנה משותף קל יותר לגיוס, אך עם גידול במספר החיבורים במקביל הוא נתקל בנעילות ובתיאום של מטמון משותף.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| תהליכים מתמידים | הרבה חיבורים במקביל, סשנים ארוכים, סטרימינג, צ'אטים, טלמטריה | אי אפשר להחזיק את המצב במאגר משותף; את הצוות צריך ללמד את מודל הבידוד; אתחול מחדש של מצב חי דורש תרחיש מחושב |
| סטאק קלאסי עם מצב משותף | פרופיל CRUD יציב, חיבורים ארוכים נדירים, הצוות כבר שולט בסטאק | נעילות ומטמון משותף הופכים לצוואר בקבוק; עם גידול החיבורים גדלים מספר המופעים והתקורה על סנכרון |
| היברידי | הליבה נשארת על הסטאק הנוכחי, ומצב זמן-האמת מוצא למעגל נפרד | שני מעגלים בתפעול: אימות משותף, נראות אחודה, עקביות נתונים ביניהם |
| שירותי ענן מוכנים | צריך החלפת אירועים במהירות, אין צוות ייעודי לכך, העומס צפוי | תשלום לפי מספר חיבורים והודעות, מגבלות פורמט נתונים, תלות בספק, העברה פנימה קשה |
היברידי נלקח כאשר אין טעם לכתוב מחדש את הליבה, אבל החיבורים החיים כבר כואבים: הזרם הראשי נשאר על הסטאק הקודם, האירועים עוברים במעגל נפרד על תהליכים.
הגישה אינה מתאימה אם הנתונים אינם אמורים לצאת מהמעגל שלכם, אם פרופיל העומס קופצני או שנדרש שליטה מלאה בהשהיה — אז נשקול תהליכים מתמידים כאפשרות העיקרית.
איך מתנהל פיתוח ב-Elixir: שלבי העבודה ההנדסית
סדר העבודה שלנו זהה גם לשירות חדש וגם להעברת שירות קיים ל-Elixir — משתנה רק ההיקף. שלבי ההטמעה בנויים כך שכל החלטה ארכיטקטונית נשענת על פרופיל עומס מדוד, ולא על הנחות לגביו.
- ביקורת עומס. מצלמים פרופיל: שיעור פעולות קריאה וכתיבה, משך הבקשות באזורים שונים, התנהגות בשעות שיא, מגבלות של מערכות חיצוניות. בפלט — מפת צווארי בקבוק ורשימה של מה שחייב לשרוד כשל.
- סכמת פיזור תהליכים. מפרקים את הדומיין לתהליכים מבודדים תחת סופרוויזרים: היכן להחזיק מצב בזיכרון, היכן במאגר משותף, היכן לפנות ישירות למסד הנתונים. כך נפילה של אזור אחד אינה גוררת את האחרים.
- קלאסטר ותורים. מגדירים קלאסטר מבוזר ועיבוד ברקע: נסיונות חוזרים, הגבלת מקביליות, הגנה מפני משימות כפולות, סדר ההודעות.
- בדיקה בכשלים. מפילים בכוונה צומת, קישור ותלות חיצונית, בודקים אם השירות קם מעצמו, כמה בקשות הולכות לאיבוד ובכמה זמן מתאושש השירות.
- קבלה לפי מדדים. משווים את התוצאה לספים שסוכמו לפני ההתחלה: שיעור הבקשות המוצלחות, זמן התגובה בשיא, צריכת זיכרון, מהירות עיבוד התור ברקע.
- מסירה לתפעול. מוסרים תיעוד, סכמות פיקוח, נהלי תורנות, מדדים והתראות מוגדרים, ומקיימים הדרכה לצוות הלקוח.
כל שלב מסתיים בארטיפקט שאפשר לבדוק ולדון בו — ולכן סכמת העמידות בכשלים והקבלה לפי מדדים אינן הופכות לפורמליות בסוף הפרויקט.
מה מוסרים לידיים: סכמות, נהלים ותיעוד
מסירת הפרויקט אינה ארכיון עם קוד מקור, אלא ערכה שלפיה מהנדס תורן מרים את הסביבה, מאתר את סיבת ההידרדרות ומשחזר את השירות בלי מחבר הקוד. את השלמות בודקים בפועל: פורסים מתקן בדיוק לפי התיעוד ומתעדים היכן היה צריך לנחש.
- תצורות סביבה: פרמטרי הקלאסטר, מגבלות חיבורים, פסקי זמן, רפליקציה, עבודה עם סודות — עם הסבר למה הערך הוא דווקא כזה.
- סכמות זרימת נתונים: מסלול הבקשה מהמאזן ועד למאגרים, תורים, משימות רקע ומקומות שבהם אירוע יכול ללכת לאיבוד או להשתכפל.
- סכמות עמידות בכשלים: התנהגות בנפילת צומת, באובדן מסד נתונים, באי-זמינות של שירות חיצוני, סדר המיתוג וזמן החזרה לפעילות.
- מדיניות גישה ותפקידים: מטריצת "תפקיד — פעולה — משאב", הרשאות ללוגים, שינוי תצורה, גישה למעגל הפרודקשן, רוטציית מפתחות.
- תיעוד הבקאנד: מבנה המודולים, חוזי ממשקים, מיגרציות נתונים, התנהגות באספקה חוזרת, גבולות עומס.
- נהלי תפעול: תורנות, תגובה לאותות ניטור, הסלמה, סדר שחרור גרסה וחזרה אחורה, עבודות מתוכננות.
- תוכנית התאוששות: תרחישים של תקלות אופייניות, נקודות גיבוי, זמן התאוששות יעד, בדיקה בתרגילים.
קייס: שיא פי חמישה בלי ניתוקים ובקשות אבודות
השירות קיבל זרם בקשות מכמה ערוצים — טופס באתר, אפליקציית מובייל, אינטגרציות שותפים. בשיא החיבורים נקרעו: הלקוח לא קיבל אישור קבלה ושלח את הבקשה שוב, וחלק מהפניות אבדו בין הקבלה לכתיבה.
שיעור הבקשות האבודות הגיע לכמה אחוזים, התגובה התארכה מעשיריות השנייה לכמה שניות, ואת הפערים המפעילים בדקו ידנית.
הקבלה הופרדה מהעיבוד: הבקשה נרשמת ביומן ומאושרת ללקוח מיד, בהמשך מטפל לוקח אותה, והפרץ נבלע בתור עם הגבלת קצב. שליחה חוזרת מזוהה לפי מפתח אידמפוטנטיות ואינה יוצרת כפילות. עמידות בכשלים נבנתה על פיקוח על תהליכים: צומת עיבוד שנפל מופעל מחדש מעצמו, ומצב הבקשה משוחזר מהיומן.
את הליבה עיבדנו מחדש ב-Elixir — מודל התהליכים הקלים מחזיק עשרות אלפי חיבורים במקביל בלי גידול בהשהיה.
מה קרה בשיא
| מדד | לפני | אחרי |
|---|---|---|
| בקשות אבודות | כמה אחוזים ביום שיא | 0 — הפנייה אושרה ונשמרת |
| השהיית תגובה | שניות, גדלה עם העומס | עשיריות השנייה, אינה תלויה בשיא |
| התאוששות אחרי כשל צומת | עשרות דקות, ידנית | עשרות שניות, אוטומטית |
ניתוחי תקלות יצאו מהשגרה: המפעילים עוסקים בבקשות, ולא בהשוואת פניות שנעלמו.
מה עושים אם חלק מהמערכת כבר עובד על סטאק אחר?
כן — אפשר להעביר חלק מהמערכת ל-Elixir בלי לעצור את המוצר, אבל את הגבול חותכים לא לפי שכבות, אלא לפי בעלות על נתונים. קודם מתעדים איזו ישות שייכת למי: לכל טבלה ולכל תרחיש כתיבה צריך להיות בדיוק שירות אחד כבעלים. האחרים פונים אליו דרך חוזה, ולא ישירות למסד הנתונים שלו. בלי התנאי הזה סטאק מעורב הופך במהרה לפערי נתונים שמחפשים אותם שבועות.
את הגבול מציבים היכן שאופי העומס משתנה: חיבורים ארוכים, זרמי אירועים, התראות, צ'אטים, תרחישי תשלום. חלק כזה מעבירים בשלמותו — להעביר חצי דומיין יקר יותר מאשר ההעברה עצמה. במפגש משאירים שכבה דקה של תאימות שמכירה את שני המעגלים ויודעת לנתב קריאות לכל כיוון.
העברת עומס הדרגתית נעשית בחלקים: השער שולח חלק מהתעבורה לשירות החדש, ואת השאר — לישן. קודם מצב צל: את הבקשה מעבדים שני המעגלים, את התגובות משווים, ופערים נבדקים לפני שהמשתמש רואה אותם. את החלק מגדילים לפי מדדים — שיעור שגיאות, השהיות, התנהגות בשיא, ולא לפי לוח שנה.
כתיבה כפולה אינה נעשית כלל: תמיד כותב בעלים אחד, והמעגל השני לומד על השינוי דרך אירוע. לנסיונות חוזרים ולניתוקים — מפתח אידמפוטנטיות וטבלת הודעות יוצאות טרנזקציונית, כדי שאירוע לא ילך לאיבוד בעת תקלה. כך שני המעגלים אינם רבים על אותם נתונים ואינם דורסים עריכות של האחר.
תאימות השירותים נשמרת בחוזים: סכמת הבקשות מנוהלת בגרסאות, שדות ישנים לא נמחקים כל עוד לפחות מעגל אחד קורא אותם. מתג החזרה נשאר עד לרגע שבו השירות החדש מחזיק את מלוא העומס באופן יציב. אז מכבים את הישן, משלימים את שאריות הנתונים ומסירים את המסלולים הכפולים.
ספקות טכניים: עמידות בכשלים, גיוס, תמיכה
הספקות הטכניים סביב בקאנד בזמן-אמת הם בדרך כלל אותם ספקות: איך המערכת מתנהגת בעת כשל של צומת, מי מתחזק את הקוד אחרי ההשקה ואיפה משיגים מהנדסים לסטאק צר. להלן — תשובות קצרות למה שאנחנו דנים בו לרוב לפני ההתחלה.
איך אתם בודקים את התנהגות המערכת בכשלים?
את הכשל משחזרים בכוונה: עוצרים צומת בקלאסטר תחת עומס, קוטעים את החיבור למסד הנתונים, מאפסים ערוצי websocket — ובודקים כמה סשנים התחברו מחדש בלי אובדן נתונים. פועל מודל הסופרוויזרים של Elixir: תהליך שנפל קם מחדש, ומצב הסשן משוחזר מהמאגר. בקבלה מוסרים מספרים: זמן ההתאוששות ושיעור ההודעות האבודות.
איך מחפשים מהנדסים לסטאק הזה?
השוק לענף הזה צר, ולהעמיד פנים שזה לא כך זה חסר טעם. ליבת הצוות היא שלנו, ומתרחבים באמצעות מעבר של מפתחים מסטאקים פונקציונליים אחרים: את התחביר תופסים מהר, זמן רב יותר לוקחת ההתרגלות לנתונים בלתי-משתנים ולאתחול מחדש במקום חריגות. המשימות הראשונות — מחוץ למסלול הקריטי, עם חונך וביקורת קוד חובה.
איך בנויה התמיכה אחרי ההשקה?
משמרת תורנות לפי לוח זמנים, ערוצי התראות ונוהל הסלמה. נראות נבנית על מבשרי כשל, ולא על עצם הנפילה: גידול בתורי ההודעות, השהיית עיבוד, תדירות אתחולים מחדש של תהליכים. שינויים יוצאים בחלון של עומס נמוך, ושחרור גרסה חוזר אחורה בפקודה אחת.
תארו את המשימה — נחזור עם הערכה, תוכנית ורשימת בדיקות שנכלול בקבלה.נדון במשימה: נפרק את פרופיל העומס והסיכונים
לפני שאנחנו נוקבים בלוחות זמנים, אנחנו מסתכלים על פרופיל העומס: כמה חיבורים במקביל מחזיקה המערכת, איך התעבורה מתחלקת בין בקשות רגילות לערוצים מתמידים, והיכן נוצרים שיאים. בלי המספרים האלה ההערכה ותוכנית העבודה הופכות לניחוש, וסיכוני ההטמעה צפים כבר בשחרור הגרסה הראשון. כדי לדון במשימה באופן ענייני, די בחמישה פרטים:
- פרופיל עומס — ערכי שיא של חיבורים במקביל ובקשות בשנייה, שיעור תעבורת זמן-אמת, פרצים יומיים ועונתיים, דרישות לזמן תגובה.
- הסטאק הנוכחי — על מה עובד הבקאנד, הענף העדכני של OTP, ספריות וברוקרים של הודעות, מה כבר הועבר ומה עדיין נשען על קוד ישן.
- נתונים ומאגרים — סכמות, נפחים, דרישות עקביות, טבלאות חמות, נוהל גיבויים ותרחיש התאוששות שנבדק.
- תרחישי כשל — מה כבר נפל ואיך התאוששו, אילו צמתים בלי גיבוי, היכן הודעות הולכות לאיבוד, כמה מהר המערכת מידרדרת בעומס יתר.
- תשתית ותהליכים — היכן פורסים את השירות, איך בנויים שחרור גרסאות וחזרה אחורה, האם יש נראות, מי מקבל החלטות לפי עדיפויות.
לפי התשובות האלה אנחנו מרכיבים מפת סיכוני הטמעה: היכן נדרש מעגל עומס נפרד, מה נכתב מחדש בשלמותו ומה מועבר בשלבים. אחרי זה חוזרים עם הערכה ותוכנית עבודה — עם שלבים, קריטריוני קבלה ונקודות חזרה אחורה.







