כולם מדברים על NoSQL, אף אחד לא מדבר על Event Sourcing

מה היתרון של Event Sourcing על פני CRUD וכיצד הוא בא לידי ביטוי?

startup PD

מאת לידן חיפי, מפתח בכיר בחברת Wix.

בעולם האינטרנט של היום, אחד האתגרים המורכבים ביותר הוא בניית מערכת שיכולה לעמוד בעומס גולשים רב ושניתנת להרחבה בקלות (Highly scalable). הפתרונות הטכנולוגיים הנפוצים היום הם שימוש בבסיסי נתונים NoSql וחלוקה של האפליקציה ל-Microservices כשלכל שירות יש תפקיד ספציפי.

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

אתגרים בארכיטקטורה מבוססת CRUD

היום נהוג להשתמש בניהול מידע מבוסס CRUD – Create-Read-Update-Delete לפיו מעדכנים את הערכים בבסיס הנתונים עצמו. נניח שאנחנו רוצים לבנות גרסה משלנו לטוויטר, משתמש יכול להיכנס, לפרסם טוויטים ולקרוא טוויטים של משתמשים אחרים.

ארכיטקטורה מבוססת CRUD תהיה מורכבת מטבלאות מנורמלות של משתמשים וטוויטים, כאשר בבניית פרופיל משתמש (timeline) יהיה צורך בביצוע join בין הטבלאות, וכשמדובר בכמות התעבורה העצומה שעוברת דרך טוויטר, זה פשוט לא מעשי:

  1. ברוב המקרים יהיה קשה מאוד לשנות סכמה בטבלאות גדולות, לפי דרישות עתידיות – למשל אם נרצה להוסיף מספר לייקים לטוויט.
  2. עבודה של מספר משתמשים על אותו אובייקט במקביל דורשת שימוש בטרנזקציות ונעילות בבסיס הנתונים, מה שמשפיע לרעה על ביצועי האפליקציה.
  3. טבלאות מנורמלות אופטימליות לכתיבה אך יקרות לקריאה, כאשר באפליקציות אינטרנט לרוב נבצע הרבה יותר פעולות קריאה מאשר כתיבה. פעולות קריאה מורכבות יותר בשל השימוש ב-join, מה שגורם להאטה משמעותית בביצועים.
  4. טבלאות מנורמלות מאפשרות שמירה על רמה גבוהה של קונסיסטנטיות. כלומר, אם כתבתי ערך מסוים לבסיס הנתונים אני מצפה לקרוא אותו מיד אחרי הכתיבה. אולם ליתרון זה יש מחיר, השאלה אם הוא מוצדק. בניגוד לאפליקציות בנקאיות, הויתור על קונסיסטנטיות באפליקציות אינטרנט הוא במקרים מסוימים אפשרי.

בעיות אלה ניתנות לפתרון באמצעות שימוש ב-Event Sourcing – תבנית שלפיה לא שומרים את המצב הנוכחי של האובייקט, אלא את האירועים שהובילו למצב זה, כאשר אירוע מוגדר כפעולה שקרתה בעבר.
כל פעולה שמבוצעת במערכת על אובייקט מסוג מסוים מוסיפה ללוג האירועים אירוע חדש (Append only), ולא מתבצע שום שינוי של אובייקטים קיימים.

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

אז איך בעצם Event Sourcing עובד?

התבנית מתארת למעשה רצף של אירועים ששמורים בבסיס הנתונים (בדרך כלל כאובייקט Json). לכל אירוע נשמר מידע נוסף כמו מתי הוא קרה, מי המשתמש שביצע אותו ומספר סידורי עולה שמציין את הגרסה של האובייקט (כל אירוע משנה את האובייקט ולכן מקבל מספר גרסה שונה), וכמובן את פרטי האירוע עצמו.

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

image11

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

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

image03

ניתן לראות איך יצרנו טוויט חדש מרצף האירועים:

image08

כדי למחוק טוויט, נשמור אירוע של TweetDeleted שיייצג את המחיקה. זאת אומרת, לא נמחק את הטוויט בפועל אלא רק נסמן אותו כ״מחוק״.

מימוש מערכת מבוססת Event Sourcing

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

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

image10

