מדוע מונוליט שובר את אפליקציית המובייל כאשר העומס גדל
ה-backend המונוליטי של אפליקציית מובייל מחזיק מעמד עד לסף מסוים: כל עוד יש מעט בקשות והצוות אחד, הוא צפוי וזול לתחזוקה. הבעיה היא שלקוח המובייל הוא הצרכן התובעני ביותר של ה-API.
הוא פועל מרשתות לא יציבות, שולח מקבצי בקשות בעת פתיחת מסך ומתעדכן בגלים כאשר משתמשים מתקינים גרסה חדשה. ברגע שהעומס על אחד מהמקטעים גדל, השירות כולו מדרדר, ולא פונקציה בודדת.
הראשון שנכנע הוא זמן התגובה של ה-API. אנליטיקה כבדה, ייצוא פידים או חישוב מחדש של המלצות מתבצעים באותו תהליך שבו מתבצעת הכניסה לאפליקציה — ובקשות לאימות עומדות בתור משותף אחריהם.
לאחר מכן מופעל אפקט שרשרת: מקטע איטי מחזיק חיבורים, מאגר התהליכים מתכלה, ומפסיקים לעבוד דווקא אותם endpoints שאין להם קשר לעומס היתר. המשתמש רואה לא "המדור לא עובד", אלא "האפליקציה לא נפתחת", ועוזב לפני שאתם מספיקים להגיב.
צוואר הבקבוק השני הוא מחזור השחרור. כל תיקון, אפילו במודול אחד, מחייב בנייה מחדש והפצה מחודשת של כל המונוליט, ולכן השינויים מצטברים בענפים משותפים ויוצאים במקבצים גדולים. שגיאה במקבץ כזה מחזירה אחורה את כל השיפורים, ולא אחד. צוותים מתחילים לתאם חלונות הפצה ביניהם, ומהירות האספקה יורדת בדיוק כאשר השוק דורש להאיץ אותה.
ארכיטקטורת מיקרו-שירותים של אפליקציית מובייל פותרת זאת אחרת: מקטעים קריטיים — כניסה, תשלום, פיד — חיים כשירותים נפרדים עם משאבים משלהם וקצב הפצה משלהם. עומס יתר על ההמלצות כבר לא מפיל את האימות, ותקלה מתוחמת למעגל אחד במקום לכל ה-backend.
מפרקים כיצד זה נסגר בפועל: היכן עובר הגבול בין השירותים, כיצד מוגדרת בידוד תקלות ומה קורה למחזור השחרור.
ארכיטקטורת מיקרו-שירותים של אפליקציית מובייל: מה משתנה
ארכיטקטורת מיקרו-שירותים לאפליקציה מובייל: מה משתנה
המעבר לארכיטקטורת מיקרו-שירותים באפליקציית מובייל מתחיל בפירוק השירותים לפי גבולות היכולות העסקיות: קטלוג, הזמנות, תשלומים, פרופיל, התראות. כל שירות מקבל סכימת נתונים ומחזור שחרור משלו — תיקון לוגיקת המשלוחים כבר אינו מחייב בנייה מחדש ובדיקה חוזרת של כל המונוליט. בפועל הדבר מקצר את הזמן מקומיט לפרודקשן משעות לדקות ומסיר את תור השחרורים בין הצוותים.
בידוד כשלים במקום קריסה משותפת
במונוליט, קריסה של מטפל אחד גוררת את כל התהליך: הלקוח רואה שגיאה בכל המסכים בבת אחת. במיקרו-שירותים, כשל בתשלומים מתוחם — הקטלוג, הסל והפרופיל ממשיכים להגיב, והאפליקציה מציגה מצב מובן במקום מסך לבן.
Circuit breaker בשער חוסם את השירות המתדרדר בתוך שברירי שנייה, ותור המשלוח החוזר מחזיר בקשות שאבדו כאשר השירות חזר לפעול. שיעור הסשנים המוצלחים בזמן התדרדרות חלקית נשאר גבוה, ולא צונח לאפס.
שחרורים עצמאיים ותאימות לאחור
כל שירות מנוהל בגרסאות בנפרד: לקוח המובייל ממשיך לעבוד עם גרסה ישנה של החוזה, בעוד שהחדשה נפרסת על חלק מהמשתמשים. זה מסיר את הכאב העיקרי של שחרורים — עדכון כפוי של האפליקציה בגלל תיקון בצד השרת.
החוזים מקובעים בסכימה, שינויים לא תואמים עוברים דרך ניהול גרסאות, ולא דרך פריסה בו-זמנית של הלקוח והשרת.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| מונוליט עם מודולים | שלב מוקדם, צוות אחד, גבולות לא ברורים | מחזור שחרור משותף, כשל של מודול אחד מפיל הכל |
| מיקרו-שירותים עם שער משותף | מספר צוותים, קצב שינויים שונה | נדרשים נראות וחוזים, תפעול יקר יותר |
| היברידי: ליבה + שירותים | הוצאה הדרגתית של צווארי בקבוק | ניתוב כפול זמני של בקשות |
עבור תיעוד מעקב מקצה לקצה של בקשה בין שירותים אנו משתמשים בטרייסינג מבוזר — OpenTelemetry: ניתן לראות באיזה קטע בשרשרת גדל זמן ההשהיה, ללא הצלבה ידנית של לוגים.
מה זה נותן במדדים
לאחר הפירוק, זמן ההתאוששות מכשל של שירות בודד נמדד בדקות, ולא בעצירה של כל המוצר. הצוותים משחררים באופן עצמאי, שיעור השינויים שמגיעים לפרודקשן ללא סנכרון ידני עולה, ורדיוס הפגיעה של פגם אחד מוגבל לתחום אחד.
שלבי יישום ארכיטקטורת מיקרו-שירותים
המעבר למיקרו-שירותים אינו "לכתוב מחדש את הבקאנד", אלא החלפה מבוקרת של המונוליט, שבה כל צעד ניתן לבדיקה ולהפיכה. מתחילים במיפוי המצב הקיים: אילו מודולים מחוברים ישירות, היכן נמצאים טבלאות וטרנזקציות משותפות, ואילו קריאות קריטיות ללקוח המובייל.
מתוצאות הביקורת רואים אילו שירותים ניתן להפריד ללא כאב, ואילו גוררים אחריהם ארגון מחדש של הנתונים — זה קובע את סדר העבודה ואת גבולות הגרסה הראשונה.
- ביקורת הבקאנד. משרטטים מפת תלויות, מבצעים פרופיילינג של עומס לפי נקודות קצה, ומתעדים צווארי בקבוק ונקודות כשל. בפלט — רשימת דומיינים מועמדים והערכת סיכונים לכל אחד.
- תכנון השירותים. מגדירים את גבולות הדומיינים, החוזים ביניהם, סכימת הנתונים ואסטרטגיית המיגרציה. בנפרד מתכננים שכבה ללקוח המובייל: אגרגציה של תגובות, גרסאות API, כדי שגרסאות ישנות של האפליקציה לא יישברו בעת עדכון השירותים.
- הקמת התשתית. פורסים קונטיינריזציה, אורקסטרציה, ניתוב תעבורה, לוגים ומטריקות מרוכזים. מגדירים אוטוסקיילינג ובדיקות health, כך שכשל של שירות אחד לא יפיל את האפליקציה כולה.
- הוצאה הדרגתית של הלוגיקה. מעבירים דומיינים אחד אחד, עם עבודה במקביל של המתאר הישן והחדש והחלפת תעבורה. כל צעד ניתן לחזרה ללא השבתה.
- קבלת הפתרון. מריצים בדיקות עומס ועמידות בכשלים, משווים מטריקות לבסיס, ובודקים את תקינות הנתונים לאחר המיגרציה.
- מסירה ותיעוד. מוסרים סכימות שירותים, קונפיגורציות, נהלי פריסה וניטור, ומכשירים את צוות הלקוח.
סדר זה נותן תוצאה צפויה: זמן התגובה תחת עומס מפסיק להידרדר, ותקלה בדומיין אחד מתוחמת במקום קריסה של כל הבקאנד. תארו את המשימה — נחזור עם הערכה ותוכנית לפי המתאר שלכם.
במה שונה ארכיטקטורת מיקרו-שירותים ממונולית מודולרית?
מונולית מודולרית היא אפליקציה אחת, שבתוכה הקוד מחולק למודולים מבודדים עם גבולות ברורים ואיסור על פניות ישירות ביניהם. הבנייה והפריסה מתבצעות כארטיפקט אחד, הטרנזקציות נשארות מקומיות, והצימוד בין המודולים נשלט ברמת הבנייה.
ארכיטקטורת מיקרו-שירותים של אפליקציית מובייל מפזרת את אותם מודולים לתהליכים נפרדים עם מאגרי נתונים וחוזי רשת משלהם — ובדיוק כאן משתנה הכול: עלות הבעלות, מהירות השחרורים ומחיר הטעות.
מהירות השחרורים. במונולית פונקציה חדשה יוצאת בשחרור משותף: עד שהיא לא תעבור רגרסיה של כל האפליקציה, היא לא תצא. במיקרו-שירותים הצוות מפיץ את השירות שלו באופן עצמאי, אבל משלם על כך בחוזים — שינוי שובר ב-API של שירות אחד עוצר את השחרורים של כל מי שתלוי בו. הרווח במהירות אמיתי רק עם ניהול גרסאות של חוזים ותאימות לאחור כברירת מחדל.
עלות הבעלות. מונולית זולה יותר בתפעול: ריצה אחת, מסד נתונים אחד, ניטור אחד. מיקרו-שירותים מוסיפים טרנזקציות מבוזרות, ניסיונות חוזרים, אידמפוטנטיות, תיעוד מעקב בין שירותים ומעגל נראות נפרד. העלויות האלה אינן חד-פעמיות — הן נכללות בכל פונקציה חדשה.
מורכבות התפעול. כשל במונולית הוא מקומי ומשוחזר על סטנד אחד. במיקרו-שירותים תקלה של צומת אחד מתבטאת במפל: פסקי זמן, חזרות, הצפת תורים. בלי circuit breaker ומגבלות על מקביליות מפל כזה מכבה את כל הבקאנד.
מסקנה מעשית: מונולית מודולרית מנצחת כל עוד יש מעט צוותים והדומיין עוד משתנה. המעבר למיקרו-שירותים מוצדק כאשר המודולים כבר יציבים, ומחזורי השחרור של הצוותים החלו להפריע זה לזה.
מקרה בוחן: כיצד קיצרנו את זמן השחזור לאחר תקלה
הלקוח הגיע עם מונוליט: שירות אחד שירת את הקטלוג, ההזמנות, התשלומים וההתראות. כל שינוי במודול התשלומים גרר אחריו בנייה מחדש והפעלה מחדש של כל האפליקציה, ונפילה של צומת אחד הפילה את כל הבקאנד. הלקוח המובייל ברגעים כאלה קיבל שגיאות בכל המסכים בבת אחת — מהפיד ועד הסל. זמן השחזור הממוצע לאחר תקלה עמד על רמה של כמה שעות, וזאת מבלי לקחת בחשבון את הזמן לאיתור הסיבה.
מה שינינו
חתכנו את המונוליט לפי גבולות תחומי העניין: קטלוג, הזמנות, תשלומים, התראות ופרופיל הפכו לשירותים נפרדים עם מאגרי הנתונים שלהם. הלקוח המובייל מתקשר איתם דרך שער אחיד שמנתב בקשות ומחזיק את ההרשאה.
המסלול הקריטי «סל → תשלום» הועבר לשירות נפרד עם מאגר משאבים משלו — אי-הזמינות שלו כבר לא מפילה את הקטלוג.
בנפרד סגרנו סוג כשל שהיה קודם הנפוץ ביותר: עומס יתר בזמן עומס שיא. תור ההודעות מחליק את הקפיצות, וכל שירות מתרחב באופן עצמאי — למבצע מכירות מרימים רק את התשלומים וההזמנות, בלי לגעת בשאר.
מדדים לפני ואחרי
| מדד | לפני | אחרי |
|---|---|---|
| זמן שחזור ממוצע | שעות | דקות |
| שיעור הכשלים בגלל עומס יתר | הסיבה העיקרית להשבתות | מקרים בודדים |
| אזור הפגיעה בזמן תקלה | כל האפליקציה | שירות אחד |
| הוצאת עדכון למודול | בנייה מחדש של כל הבקאנד | בנייה של שירות אחד |
הקיצור של זמן השחזור לא הושג רק על ידי פיצול הקוד. הגדרנו בדיקות חיות לכל שירות, הפעלה מחדש אוטומטית של מופעים שנפלו ואיסוף מדדים עם תיעוד בקשות — כעת סיבת התקלה נראית לאורך שרשרת הקריאות, ולא מתגלית על ידי מעבר על הלוגים.
Kubernetes לקח על עצמו את התזמור: הפעלה מחדש, פיזור עומס ועדכון ללא השבתה. החלק ההנדסי — בכך לחתוך נכון את השירותים ולא להפוך את הארכיטקטורה המיקרושירותית של אפליקציית המובייל למונוליט מבוזר.
ספקות לפני ההתחלה: תאימות, מיגרציה, חזרה לאחור
המעבר ממונוליט לארכיטקטורת מיקרו-שירותים של אפליקציית מובייל כמעט אף פעם לא נעשה "ביום אחד" — לכן השאלה הראשונה בהתחלה היא בדרך כלל על מיגרציה ללא השבתה.
אנחנו מפרקים את המעבר לשלבים: תחילה מוציאים שירות לא קריטי, מריצים אותו במקביל למונוליט ומשווים תגובות, ורק כשהפערים אפסיים — מעבירים את התעבורה. גישה כזו מאפשרת חזרה לאחור בכל שלב: כל עוד הלוגיקה הישנה חיה, לחזור אחורה זה להחליף מסלול, ולא לשחזר מסד נתונים מגיבוי.
מה אם חלק מהנתונים כבר נצברו בסכימה הישנה? את המיגרציה אנחנו עושים הפיכה: כותבים לאחסון החדש וממשיכים לשכפל לישן, עד שאנחנו משוכנעים בשלמות הנתונים. בנפרד אנחנו בודקים שלקוחות מובייל מגרסאות ישנות, שעדיין קיימות במכשירים, ממשיכים לעבוד — את חוזה ה-API מרחיבים, ולא שוברים.
מה בנוגע להרשאות ואבטחה? השירותים מתקשרים דרך רשת פנימית, החוצה פתוח רק השער, כל שירות מקבל פרטי זיהוי משלו עם הרשאות מינימליות. סודות לא נמצאים בקוד — הם באחסון ומתחלפים ברוטציה.
האם אפשר לחזור לאחור מגרסה, אם הבעיה צצה בפרודקשן?
כן. כל שירות מקבל גרסאות בנפרד, לכן החזרה לאחור נוגעת רק בו, ולא בכל האפליקציה. את סכימת הנתונים אנחנו משנים כך שהגרסה הקודמת של השירות תמשיך לעבוד איתה — ואז חזרה לגרסה הקודמת לא דורשת מיגרציה אחורה.
לידי הלקוח מתקבלים:
- סכימת השירותים ואזורי האחריות שלהם;
- תיאור חוזי ה-API וכללי הגרסאות;
- קונפיגורציות הסביבות ומדיניות ההרשאות;
- נוהל מיגרציה ללא השבתה ותוכנית חזרה לאחור;
- ניטור והתראות מוגדרים לכל שירות;
- תיעוד לפריסה ולתפעול.
תארו את הארכיטקטורה הנוכחית שלכם ואת המגבלות — נחזור עם הערכה ותוכנית מעבר.
לוחות זמנים ליישום ארכיטקטורת מיקרו-שירותים לפי שלבים
המעבר ממונוליט למיקרו-שירותים אינו נעשה בריליס אחד: כל עוד המתאר החדש לא קיבל את עומס הייצור, הישן ממשיך לשרת את המשתמשים. לכן אנו בונים את תוכנית-הזמנים כך שכל שלב יניב תוצאה עובדת ויותיר אפשרות לחזרה לאחור.
להלן — לוחות זמנים משוערים למוצר מובייל טיפוסי עם קהל פעיל; בפרויקט ספציפי הם משתנים בהתאם למספר האינטגרציות ולהיקף הנתונים המצטברים.
לוחות הזמנים תלויים בשלושה דברים: כמה אינטגרציות חיצוניות קשורות למונוליט, האם קיימת צוות ייעודי למיגרציה, וכמה מחמירות הדרישות לרציפות השירות. בחלון זמינות נוקשה גלי ההעברה מתארכים, אך המוצר אינו יוצא להשבתה. אנו קובעים נקודות בקרה לפי שלבים, כדי שתראו התקדמות לא לפי דוחות, אלא לפי שירותים עובדים בייצור.
תארו את המוצר שלכם ואת העומס הנוכחי — נחזור עם הערכה ותוכנית לפי שלבים.







