במאמר הקודם שלום, AWS Lambda! הצגנו את למבדה, ובעיקר כיצד ה main successful flow עובד.
לא הספקנו כמעט ולהיכנס לעומק / להתנהגויות השונות במודל של למבדה.
זה מה שננסה לעשות בפוסט הזה.
כאשר מתבצעת הפעלה (invocation) ראשונה של הפונקציה שלנו, למבדה מפעילה "Container" שבו תרוץ הפונקציה.
מהו ה- container הזה? אין תיעוד מדויק, אך מדובר בסופו של דבר בסביבת ההרצה של הפונקציה, ע"פ ה settings שהגדרנו.
• אם הפונקציה רצה עשר פעמים בדקה - אזי זו הפעלה אחת לכל גרסה / עותק חדש של פונקציה (הסבר בהמשך). הבדל זניח.
• אם הפונקציה רצה פעם בעשר דקות, וזמן ה execution שלה קצר - ה cold start עשוי להכפיל את העלויות.
הערה: העניין הכספי כנראה לא יגרום לכם לעזוב את השימוש בלמבדה, מטרת הדיון היא בעיקר להסביר את אופן העבודה של למבדה.
זמן מה לאחר שהפונקציה הופעלה, למבדה תשמור את ה container חי. הזמן המדויק כנראה תלוי בעומס על השרתים של למבדה, אבל הוא יכול לנוע בין שניות ספורות - לעשרות דקות.
• אין קונטיינר זמין - למשל "הרגו" אותו לא מזמן...
• הבקשה הגיעה ל Availability Zone A, אבל הקונטיינר נמצא ב Availability Zone אחר.
• יש מספר הרצות מקביל של הפונקציה: אם מספר ההרצות בשניה עלה מ 10 ל 100, למבדה תתחיל להריץ עוד ועוד קונטיינרים במקביל - עד שהצורך יסופק.
כלומר: ייתכן ויש כבר 6 קונטיינרים עובדים, אך במקום לחכות שקונטיינר יתפנה - למבדה החליטה להפעיל קונטיינר שביעי.
• בעת כתיבת הפוסט, אמזון לא תריץ יותר מ 1000 קונטיינרים במקביל - אלא אם ביקשתם.
• למבדה תקבע את כמות המקביליות כתלות במקור ה event:
• אם מדובר בהודעות ב Queue (או "stream-based-event") - המקביליות תהיה ע"פ כמות ה shards של queue.
• כאשר מדובר בקריאות ישירות (למשל HTTP) - המקביליות תהיה מספר הקריאות בשניה * כמה שניות לוקח לפונקציה לרוץ בממוצע.
• בכל מקרה, למבדה היא לא קסם. גם אם יש spike "מטורף" - אמזון לא תפתח יותר מ 500 קונטיינרים חדשים בדקה. אפשר לקרוא עוד פרטים על המדיניות כאן.
• סיבה אחרת לא מתועדת / שאני לא מכיר.
• כמובן: הקוד שלכם. אם אתם משתמשים ב ORM, או ספריות שדורשות אתחול משמעותי - הזמן הזה ישולם ב Cold Start.
• כמות הזיכרון שהוקצתה לפונקציה (=> CPU). יותר זיכרון - אתחול מהיר יותר.
• כמות הקוד שנטען ב ZIP, ספריות וכיוצ"ב: פחות קוד - אתחול מהיר יותר.
• קוד ב node.js ופייטון יאותחל מהר יותר מקוד ג'אווה או #C.
• network latency - עוד כ 100ms או יותר (?), אלא אם אתם קוראים לפונקציה באותו ה region - ואז ה latency הוא מילישניות בודדות.
• זמני הריצה של ה API Gateway - כ 30 עד 150 מילישניות (ע"פ נתונים של New Relic).
• הקמת חיבור מאובטח (TLS/SSL) - עשוי לארוך עוד 50-200 מילישניות.
דיי פופולארי היום לדבר על טכניקות ל"שמירת ה container חי" והימנעות מ Cold Start.
בשקלול של כל הרכיבים, נראה ש"הימנעות מ Cold Start" היא התעסקות דיי מיותרת - המשקל של הרכיבים האחרים הוא גדול יותר. לפחות, ברוב הגדול של המקרים.
אם לפונקציה שלכם יש קצב הפעלה בינוני עד גבוה, רבות מההפעלות יתרחשו על container קיים.
זה אומר ש:
• הגדרות "סטטיות" שנעשו מחוץ ל handler function - עדיין תקפות: חיבור לבסיסי נתונים / רדיס וכו'.
• Cache ששמרתם בזיכרון - עדיין מלא.
• דברים שנרשמו לדיסק עדיין שם (יש לכם עד חצי ג'יגה אכסון בתיקיה tmp/)
זה פוטנציאל לשיפור ביצועים משמעותי.
כמובן, שלעולם אי אפשר להסתמך על container reuse: אם הוא יצא - אפשר לנצל אותו, אם לא אז לא. עליכם לכתוב את הקוד בהתאם.
כמו כן, חשוב להבין את התנהגות ה cache בצורה נכונה: אם יש מקביליות גבוהה (concurrent invocation), אזי יש כמה containers שכל אחד עם cache ב state שונה. חשוב לקחת את זה בחשבון. אם חשוב לכם cache אחיד - פשוט השתמשו ברדיס.
Throttling and retries
כאשר יש הרבה יותר בקשות הרצה להפעלה של פונקציות מאשר containers - למבדה תגדיל את מספר ה containers הרצים.
כאשר יש מעט יותר בקשות הרצה להפעלה של פונקציות מאשר containers - למבדה תבצע throttling. כלומר: תמתין עם הבקשה עוד קצת - ואז תפעיל אותה על גבי ה containers הקיימים.
• עבור מקורות מסוג Queue (רשמית: "stream-based events") - למבדה תבצע retry לעד. עד שהבקשות מטופלות.
• עבור מקור קריאה אסינכרונית - למבדה תבצע retry בטווח של עד 6 שעות מרגע הקריאה, עם המתנה בין ניסיון לניסיון, מה שאמור ליצר סיכוי נמוך מאוד למצב של הודעה שלא טופלה.
• עבור קריאות סינכרוניות (ה event source ממתין לתשובה) - למבדה תחזיר לאפליקציה HTTP status code של 429 - ותצפה שהאפליקציה תבצע retry בעצמה.
Throttling הוא מצב תקין וצפוי בלמבדה - מצב שעליכם לקחת בחשבון! הוא יקרה.
• Synchronous Exception - כזו שתוצאתה חוזרת למי שקרא לפונקציה.
• Asynchronous Exception - כזו שתוצאתה מגיעה ל cloudwatch. לקוח הפונקציה לא מודע לדבר.
• עבור מקורות מסוג Queue (רשמית: "stream-based events") - למבדה תבצע retry כל עוד ה data מבחינת ה queue הוא בתוקף.
• עבור מקור קריאה אסינכרונית - למבדה תבצע retry עוד שתי פעמים, בד"כ בהפרש של כדקה, ואז תשלח את ההודעה ל Dead Letter Queue שהוגדר על ידכם על גבי SNS או SQA.
• ליתר דיוק בדיקות הראו שאכן ב 99% מהפעמים יהיו 2 retries, אבל לעתים רחוקות יהיה רק retry אחד - ולפעמים גם יבוצעו עד שש retries. מקור.
• עבור קריאות סינכרוניות (ה event source ממתין לתשובה) - הודעת השגיאה תחזור לאפליקציה, ובאחריותה לבצע retries.
חשוב לציין מגבלה שלא כתובה פה: בעת כתיבת הפוסט, ל API Gateway יש timeout קבוע של 30 שניות. אם יש לכם פונקציה שזמן הריצה שלה ארוך יותר - API Gateway יבצע timeout, בכל זאת.
בשלב הזה אתם אמורים להבין כמעט את כל המדדים שמסופקים ב Monitoring של למבדה:
המדד היחידי שלא הוסבר הוא ה iterator age שרלוונטי רק למקורות מסוג stream-based events.
הוא מציין כמה זמן ארך מרגע שפונקציית הלמבדה שלנו קיבלה batch של הודעות, ועד שסיימה לטפל בהודעה האחרונה. כלומר: מה ה latency המרבי של טיפול בהודעות ב batch, שהגיעו ממקור שכזה.
שווה להזכיר את שירות ה Lambda@Edge ששוחרר לאחרונה, שירות המאפשר להריץ פונקציות למבדה (Nodejs בלבד, כרגע) על ה PoPs (כלומר: Point of Presence) של CloudFront.
לאמזון יש כ 14 regions בעולם עליהם אפשר להריץ את כל שירותי ה AWS, אבל יש לה קרוב ל 100 מיקומים בהם יש לה נוכחות - עבור שירות ה CDN שלה, הרי הוא CloudFront.
עבור פעולות פשוטות למדי, אם תוכלו להריץ את הקוד קרוב מאוד ללקוח שלכם - תוכלו לספק לו תשובה מהירה ביותר: ה latency ל"קוד" שלכם עשוי להיות עשרות מילי-שניות, ולא מאות מילי-שינות.
למבדה_על_הקצה (Lambda@Edge) תנהל עבורכם את שכפול הקוד לרחבי העולם - ותדאג שהלקוחות שלכם, יקבלו זמני תגובה משופרים.
את פונקציית הלמבדה_על_הקצה שלכם, אפשר לחבר לארבע נקודות-חיבור שונות בתהליך של cloudfront (בתרשים: החצים הירוקים והכתומים), ולבצע שינוי על המשאב שהתבקש.
כמובן שבשירות כזה מאוד חשוב לספק זמני תגובה מהירים מאוד - מה שלא תמיד מתרחש בצמד למבדה + API Gateway.
ה API Gateway הוא מחוץ לתמונה, יצירת ה HTTP connection עם TLS/SSL - כבר מתבצע ע"י cloudfront, ולכן הרכיב היחידי שעלול לגרום לעיכובים (ולייתר את היתרון שבריצה על PoP) הוא זמן האתחול של ה container של למבדה. לא סתם התחילו ב nodejs כסביבת-ריצה.
ללמבדה_על_הקצה - יש מגבלות משלה (מקסימום 128MB זיכרון, זמני ריצה קצרים יותר, אין גישה לשירותים אחרים של אמזון, וכו')
שירותים נוסח למבדה_על_הקצה כבר היו קיימים (למשל: PubNub Blocks) - אבל לאמזון יש יתרון מובנה מול מתחרים קטנים יותר.
יהיה מעניין!
סיכום
המאמר הקודם באמת היה מאוד מקדמי / בסיסי - ונראה לי שהצלחנו לסגור את הפער ולספק היכרות מספיק מעמיקה עם שירות הלמבדה - ויחסית בקלילות.
----
לינקים רלוונטיים
במאמר הקודם שלום, AWS Lambda! הצגנו את למבדה, ובעיקר כיצד ה main successful flow עובד.
לא הספקנו כמעט ולהיכנס לעומק / להתנהגויות השונות במודל של למבדה.
זה מה שננסה לעשות בפוסט הזה.
כאשר מתבצעת הפעלה (invocation) ראשונה של הפונקציה שלנו, למבדה מפעילה "Container" שבו תרוץ הפונקציה.
מהו ה- container הזה? אין תיעוד מדויק, אך מדובר בסופו של דבר בסביבת ההרצה של הפונקציה, ע"פ ה settings שהגדרנו.
• אם הפונקציה רצה עשר פעמים בדקה - אזי זו הפעלה אחת לכל גרסה / עותק חדש של פונקציה (הסבר בהמשך). הבדל זניח.
• אם הפונקציה רצה פעם בעשר דקות, וזמן ה execution שלה קצר - ה cold start עשוי להכפיל את העלויות.
הערה: העניין הכספי כנראה לא יגרום לכם לעזוב את השימוש בלמבדה, מטרת הדיון היא בעיקר להסביר את אופן העבודה של למבדה.
זמן מה לאחר שהפונקציה הופעלה, למבדה תשמור את ה container חי. הזמן המדויק כנראה תלוי בעומס על השרתים של למבדה, אבל הוא יכול לנוע בין שניות ספורות - לעשרות דקות.
• אין קונטיינר זמין - למשל "הרגו" אותו לא מזמן...
• הבקשה הגיעה ל Availability Zone A, אבל הקונטיינר נמצא ב Availability Zone אחר.
• יש מספר הרצות מקביל של הפונקציה: אם מספר ההרצות בשניה עלה מ 10 ל 100, למבדה תתחיל להריץ עוד ועוד קונטיינרים במקביל - עד שהצורך יסופק.
כלומר: ייתכן ויש כבר 6 קונטיינרים עובדים, אך במקום לחכות שקונטיינר יתפנה - למבדה החליטה להפעיל קונטיינר שביעי.
• בעת כתיבת הפוסט, אמזון לא תריץ יותר מ 1000 קונטיינרים במקביל - אלא אם ביקשתם.
• למבדה תקבע את כמות המקביליות כתלות במקור ה event:
• אם מדובר בהודעות ב Queue (או "stream-based-event") - המקביליות תהיה ע"פ כמות ה shards של queue.
• כאשר מדובר בקריאות ישירות (למשל HTTP) - המקביליות תהיה מספר הקריאות בשניה * כמה שניות לוקח לפונקציה לרוץ בממוצע.
• בכל מקרה, למבדה היא לא קסם. גם אם יש spike "מטורף" - אמזון לא תפתח יותר מ 500 קונטיינרים חדשים בדקה. אפשר לקרוא עוד פרטים על המדיניות כאן.
• סיבה אחרת לא מתועדת / שאני לא מכיר.
• כמובן: הקוד שלכם. אם אתם משתמשים ב ORM, או ספריות שדורשות אתחול משמעותי - הזמן הזה ישולם ב Cold Start.
• כמות הזיכרון שהוקצתה לפונקציה (=> CPU). יותר זיכרון - אתחול מהיר יותר.
• כמות הקוד שנטען ב ZIP, ספריות וכיוצ"ב: פחות קוד - אתחול מהיר יותר.
• קוד ב node.js ופייטון יאותחל מהר יותר מקוד ג'אווה או #C.
• network latency - עוד כ 100ms או יותר (?), אלא אם אתם קוראים לפונקציה באותו ה region - ואז ה latency הוא מילישניות בודדות.
• זמני הריצה של ה API Gateway - כ 30 עד 150 מילישניות (ע"פ נתונים של New Relic).
• הקמת חיבור מאובטח (TLS/SSL) - עשוי לארוך עוד 50-200 מילישניות.
דיי פופולארי היום לדבר על טכניקות ל"שמירת ה container חי" והימנעות מ Cold Start.
בשקלול של כל הרכיבים, נראה ש"הימנעות מ Cold Start" היא התעסקות דיי מיותרת - המשקל של הרכיבים האחרים הוא גדול יותר. לפחות, ברוב הגדול של המקרים.
אם לפונקציה שלכם יש קצב הפעלה בינוני עד גבוה, רבות מההפעלות יתרחשו על container קיים.
זה אומר ש:
• הגדרות "סטטיות" שנעשו מחוץ ל handler function - עדיין תקפות: חיבור לבסיסי נתונים / רדיס וכו'.
• Cache ששמרתם בזיכרון - עדיין מלא.
• דברים שנרשמו לדיסק עדיין שם (יש לכם עד חצי ג'יגה אכסון בתיקיה tmp/)
זה פוטנציאל לשיפור ביצועים משמעותי.
כמובן, שלעולם אי אפשר להסתמך על container reuse: אם הוא יצא - אפשר לנצל אותו, אם לא אז לא. עליכם לכתוב את הקוד בהתאם.
כמו כן, חשוב להבין את התנהגות ה cache בצורה נכונה: אם יש מקביליות גבוהה (concurrent invocation), אזי יש כמה containers שכל אחד עם cache ב state שונה. חשוב לקחת את זה בחשבון. אם חשוב לכם cache אחיד - פשוט השתמשו ברדיס.
Throttling and retries
כאשר יש הרבה יותר בקשות הרצה להפעלה של פונקציות מאשר containers - למבדה תגדיל את מספר ה containers הרצים.
כאשר יש מעט יותר בקשות הרצה להפעלה של פונקציות מאשר containers - למבדה תבצע throttling. כלומר: תמתין עם הבקשה עוד קצת - ואז תפעיל אותה על גבי ה containers הקיימים.
• עבור מקורות מסוג Queue (רשמית: "stream-based events") - למבדה תבצע retry לעד. עד שהבקשות מטופלות.
• עבור מקור קריאה אסינכרונית - למבדה תבצע retry בטווח של עד 6 שעות מרגע הקריאה, עם המתנה בין ניסיון לניסיון, מה שאמור ליצר סיכוי נמוך מאוד למצב של הודעה שלא טופלה.
• עבור קריאות סינכרוניות (ה event source ממתין לתשובה) - למבדה תחזיר לאפליקציה HTTP status code של 429 - ותצפה שהאפליקציה תבצע retry בעצמה.
Throttling הוא מצב תקין וצפוי בלמבדה - מצב שעליכם לקחת בחשבון! הוא יקרה.
• Synchronous Exception - כזו שתוצאתה חוזרת למי שקרא לפונקציה.
• Asynchronous Exception - כזו שתוצאתה מגיעה ל cloudwatch. לקוח הפונקציה לא מודע לדבר.
• עבור מקורות מסוג Queue (רשמית: "stream-based events") - למבדה תבצע retry כל עוד ה data מבחינת ה queue הוא בתוקף.
• עבור מקור קריאה אסינכרונית - למבדה תבצע retry עוד שתי פעמים, בד"כ בהפרש של כדקה, ואז תשלח את ההודעה ל Dead Letter Queue שהוגדר על ידכם על גבי SNS או SQA.
• ליתר דיוק בדיקות הראו שאכן ב 99% מהפעמים יהיו 2 retries, אבל לעתים רחוקות יהיה רק retry אחד - ולפעמים גם יבוצעו עד שש retries. מקור.
• עבור קריאות סינכרוניות (ה event source ממתין לתשובה) - הודעת השגיאה תחזור לאפליקציה, ובאחריותה לבצע retries.
חשוב לציין מגבלה שלא כתובה פה: בעת כתיבת הפוסט, ל API Gateway יש timeout קבוע של 30 שניות. אם יש לכם פונקציה שזמן הריצה שלה ארוך יותר - API Gateway יבצע timeout, בכל זאת.
בשלב הזה אתם אמורים להבין כמעט את כל המדדים שמסופקים ב Monitoring של למבדה:
המדד היחידי שלא הוסבר הוא ה iterator age שרלוונטי רק למקורות מסוג stream-based events.
הוא מציין כמה זמן ארך מרגע שפונקציית הלמבדה שלנו קיבלה batch של הודעות, ועד שסיימה לטפל בהודעה האחרונה. כלומר: מה ה latency המרבי של טיפול בהודעות ב batch, שהגיעו ממקור שכזה.
שווה להזכיר את שירות ה Lambda@Edge ששוחרר לאחרונה, שירות המאפשר להריץ פונקציות למבדה (Nodejs בלבד, כרגע) על ה PoPs (כלומר: Point of Presence) של CloudFront.
לאמזון יש כ 14 regions בעולם עליהם אפשר להריץ את כל שירותי ה AWS, אבל יש לה קרוב ל 100 מיקומים בהם יש לה נוכחות - עבור שירות ה CDN שלה, הרי הוא CloudFront.
עבור פעולות פשוטות למדי, אם תוכלו להריץ את הקוד קרוב מאוד ללקוח שלכם - תוכלו לספק לו תשובה מהירה ביותר: ה latency ל"קוד" שלכם עשוי להיות עשרות מילי-שניות, ולא מאות מילי-שינות.
למבדה_על_הקצה (Lambda@Edge) תנהל עבורכם את שכפול הקוד לרחבי העולם - ותדאג שהלקוחות שלכם, יקבלו זמני תגובה משופרים.
את פונקציית הלמבדה_על_הקצה שלכם, אפשר לחבר לארבע נקודות-חיבור שונות בתהליך של cloudfront (בתרשים: החצים הירוקים והכתומים), ולבצע שינוי על המשאב שהתבקש.
כמובן שבשירות כזה מאוד חשוב לספק זמני תגובה מהירים מאוד - מה שלא תמיד מתרחש בצמד למבדה + API Gateway.
ה API Gateway הוא מחוץ לתמונה, יצירת ה HTTP connection עם TLS/SSL - כבר מתבצע ע"י cloudfront, ולכן הרכיב היחידי שעלול לגרום לעיכובים (ולייתר את היתרון שבריצה על PoP) הוא זמן האתחול של ה container של למבדה. לא סתם התחילו ב nodejs כסביבת-ריצה.
ללמבדה_על_הקצה - יש מגבלות משלה (מקסימום 128MB זיכרון, זמני ריצה קצרים יותר, אין גישה לשירותים אחרים של אמזון, וכו')
שירותים נוסח למבדה_על_הקצה כבר היו קיימים (למשל: PubNub Blocks) - אבל לאמזון יש יתרון מובנה מול מתחרים קטנים יותר.
יהיה מעניין!
סיכום
המאמר הקודם באמת היה מאוד מקדמי / בסיסי - ונראה לי שהצלחנו לסגור את הפער ולספק היכרות מספיק מעמיקה עם שירות הלמבדה - ויחסית בקלילות.
----
לינקים רלוונטיים
הודעתך לא התקבלה - נסה שוב מאוחר יותר
Oops! Something went wrong while submitting the form