אם מימשנו כך את התבנית הבסיסית, סביר להניח שניתקל בשני אתגרים עיקריים:

Side effects

בדוגמה של טוויטר נרצה שבכל Retweet הכותב המקורי של הטוויט יקבל נוטיפיקציה. מובן שפעולה זו חייבת לקרות פעם אחת, ולא בכל פעם ש״ננגן״ את האירועים של אותו טוויט כדי לשחזר את המצב שלו.

הפתרון לכך הוא שילוב של תבנית Pub/Sub באפליקציה, כאשר Event-handlers שונים מאזינים לאירועים מסוימים במערכת, ומבצעים את הפעולה הנדרשת כתגובה לאירוע.

האובייקט NotificationHandler יאזין לאירוע TweetRetweeted. ואז כשמשתמש יעשה ריטוויט, המערכת תדווח אירוע של TweetRetweeted וה-handler ישלח נוטיפיקציה. לעומת זאת, כשנרצה לשחזר את הטוויט, ״ננגן״ את האירועים שלו ב-TweetHandler בלבד, וכך נוודא שהנוטיפיקציה תישלח פעם אחת.

image07

שאילתות ו-Views

בוודאי שמתם לב שבכל שחזור של אובייקט (למשל כאשר נרצה לטעון טוויט בודד) אנחנו ״מנגנים״ את כל האירועים שקשורים אליו. פעולה זו עשויה לקחת זמן כשמדובר במאות אירועים.

הפתרון לבעייה זו הוא שימוש ב-Views, סכמות עזר שבנויות באופן דומה לממשק המשתמש ולכן אופטימליות לקריאה. הדרך לממש את ה-Views היא ע״י תבנית נוספת שנקראת CQRS (command query responsibility segregation) שמטרתה להפריד את האופן שבו אנחנו כותבים את המידע מהאופן שבו אנחנו קוראים אותו.

במילים אחרות, כל פעולה של משתמש במערכת (הנקראת גם Command) מתורגמת לאירוע או לרצף של אירועים שנשמרים בבסיס הנתונים הראשי של האפליקציה, ולאחר מכן מעדכנים נתונים ב-Views. כאשר המשתמש יבקש לקרוא מידע מהמערכת – נמשוך את המידע מה-Views ונחזיר אותו למשתמש.

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

commandmodel

בשילוב עם תבנית זו נקבל מערכת שהיא אופטימלית לכתיבה (Event-Sourcing) ואופטימלית לקריאה (CQRS), במחיר של ויתור מסוים על רמת הקונסיסטנטיות. כאמור, זהו ויתור סביר באפליקציות רשת גדולות- השיפור המשמעותי בביצועים מפצה על כך.

לסיכום

הצגנו מספר יתרונות למערכת המבוססת על Event Sourcing לעומת ארכיטקטורת CRUD:

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

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

כתב אורח

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

הגב

10 Comments on "כולם מדברים על NoSQL, אף אחד לא מדבר על Event Sourcing"

avatar
Photo and Image Files
 
 
 
Audio and Video Files
 
 
 
Other File Types
 
 
 
Sort by:   newest | oldest | most voted
Jako
Guest

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

Event Sourcing הוא לא פתרון קסם, אלא ארכיטקטורה עם יתרונות וחסרונות.

2. אזכיר את Datomic ו-Event Store, שהינם databases המבוססים על הרעיונות שהוזכרו.

Lidan Hifi
Guest

תודה, אכן נקודות חשובות שהוזכרו במשפט בסוף הכתבה. מעל scale מסוים ככל הנראה נעדיף לעבוד על נתונים מסוכמים (views) ולא על המידע הגולמי, בשילוב של שניהם נוכל לשלוט על רמת הקונסיסטנטיות הנדרשת. השילוב הזה נשמע פשוט בתיאוריה, אבל כפי שאמרת- אם לא עושים אותם נכון, סנאפשוטים עשויים להיות לא פרקטיים ולהאט את המערכת.

אדם
Guest

נהניתי לקרוא, אצלול לזה בסופ”ש הקרוב.

דיבי
Guest
תכננתי ובניתי בסיסי נתונים המסוגלים להתמודד עם מאות אלפי אירועים חדשים בשניה במגוון מערכות high performance (בטחון/טלקו/social וכו…) וכל זאת ללא nosql ופתרונות נישה על אף שיש לי נסיון רב גם בפתרונות אלו. ניתן להשיג ביצועים של 100,000 tps ויותר בעלויות נמוכות מאד וזאת על ידי ניהול נכון של בסיס מתונים רלציוני מנסיון רב שנים ועשרות מערכות אותי הקמתי. השיטה הטובה ביותר לנצל בסיסי נתונים רלציוני דוגמת MySQL ו Postgres ודומיהם למערכות high performance מורכבת מכמה עקרונות חשובים : 1. הימנעות מוחלטת משימוש ב update ו-delete אשר יקרות פי 5 ו 3 בהתאמה ב-io מפעולת insert. אין עדכונים ומחיקות. רק… Read more »
Jako
Guest

לגבי 4, הרבה פעמים לא צריך אפילו SSD שכן טבלאות אלה נמצאות בזכרון אם ניגשים אליהן בתדירות גבוהה.

Lidan Hifi
Guest

העלית נקודות מאוד חשובות, לא בהכרח בנושא הכתבה אבל מאוד רלוונטיות למי שמתכנן לבנות אפליקציה שתתמוך ב-scale גבוה. אנחנו ב-Wix עובדים עם MySQL ברוב האפליקציות, בדיוק לפי אותן נקודות שהזכרת- טבלאות לא מנורמלות, הימנעות מטרנזקציות (או שימוש בטרנזקציות אפליקטיביות ולא של בסיס הנתונים) וטבלאות ״ארכיון״.

לא מזמן פרסמנו פוסט בבלוג ה-Engineering שלנו בדיוק באותו נושא למי שמעוניין להרחיב:
http://engineering.wix.com/2015/12/10/scaling-to-100m-mysql-is-a-better-nosql

תודה!

Yaniv Davidovich
Guest

כתבה מעניינת ואיכותית. תודה!

יונתן
Guest

דיבי, אם אתה זמין לפרוייקט ייעוץ – השאר פרטים בבקשה. מייל אדהוקי שלי לקבלת פניה:
geekdbarticle@mailinator.com

תיבי
Guest
מה אני אגיד לכם, זה מאוד חשוב ללמוד מWix, אחד האתרים הכי איטיים בפלנטה עם אחת מאפליקציות העריכה הכי איטיות בפלנטה, איך לבנות טכנולוגיות. לא יודע אם שמתם לב, אבל למרות התקציב העצום שלכם, אתם עדין מעסיקים בעיקר היפסטרים שעסוקים באיך להתארגן על המדבקה הסקסית ביותר למקבוק שלהם במקום ללמוד איך פאקינג מתכנתים… הCTO או הארכיטקטים שלכם – לא יודעים מה הם עושים. הם עמוק בתוך מערבולת הבאז-וורדס של בועת ההיי טק ההיפסטרית הנוכחית והרחק מעולם הטכנולוגיות. הבאז שהם מנסים להחדיר בכתבה העלובה הזו – רק עדות לכך. “10 סיבות לעבור מMicro Services ל Event Driven I/O” – חלאס, קודם… Read more »
תלאח
Guest
איזה כיף זה לחרטט באז-וורדס כמו “Event Sourcing” במקום פשוט לכתוב שאתה מחזיק את כל פעולות המשתמש ובונה תמונה מלאה בזמן השליפה? זאת אומרת, זה לא שיש מתכנת אמיתי C, C++ שלא היה פותר את זה ב35 דקות, זה פשוט יותר כיף להמציא חרטוטים, להגיד לכולם “מה, אתם לא מכירים Harta Blocking?” ואז לצאת הגבר החכם והמוביל טכנולוגית. אז בזמן שבעולם הסטארטאפ הישראלי נלהבים מאוד מהבאז-וורד NOSQL כאילו לראשונה אנחנו מסוגלים להתמודד עם המון מידע, כבר בשנות ה90 המוקדמות היו DBs של מיליארדי רשומות שעבדו ועבדו מצוין, זה פשוט עניין של מי מתחזק אותם וכמה חזק המתכנת שאתה מביא. רמז,… Read more »
wpDiscuz

תגיות לכתבה: