מנוע מבני-נתונים: Redis

ישנן משימות תכנות פשוטות למדי. למשל:

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

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

כאשר הצרכנים של השירות נמצאים על מחשבים אחרים (פיסית) – יש לחשוף להם את השירות:

  • לחשוף גישה ברשת (למשל Java Servlet).
  • להגדיר פרוטוקול/פורמט (למשל מבוסס REST, שיהיה פשוט) כיצד מבצעים קריאה לשירות ואיזה תשובה מקבלים.
  • לדאוג לטיפול במקביליות / בעיות consistency של הנתונים.
כל אלו יכולים להפוך משימה של דקות – למשימה של כמה שעות, ולדרוש תחזוקה גדולה יותר לאורך הזמן.
אם נתבונן במערכות מבוזרות, ניתן לראות שפעמים רבות החלק הגדול של הקוד הוא קוד שרץ מקומית – עם נקודות סנכרון בין מספר שרתים / שירותים מרוחקים. נקודות הסנכרון הללו דורשות לא מעט עבודה (יחסית לקוד מקומי דומה).הסנכרון נעשה לרוב ע"י מבני נתונים או ע"י הודעות (שגם הודעות לרוב מנהלים בעזרת מבני נתונים).

Redis (קיצור מעט מוזר של REmote DIrectory Server) הוא "מנוע מבני-נתונים" המספק לנו שירות של מספר מבני נתונים עם גישה מרוחקת, אטומיות ואפשרות של שמירת מבני-הנתונים לדיסק (Persistence). ל Redis יש ספריות client המאפשרות גישה קלה למדי במגוון רחב מאוד של שפות תכנות.
אם אתם משתמשים בשפת נישה שאינה ברשימה (למשל שפת Boo), פרוטוקול הגישה ל Redis הוא פשוט מספיק על מנת לממש Client בקלות יחסית.

ייתכן וזו הפעם הראשונה בה אתם שומעים על Redis, אולם בכל כדאי להכיר ש Redis משמש כבר היום כמה תשתיות רציניות למדי:

מול כל השמות ה"גדולים" הללו, חשוב לציין ש Redis היא מערכת פשוטה למדי. ניתן להתחיל לעבוד בה בצורה יעילה תוך שעות בודדות. השימוש ב Redis יכול לחסוך זמן פיתוח גם לצרכים "קטנים" (למשל: מערכת build).

—-

התקנה (למי שמוכן "ללכלך" מעט את הידיים)

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

Redis תומכת רשמית ב OS X, Linux ו BSD Unix. מכיוון שאני עובד על "חלונות" אני משתמש בגרסה לא רשמית – אך טובה מספיק עבור פיתוח: https://github.com/MSOpenTech/redis.

את הגרסה מפתח ומתחזק צוות של מייקרוסופט ש"שומר שטכנולוגיות-מפתח לא יסגרו בפני משתמשי חלונות/Azure". חילופי תפקידים משעשע…

הנה הסבר כיצד להתקין את Redis על "חלונות" במהירות:

קולאג' הפעולות להתקנה מהירה של רדיס על "חלונות"
  1. הורידו את קובץ ה ZIP של כל ה Repository מתוך github (כפתור ה download הוא בפאנל הימני).
  2. פתחו את קובץ ה ZIP שירד.
  3. בתוך ה ZIP, עברו לתת-התיקיה redis-2.6/bin/release.
  4. פתחו את הקובץ redisbin64.zip (מכונות 64 ביט) או הקובץ השני (32 ביט) ו"שפכו" את תוכנו לאיזו תיקיה.
  5. הפעילו את השרת של redis.
  6. הפעילו את הלקוח CLI של redis.

—-

ניסיון ראשון עם רדיס

Redis בנוי כ Dictionary (כלומר "Hash Table") ענק של צמדי .

מנקודת מבט מסוימת ניתן לומר שזהו בסיס נתונים NoSql-י, מסוג Key/Value (בעצם Key/Data Structure) שפועל In-Memory. מנקודת מבט זו Redis הוא מהיר בצורה קיצונית[א] וקצת חריג בנוף של NoSQL Databases.

בדוגמה למעלה פתחתי 2 clients של Redis ובצעתי בהם מספר פעולות על מבנה הנתונים הפשוט ביותר: String.

  1. הכנסתי ערך של "!hello world" למפתח "messages:hello". הסימן ":" הוא קונבנציה מקובלת ל namespacing של ערכים, בדומה ל סימן "." לסימון packages בג'אווה.
  2. קראתי את הערך – ערך ההחזרה מוצג בשורה הבאה.
  3. ניסיתי לקרוא מפתח שלא הושם בו ערך – וקיבלתי nil.
  4. קראתי את הערך מה client השני – והערך זמין לו.
    אם זה לא היה קורה –  לא היה הרבה טעם ב Redis, כאשר אני מקבל את זה בקלות בג'אווה : )

את הפקודות, אגב, אני מקליד ב Capital letters לצורך ההדגשה, הפרוטוקול של redis מקבל אותן בכל case.
המפתחות הם (כמובן) Case Sensitive.

Lists

בואו נעבור למבנה נתונים מעט יותר מורכב: List.
המקביל בג'אווה (עולם מוכר?) ל List של Redis הוא <LinkedList<String, בערך. מדובר ברשימה משורשת עם השלכות הסיבוכיות הידועות (זול להכניס, יקר לחפש). היא מנהלת רק מחרוזות. בתיעוד של Redis מצוינת על כל פקודה הסיבוכיות שלה, למשל (O(n + הסבר מהו n.

במה List שונה מ <LinkedList<String של ה JDK? הנה 2 דוגמאות:

  • המימוש מעט שונה. למשל פקודת LINDEX (האות L עבור List) סורקת את הרשימה משני הכיוונים: פעם מימין, ופעם משמאל – מה שאומר שאם האיבר שאנו מחפשים הוא האחרון ברשימה – ניתן לצפות לזמן של (O(1.
  • בג'אווה יש רשימה "מסונכרנת" או רשימה "לא מסונכרנת". ב Redis זו אותה רשימה כאשר יש פעולת שליפה "מסונכרנת" או "לא מסונכרנת". הגישה של Redis היא מאוד לא-דפנסיבית (כמו ג'אווה), אלא יותר כמו של Unix ("אתה אחראי למה שאתה עושה").

בואו נשחק מעט עם List:

  1. גיא יוצר רשימה בשם guyList. הדרך ליצור רשימה ברדיס – היא פשוט להתחיל ולהכניס לה ערכים, במקרה זה: בעזרת פקודת LPUSH = "דחוף לרשימה" וגם "דחוף משמאל".
    ניתן לראות את ערך ההחזרה – 3, 3 ערכים נוספו לרשימה.
  2. עכשיו נדחוף ערך אחד מימין בעזרת RPUSH.
  3. LLEN בודק את אורך הרשימה. כצפוי: הוא 4.
  4. ניתן לקבל טווח של ערכים ברשימה ע"פ האינדקס שלהם, במקרה הזה – כל הרשימה שלנו.עכשיו נוסיף לסיטואציה את בן, שמעביר פריטים מהרשימה של גיא לרשימה משלו.
    הכוונה שלי היא לייצר מעט "דרמה", והיא לא לעסוק בנושאי אבטחה. מבחינת אבטחה: יש לנהל ניהול גישה (authentication) בצורה אפליקטיבית על שרת האפליקציה (ג'אווה, Haskell, רובי, Whatever) [ב].
  5. בן בוחר דווקא להשתמש בפקודות רדיס ב lower case – מותר.
    מכיוון שהוא הולך לבצע פקודה הנוגעת ל2 מבני נתונים שונים (כל פקודות רדיס אטומיות כל עוד מדובר במבנה נתונים אחד) – עליו לשמור על consistency והוא עושה זאת ע"י הפעלת transaction – פקודת multi, ומקבל אישור.
  6. הוא מסיר מצד ימין (rpop) את האבר האחרון ברשימה של גיא.
  7. הוא "דוחף" את ערך האיבר ("itemD") לרשימה חדשה משלו. ניתן לציין את הערכים עם או בלי מירכאות. מכיוון שאלו מחרוזות – התוצאה תהיה זהה.
  8. בעזרת exec בן מנסה לבצע "commit" לטרנזקציה – והוא מצליח.
  9. בעצם, מכיוון שתהליך העברת ערכים בין רשימות הוא נפוץ ברדיס, יש פקודה מקוצרת שעושה את 2 הפעולות הנ"ל בצורה אטומית (כלומר: לא צריך להפעיל טרנזקציה). הפקודה נקראת… (מפתיע!): rpoplpush ומקבלת את המקור והיעד. הערך שעכשיו יעבור הוא "itemA". ייתרון נוסף בפקודה ישירה הוא ביצוע roundtrip יחיד ברשת – ולא ארבעה.
    האם יש גם פקודת lpoprpush או lpoplpush ברדיס? בכן… לא. רדיס שומר על פשטות, לעתים במחיר הקלות למשתמש: לעתים צריך מעט יצירתיות בכדי למצוא את הדרך עם הפקודות המובנות של רדיס (או שאפשר להרחיב את הקיים בעזרת LUA – על כך בהמשך). זה, לטעמי, מעט חסרון של רדיס – והייתי שמח שתצוץ ספריית הרחבה (למי שמעוניין) שמשכללת את סט הפקודות שרדיס מכיר.
  10. אנו בוחנים את הרשימות ורואים את התוצאה הסופית.

מבני נתונים נוספים

Redis תומך בחמישה טיפוסים:

  • String (עד 512MB) – יכול להכיל כל אובייקט מקודד למחרוזת (json, תמונה וכו'). מכיוון של Redis אין indexing – אין טעם "לפרק" אובייקטים מורכב לחלקים קטנים יותר. אני מניח שניתן ליישם אינדוקס חיצוני – אם צריכים.
    ניתן לאכסן ב strings גם ערכים מספריים ולבצע עליהם פעולות אטומיות כגון INC (קיצור של increase – כמו בשפת פאסקל), INCBY ו INCBYFLOAT.
  • List שהוא בעצם <LinkedList<String, עליו דיברנו למעלה. ל List יש גם פעולות blocking שאם יבוצעו על רשימה ריקה "יתקעו" את ה client עד שיהיה ערך מסוים או שיעבור timeout שהוגדר בפעולה.
  • Sets שהוא בעצם <Set<String (כל איבר יכול להופיע פעם אחד בלבד). מאפשר לעשות פעולות יעילות על קבוצות כגון Union או intersection. פקודות של Set מתחילות באות "S".
  • SortedSet שהוא בעצם <SortedSet<String ומחזיק את הרשימה באופן ממוין-תמידית, מה שמאפשר לבצע פעולת Range (שליפה של טווח של איברים) בצורה יעילה. לכל ערך ב SortedSet יש ערך מספרי (score) וערך מחרוזת (value). הערך המספרי קובע את הסדר. על מבני נתונים אלו ניתן גם לעשות פעולות על קבוצות (כמו union) ואפילו לבצע פעולות חישוביות על ה scores (פקודת ZUIONSTORE). פקודות של SortedSets מתחילות באות "Z".
  • Hash – שהוא בעצם <Map<String (או <Dictionary<String למי שבא מ #C). כלומר: value של ה K/V store שלנו הוא K/V בעצמו. לא ניתן לקנן Hash מעבר לרמה אחת. פקודות של Hash מתחילות באות "H".

בעיות נפוצות לדוגמה שנפתרות בעזרת Redis:

  1. Cache מבוזר, המשותף לכמה שרתים. שיתוף זה מאפשר ששרת אחד יחדש את ה cache – וכל השאר יהנו מחידוש זה.
  2. ניהול State אשר מצד אחד הוא בזיכרון (כמו server session state) ומצד שני הוא משותף (כמו db session state) כך שאם המשתמש אינו יכול לחזור ל node האחרון שטיפל בו, ה node החדש יכול לגשת ל session של המשתמש ולא לרסט אותו. אם הנושא לא מוכר – ניתן ללמוד עליו מהספר של מרטין פאוולר שבקישורים.
  3. Pub/Sub – מערכת הודעות בין כמה שרתים.
  4. Job Queue לחלוקת עבודה במערכת מבוזרת.
  5. ספירה וניהול מבוזר של counters.
  6. פתרון בעיות של מערכות מבוזרות כגון Leader Election, בעיות הצבעה ובעיות שעון / סנכרון זמנים. ניתן למצוא בלינק הבא כמה רמזים / המלצות למימוש.

לבעיית ה Pub/Sub החליטו להציע פתרון מובנה – הנוח מאוד לשימוש.
לבעיות ה Cache ישנן פקודות כמו EXPIRE (מחיקת ערך לאחר זמן נקוב), TTL (לבדוק כמה זמן קצוב נותר לאיבר) או PERSIST (ביטול הקצבת הזמן).
לבעיית ה counting יש את משפחת פקודות ה INC.
וכו'

דוגמה לשימוש ב Redis בעזרת client (או driver) לג'אווה הנקרא Jedis

יכולות אחרות

ל Redis יש עוד כמה יכולות משמעותיות שכדאי להכיר:

Persistency
היכולת לשמור את מבני הנתונים לדיסק.
ברדיס יש שני מנגנוני שמירה:

  • RDB (קיצור של Redis Database) – שמירת כל מבני הנתונים בזיכרון ביחד לדיסק, ע"פ מדיניות קבועה / פעולה יזומה של המשתמש.
  • AOF (קיצור של Append Only File) – שמירת פעולות אחרונות ל Log file לצורך התאוששות במקרה של קריסה.
כדאי לציין זאת עכשיו: Redis הוא לא פתרון בסטנדרט גבוה של durability בשמירה לדיסק. אם אתם שומרים (בקיצוניות) מידע פיננסי – השתמשו במנגנון אחר בכדי לשמור אותו, לא ברדיס. ניתן להגיע עם רדיס לאמינות לא-רעה שמתאימה לשימושים רבים.
AOF ניתן להפעיל אותו עבור כל שינוי של מפתח (פגיעה קשה בביצועים) או כל שנייה (tradeoff סביר בין אמינות וביצועים – ברירת המחדל). ניתן גם לכבות יכולת זו בכלל או להשאיר את ה flushing למדיניות של מערכת ההפעלה, שזה סוג של כתיבה מדי זמן-מה.
RDB ניתן להפעלה ע"י פקודות (SAVE או BGSAVE) או קונפיגורציה. קונפיגורציה נעשית בקובץ redis.conf, בואו נתבונן ב section המתאים:
ברירת המחדל היא סבירה למדי:
  • שמירה לאחר 15 דקות לאחר שינוי של מפתח כלשהו.
  • שמירה לאחר 5 דקות אם השתנו 10 מפתחות או יותר.
  • שמירה לאחר דקה אם השתנו 10,000 מפתחות.
אם המערכת שלכם עובדת בעומסים נמוכים – ניתן לצמצם, נאמר, לשמירה לאחר דקה לאחר שינוי כלשהו (קרי save 60 1).
ניתן לקרוא עוד בנושא בתיעוד הרשמי של רדיס.

Transactions
כפי שהראנו למעלה בעזרת פקודות כמו MULTI ו EXEC ניתן לייצר טרנזקציות בצורה פשוטה.
ניתן לקרוא עוד בנושא בתיעוד הרשמי של רדיס.

Scripts
ניתן לכתוב בשפת LUA סקריפטים המבצעים סדרת פקודות – וכך להרחיב את סט הפקודות הזמין. הסקריפטים יכולים להישלח בכל קריאה (הפעלה של פקודת EVAL) או להישמר בקובץ ה redis.conf.
יתרונות ה Scripts דומים למחשבה על Stored Procedure ב Database – אנו חוסכים את ה latency בין קריאה לקריאה ובפקודה אחת ניתן לבצע את סט הפקודות ישירות ב DB (במקרה שלנו: Redis).

ניתן לקרוא עוד על סקריפטים בתיעוד של רדיס.

Clustering
Redis הוא כמעט-single-threaded. כל הפקודות יבוצעו ע"י thread יחיד ורק פעולות של שמירה לדיסק עשויות להיעשות ב thread נפרד. משמעות אחת היא שאם יש לכם שרת עם שמונה cores – יש להפעיל 8 תהליכים שונים של redis (ב multiplexing) על מנת לנצל את כח החישוב של המכונה כראוי.
שכפול מנועים הוא תסריט אפשרי לניהול cache – אבל בעייתי לכמעט כל תסריט אחר. לצורך כך יש ברדיס מנגנון של Partitioning ויש גם מנגנון של master/slave cluster.

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

סיכום

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

כשתהיה לכם בעיה שכרוכה במספר מחשבים ("מבוזרת") – חשבו על Redis.

שיהיה בהצלחה!

לינקים מעניינים

הארכיטקטורה של רדיס: http://www.enjoythearchitecture.com/redis-architecture ו http://pauladamsmith.com/articles/redis-under-the-hood.html

תבניות שימוש ברדיס: http://www.slideshare.net/dvirsky/kicking-ass-with-redis

רדיס בטוויטר: http://bit.ly/1pm7PsV

עוד פרטים על ה Persistency של רדיס: http://oldblog.antirez.com/post/redis-persistence-demystified.html

redsmin – כלי monitoring לרדיס: https://redsmin.com

[א] נו – הכל רץ בזיכרון. זה לא רציני לזרוק סתם כך מספרים ללא Use-case מדויק וחומרה עליה הבדיקה רצה, אבל בהערת צד אפשר לספר שמדברים על מספרים כגון "100,000tps" – מספר שמשאיר באבק כל "בסיס נתונים" אחר, בערך.
tps = transactions per seconds שאילתות בשנייה. ברור שקל יותר לשלוף ערך מתא בזיכרון מלבצע join על מידע ששמור על הדיסק.

[ב] יותר ספציפית, כן יכול להיות מצב שבו מישהו חדר ל Data Center – ואז רדיס הוא "פרוץ" לגישה. לרדיס יש אפשרות לבצע אימות גישה (Authentication) על בסיס ססמה שנקבעה מראש – לא פרדיגמה קשיחה במיוחד, אלא כזו שתחסום את התוקף המזדמן. כאשר זקוקים ליותר הגנה – מתקינים לרוב Firewall מקומי על השרת של רדיס שיאפשר תקשורת נכנסת רק מכתובות ה IP של שרתי האפליקציה. אם שרתי האפליקציה נפרצו… לא ברור עם הגנה קשיחה יותר על רדיס תעזור.

עשה זאת בעצמך: NoSQL

מעוניינים לשדרג משהו? לעתים זול יותר ופשוט יותר לבצע שיפוץ קטן בעצמכם, במקום להשתמש בבעל מקצוע. ייתכן והתוצאה תהיה מוצלחת לא-פחות.
בפוסט זה אני רוצה לשתף במימוש מוצלח של "טכניקת NoSQL BIG DATA" שביצענו על גבי מערכת קיימת, מבלי לשנות אותה באופן מהותי ומבלי להחליף את בסיס הנתונים הרלציוני הקיים.המסר המעניין מבחינתי, הוא שניתן ליישם ״בקטנה״ רעיונות של בסיסי הנתונים NoSql בעצמכם – ולהשיג תוצאות יפות.

הבעיה

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

בדיקות שעשינו למערכת הראו שבסביבות 30-אלף פרויקטים, המערכת מתחילה להראות סימנים של שבירת הלינאריות ב scalability. כלומר: עד נקודה זו – אם רצו לנהל עוד פרויקטים היה ניתן להוסיף עוד חומרה ביחס ישר לגדילה בכמות הפרויקטים / הפעילות. מעבר לנקודה זו היה צריך להוסיף x וקצת חומרה ל x פעילות נוספת, וככל שהמספר גדל – העלות השולית הלכה וגדלה.
הבנו שהמערכת תוכל לטפל במשהו כמו 50 אלף עד 100 אלף פרויקטים, תלוי בכמות החומרה שהלקוח יסכים להקצות. באופן מעשי זהו בערך גבול ה Scalability שלנו ולכן ההמלצה ללקוחות הייתה לא ליצור מעל 50 אלף פרויקטים.חשוב להבהיר שמדובר במערכת בת כ3 שנים – שעברה לאורך חייה לא מעט שיפורי performance ו scalability. בשלבים הראשונים של המערכת הצלחנו לבצע שיפור יחיד שהגדיל את ה Scalability ב 30% – אך ככל שהזמן עבר שיפרנו אלמנטים פחות משמעותיים (כיוון שהמשמעותיים כבר שופרו) והבנו שאנו מגיעים לקצה ה Scalability של הארכיטקטורה הקיימת.

פתרון אפשרי אחד היה לנסות לעבור לבסיס נתונים NoSQL, נוסח MongoDB או CouchDB – המתאימים יותר לשימוש הספציפי של המערכת, והיו יכולים בהחלט לשפר את המצב. הבעיה: שאר האלמנטים במערכת (מלבד הפרויקטים) התנהלו בצורה משביעת-רצון בבסיס הנתונים הרלציוני. מה עושים? עושים הסבה לכל הקוד לעבוד מול בסיס נתונים NoSql או דורשים מלקוחות לנהל 2 בסיסי-נתונים שונים במקביל?!

התוצאות

בהשקעה לא כל כך גדולה (כשני מתכנתים טובים לשלושה חודשים) אנו מסוגלים, על אותה החומרה בדיוק, להריץ כחצי מיליון פרויקטים במערכת, וכמות כמעט-כפולה של פעילות (כלומר:מספר פעולות של משתמשים בשנייה). גם זמני התגובה למשתמש הקצה השתפרו. סה"כ: שיפור מדהים!

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

מקור הבעיה

ישנן סיבות שונות המצדיקות מעהר לNoSql Databases:

  • הרצון בסכמה גמישה, שלא דורשת migration בין גרסאות.
  • כמות נתונים (ב TB) שדורשת מעבר משרת אחת לכמה שרתים – מה שנקרא Scale Out.
  • בעיית Scalability מקומית. כלומר: מעל כמות נתונים מסוימת, זמן התגובה למשתמש הקצה הופך ללא-סביר.
    זו הבעיה איתה התמודדנו במערכת שלנו.

איך נוצרת בעיית Scalability?
בואו נביט על (הפשטה של) סכמת הנתונים של מערכת הפרויקטים:

האובייקטים החשובים, הבנויים בהיררכיה הם: פרויקט, נושא, דיון, תגובה ותכונה-של-תגובה.

אני רוצה להתמקד לרגע באובייקט קצת פחות טריוויאלי במודל הנתונים: Comment Attribute.
תכונה-של-תגובה (Comment Attribute) יכולה להיות דבר כמו: תאריך, שם המגיב, קישור לתמונה וכו'

בבסיס נתונים רלציוני ניתן לשמור תכונות כאלו ב-2 אופנים:

  • סכמה קשיחה: כעמודה (column) בטבלה. על כל תכונה אפשרית יוצרים עמודה חדשה.
    יתרונות: פשטות
    חסרונות: יש תכונות (כגון "deleted by admin") שמתרחשות לעתים נדירות – אך עדיין יש לשמור עבורן מקום בכל רשומה, הוספת שורה = הוספת סכמה.
  • סכמה גמישה: כטבלה נוספת, בתצורת master-detail, בה כל מפתח וערך של תכונה היא שורה נוספת.
    יתרונות: גמישות רבה ללא שינויי סכמה
    חסרונות: עוד טבלה לנהל, עוד קצת סיבוך.

המערכת הנ"ל השתמשה בסכמה גמישה.

נתונים לדוגמה, בסכמה גמישה של תכונות אובייקט (לחצו להגדלה)

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

ככלל אצבע: כשאנו מגיעים ל 10M או יותר רשומות בטבלה אחת בבסיס הנתונים – בסיס הנתונים מתחיל להגיב פחות טוב, במיוחד עם זו טבלה בה הנתונים הם לא רציפים (פרטים על כך בהמשך) ו/או יש ריבוי של אינדקסים.

חזרה לתיאוריה

בואו נתתבונן על שכבת הנתונים (כגון Hibernate) ובסיס הנתונים ביחד – כמקשה אחת, שהיא כקופסה שחורה שאנו מתעלמים ממה שקורה בתוכנה.
סוג השיפור שאני מציע בפוסט זה כנראה לא יעלה מה DBA הטיפוסי. רוב ה DBAs (מהניסיון שלי), מנסים להשיג שיפורים בעזרת שינויים בבסיס הנתונים בלבד, ומבלי לשקול שינויים של הקוד הקיים. סוג של תקרת זכוכית שבאה כנראה עם התפקיד.

הנה טבלה חשובה למדי:

מקור: גוגל

שתי נקודות שהייתי רוצה להדגיש:

  • קריאה מדיסק היא הפעולה היקרה ביותר ברשימה (נו, טוב – מלבד WAN בין-יבשתי), והיא יקרה משמעותית מפעולות מבוססות זיכרון (פי 100 עד פי 100-אלף, תלוי בתסריט).
    אם אנו מתבוננים על שכבת ה Persistence כקופסה שחורה, אזי כדאי לנו מאוד להפחית קריאות לדיסק, גם על חשבון הרבה פעולות בזיכרון. כלומר: להבין מתי בסיס הנתונים או שכבת ה ORM גורמות לקריאות לדיסק להתרחש – ולגרום לקריאות אלו לפחות ככל האפשר.
  • בניגוד לזיכרון, בו יש פער גדול בין גישה כלשהי (100ns) לקריאת 1MB של נתונים (250K ns, פי 2500), בקריאה מדיסק הפער הוא רק פי – 2. כלומר: להביא 1MB של נתונים רציפים לוקח כמו הבאה של 2 חתיכות של 4k.
    הסיבה לפער זה הוא שזמן גישה (seek time) בדיסק כוללת תנועה של זרועה מכנית וסיבוב הדיסק לנקודה הנכונה, משם קריאה רציפה היא כבר "לא סיפור".
    הערה: מגבלה זו השתפרה מאוד עם הצגת כונני ה SSD המודרניים. ניתן לקרוא עוד בנושא בפוסט מבט מפוכח על מהפכת ה SSD. עדיין, קריאה מדיסק ובמיוחד קריאה בלתי-רציפה, היא יקרה למדי. בפוסט הנ"ל ניתן לראות כונן SSD שקורא 180MB בשנייה באופן רציף, אך רק 18MB בשנייה כאשר המידע מפוזר. יחס קצב העברת-נתונים של פי 10-15 בין קריאה רציפה לקריאה אקראית הוא מאפיין שכיח בכונני SSD מודרניים. יחס זה הוא בערך פי 100-200 בכוננים קלאסיים – כך שמדובר בשיפור גדול.
כיצד נגרום לבסיס הנתונים לבצע משמעותית פחות קריאות לדיסק, מבלי לגעת בקוד של ה ORM או של בסיס הנתונים? כיצד נוכל לעשות זאת מבלי לשנות דרמטית את כל המערכת שלנו? התשובה בפסקה הבאה.

Aggregate-Based Data Storage

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

כשטוענים פרויקט:

  • נטענת רשומה מטבלת הפרויקטים
  • נטענת רשומה אחת מטבלת ה Topic (נושא אחד נפתח ב default)
  • נטענות כל רשומות הדיון מאותו ה topic (כדי להציג רשימת שמות) וכל הרשומות מהדיון הראשון, כולל כל ה comments וה attributes שלהם.

זה המידע שנדרש על מנת לספק את חווית השימוש הרצויה.

נניח מצב אופטימלי בו טבלאות הן רציפות בדיסק, ע"פ סדר הכנסת השורות.
כמה פעולות seek של הדיסק יש פה?

  • לפחות קריאה אחת עבור כל טבלה.
  • בעצם, יש אינדקסים שבהם ייתכן ויש להיעזר – כך שבפועל ייתכנו מספר קריאות לכל טבלה.
  • המידע מגיע מהדיסק בבלוקים של 4k או 16k. אם הרשומות בטבלה אינן "קרובות דיין" על מנת להיכנס לבלוק של 16k נתונים – ניאלץ "לדלג" (seek) שוב בתוך הטבלה.
    רשומות מסוג ה Comment יכולות להיכתב בהפרש זמנים ניכר אחת מהשנייה, שכן תגובות יכולות להגיע לאחר שבוע או חודש.
    רשומות מסוג ה Comment Attribute יכולות להיכתב בהפרש (כלומר, פיזור) נוסף, מאחר והן נוספות "רק ע"פ הצורך". לדוגמה: תכונת ה likesCount תיווצר רק בעת שנעשה ה Like הראשון ולא עם יצירת ה comment.
אין לי חישוב של מספר הפעולות בדיסק, אך יש לי בסיס להאמין שהוא יכול להסתיים בעשרות קריאות מהדיסק לכל discussion. השימוש ב ORM יכול להסתיר את העובדה שיצירת אובייקט "discussion" בזיכרון, רק בכדי לקחת את ה title ו lastUpdateDate – יכולה לגרום ליצירת אובייקטי ה comment והקריאה גם שלהן מהדיסק.
נקודה מעניינת, שמעצימה את הבעיה, היא שבמערכת עם הרבה מאוד פרויקטים ודיונים, הכמות הגבוהה של ה comments שמתווספים למערכת בשעה, יכולה לגרום לכך ש 2 תגובות במרחק של 5 דקות אחת מהשנייה – לא יהיו במרחק 16k בדיסק (מכיוון שנכתבו מאז הרבה comments אחרים ב discussions אחרים).
המצב הזה, בו המידע בדיסק מאורגן בניתוק מאופו השימוש בנתונים (קרי – ארצה לראות את כל ה comments מאותו דיון ביחד – ולא את כל ה comments שנכתבו בדקה מסוימת), הוא שורש הבעיה.

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

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

במקרים כאלו יש ייתרון ברור לאיגוד הנתונים ע"פ נקודת הייחוס המתאימה לשימוש הנפוץ במערכת, מה שנקרא document-based database או aggregate-database.

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

כיצד מממשים זאת?
פשוט מאוד: בוחרים נקודת ייחוס (למשל Topic, בכדי לצמצם מעט את גודל ה"קובץ" שנקרא בכל פעם), ומייצגים אותה ואת כל ההיררכיה של האובייקטים מתחתיה (למשל אובייקטי ה discussion) כרשומת JSON או XML אחת. את רשומה זו שומרים בבסיס הנתונים כ BLOB, כך שיהיה עדיין להינות משירותים של בסיס הנתונים (גיבוי, טרנזקציות וכו').

כעת, במקום להשתמש באובייקטי ה ORM לאובייקטי ה Topic ומה שמתחת – יש לכתוב מימוש מחדש, שיקרא את המידע המתאים מרשומת ה JSON ויטפל באותם הדברים שה ORM טיפל עבורנו עד כה.
במידה (כמו במקרה למעלה) שיצירה של אובייקט Topic גורמת ליצירת כל השאר – השימוש בבסיס הנתונים יהפוך ליעיל בהרבה: כל היררכית האובייקטים נקראת ממקטע רציף על הדיסק.

משמעויות נוספות
יש כמה משמעויות נוספות בגישה זו שכדאי להיות מודעים אליהן.

  • יש לממש לבד לוגיקה של קריאה / כתיבה של אובייקטים לתוך ה BLOB (מה שציינו למעלה).
  • אנו מאבדים את היכולת לעשות שאילתת SELECT על כל האובייקטים במערכת. למשל, למצוא את כל ה Comments שנכתבו בין 2 ל 4 בבוקר.
    אם אנו רוצים לבצע שאילתה שכזו – יהיה עלינו לקרוא את כל ה Topics מהדיסק, אחד אחרי השני, ולסרוק בעצמנו את המידע בתוך ה BLOB.
    אם אנו רוצים מהירות בסריקה (ויש לנו use-case ספציפי) אנו יכולים להשתמש במנועי indexing כגון Lucne.
  • השימוש בסכמה של "טקסט חופשי", כגון פורמט JSON, מאפשרת לנו לבצע שינויים לסכמת הנתונים בין הגרסאות של המוצר מבלי לבצע שינויים לסכמת בסיס הנתונים.
  • השימוש בסכמה של "טקסט חופשי" מאפשרת לנו לשלם על שדות "נדירים" רק כאשר משתמשים בהם (לדוגמה: isDeletedByAdmin) ועדיין להינות מביצועים נהדרים.

עדכון ספטמבר 2015:
הנה שני סיפורים דומים של חברות שבחרו ב"התאמה אישית" של בסיס נתונים רלציוני על פני מעבר ל NoSQL DB:

סיכום

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

כדי להינות מ BIG DATA, מספיק לעשות שינוי קצת שונה בבסיס הנתונים הרלציוני הקיים שלנו. ברור שבסיס נתונים NoSQL ייעודי יכול לתת יותר – אך לא תמיד הפער הזה מצדיק את המעבר.

שיהיה בהצלחה!

אם אתם מעוניינים ללמוד קצת יותר על BIG DATA, אתם מוזמנים לקרוא את הפוסט מה הביג-דיל ב BIG DATA?

RESTful Services – כיצד מיישמים בפועל? (2)

תזכורת: REST הוא סגנון ארכיטקטוני, המתואר כסט אילוצים שעל המערכת לציית להם. הוא מקדם שימוש נכון ומדוייק בפרוטוקול HTTP וה Web Standards. הוא "חי בהרמוניה עם ה Web" ולא רק "משתמש ב Web כאמצעי תעבורה". ארכיטקטורה ירוקה : )

קצת להכניס אתכם לסלנג של שועלי ה REST המשופשפים (לא הייתי מגדיר את עצמי ככזה):
  • ראשי התיבות REST מייצגים Representational state transfer
  • POX הוא Plain Old XML, על משקל POJO. על מנת לציין ש REST הוא XML פשוט על הנשלח על גבי [HTTP[1
  • ל REST אפשר לקרוא גם (WOA (Web Oriented Architecture – על מנת לתאר את הקשר ל SOA או (ROA  (Resource Oriented Architecture – על מנת לתאר את הקשר ל Resource-Based Distributed Systems.
  • WS-* מוקצה לחלוטין. עדיף למלמל "השם ירחם" כל פעם שמזכירים אותם ולהזכיר מיד שהם מפרים עקרונות רבים של HTTP ו ה Web (כמו למשל – ביצוע queries לקריאה בלבד ב POST).
  • שימוש רק ב URI ולא ב URL. יש הבדל קטן (URI הוא ללא ה filename), אבל מי שחי REST לרוב מקפיד לדייק.
זהו, עכשיו לא נביך את עצמנו בקרב המומחים, ואנו מוכנים לצלול לפרטים.
The REST Uniform Interface
REST מציג את החוקים הבאים:
שימוש ב URI כמתאר של resources
דוגמאות ל resources הם: instance של הזמנה, לקוח או פוסט בבלוג – המקביל ל instance של class ב OO.
כמה כללים צריכים להשמר:
  • ה URL (אני אשתמש ב URI ו URL לסרוגין. סליחה) צריך לספק שקיפות על מבנה ה Resources, כלומר:
  • כל "/" מתאר בהכרח רמה היררכית של מבנה המשאבים.
  • יש להשתמש במקף ("-") ולא בקו תחתון ("_") להפרדת מילים. כדרג אגב ע"פ התקן (RFC 3986) החלק הרלטיבי של ה URL מוגדר כ case sensitive.
  • וכו'
מידול Resource-Based
חלק זה הוא בעל ההשפעה הגדולה ביותר על ארכיטקטורת המערכת, והוא לעיתים הסיבה מדוע מימוש REST אינו ישים במערכת קיימת ללא Refactoring מקיף.
כמו שציינתי בפוסט הקודם, בעזרת ה URI אני ניגש ל Resource ישירות ומבצע עליו פעולה. ה Resource אינו מגדיר מתודות (כמו service), אלא אני יכול לבצע עליו רק את פעולות ה HTTP הסטדרטיות:
  • GET = קריאת ה resource. מקביל לקריאות פונקצניונליות כגון getOrderDetails אולי getOwner או findBid.
  • PUT = במקור מתואר: פעולת rebind של resource. הוספת resource חדש או עדכון resource קיים.
  • POST = שליחת מידע ("post") ל resource קיים. מקביל לקריאות פונקציונליות כגון executeOrder או updateOrder. שינוי ה state הקיים.
  • DELETE = מחיקת ה resource. ביצוע פעולת Delete על משאב Subscription הוא מה שהיינו מתארים ב WS כ unsubscribe().
עודכן בעקבות תיקון של אלון.למי שמכיר HTTP, מוגדרות בו יותר מ 4 הפעולות הבסיסיות הנ"ל. לדוגמא: HEAD, TRACE, OPTIONS וכו'. הגדרת ההתנהגות שלהן היא קצת פחות ברורה ופתוחה לפרשנות של מפתח מערכת ה REST.

כלל חשוב נוסף הוא שאין חובה לתמוך בכל 4 הפעולות הבסיסיות על כל משאב. ייתכן משאב שעליו ניתן לבצע רק GET ומשאב אחר שעליו אפשר לבצע רק POST. מצד שני הגדרת ה URI מחייבת שכל צומת מתאר משאב שניתן לגשת אליו. לדוגמא, אם השתמשתי ב:

אזי גם:
צריכים להיות משאבים נגישים.
ובכן, על פניו היצמדות למודל זה נראית לא מעט טרחה! מה היתרונות שאני מקבל מהם:
  • Cache: ה Scale של האינטרנט מבוסס לחלוטין על קיומם של Caches. ה Cache יכול להיות באפליקציה, שרת ה Web (למשל IIS או Apache), רכיבי הרשת או רשת ה CDN (כמו Akamai שסיפרתי עליה כאן). הכלל מאוד פשוט: קריאות GET (וגם HEAD או OPTIONS) הן cached וקריאות POST, PUT וכו' מוסיפות dirty flag על ה cache של אותו resource. אם כתבתם אפליקציות רשת רבות ואינכם זוכרים שכתבתם קוד כזה – זה מובן. המימוש נעשה ע"י שרת האינטרנט וברמות שונות של ה network devices השונים. כולם מתואמים ומצייתים לאותם חוקים. תארו לכם איזה שיפור אתם מקבלים כאשר ה router דרכו עובר משתמש באוסטרליה מספק לו את התשובה לאפליקציה שלכם מתוך cache אוסטרלי איי שם במקום להעמיס על המערכת שלכם. כל זאת, מבלי שאתם כותבים שורת קוד בודדת.
    אפליקציות רבות נוהגות להשתמש ב POST תמיד (משיקולי אורך URL אפשרי, או שקר כלשהו לגבי אבטחה) וכך מאבדות את הייתרון המשמעותי הזה. מצד שני, אם הגדרתם קריאת GET שמשנה את ה State – אכלתם אותה: ה caches לא יהיו מעודכנים[2]
  • ציוד רשת כגון Proxies, Firewalls, Web Application Firewalls מנועי חיפוש ושירותי רשת שונים מכירים את כללי ה WEB / HTTP ופועלים לפיהם. אנו נהנה מאבטחה טובה יותר, פחות בעיות לגשת לשירותים שלנו, diagnostics משופרים, ביצועים וכו'. השיפור שיושג משימוש ב CDN למשל יהיה משמעותי יותר.
  • היכולת להשתמש ב hyperlinks כשפת referencing. זה נשמע ייתרון קטן, אבל אני יכול להחזיר בתשובה לקריאה link למשאב אחר, אותו לינק הוא יציב – ניתן לשמור אותו ולהשתמש אח"כ. הוא תמיד מעודכן. זהו כלי מאוד שימושי. דוגמאות: פעולת GET על הזמנה נותנת לי links ל100 פריטים. אני יכול לסקור ולקרוא רק את הפריטים שמעניינים אותי ומתי שמתאים לי. פעולת POST שמייצרת דו"ח (או שאילתא – ברמת ה data זה בערך אותו הדבר) מחזירה לי URL שאפשר לגשת אליו כל פעם שאני רוצה לקבל את הדוח.
  • נגישות / תפוצה רחבה: כל פלטפורמה, וכל שפת תכנות כמעט יכולה לגשת למערכת שלי בקלות ללא שימוש בספריות מיוחדות (מה שלא כ"כ נכון ל WS-*). ניתן לגשת בקלות מ JavaScript או Flash. ניתן אפילו להפעיל ידנית מה Browser (למי שקצת יותר טכני).
  • פשטות: אחרי שנכנסים לראש REST הוא לא קשה במיוחד. קל לתחזק ולהרחיב את המערכת.
שימוש ב Hypermedia לניהול שינויי state
טוב, זהו נושא קצת יותר מורכב וקצת שנוי במחלוקת. אין מחלוקת שהוא חלק הגדרת ה REST, אבל פעמים רבות בוחרים לדלג עליו ולא להשתמש בו מכיוון שהוא מורכב יותר למימוש.
עקרון זה אומר שאחרי שביצעתי פעולות (לדוגמא קריאת GET של הזמנה), הפעולות האחרות הרלוונטיות האפשריות יהיו חלק מתשובת ה GET. למשל, הנה תשובה אפשרית לקריאת ההזמנה:

23
<link rel='edit'
ref='http://example.com/order-edit/ACDB' />
אני מקבל תיאור מפורש של סט הפעולות בעזרתן אני יכול להמשיך: edit עם לינק מתאים, ואת הפרטים של המוצר והלקוח.

היתרונות הם:
  • על הלקוח להחזיק / לעקוב אחר URL יחיד (Entry Point) למערכת שלי. מכאן והלאה הוא יובל בעקבות פעולותיו.
  • קוד הלקוח יכול לדעת באופן דינמי מהן הפעולות האפשריות ולאפשר אותן. השרת מצידו יכול להרחיב ולצמצם את סט הפעולות, עם הזמן, כרצונו[3].
  • אינני צריך לבצע עוד rountrip בנוסח קריאת GET ל OrderDetails על מנת לקבל קישורים למוצר או הלקוח.
כפי שאמרתי, זה נושא מורכב (מורכב = סיבה טובה להזהר) – אך הרעיון מקורי ומעניין.
Self descriptive message
יש גם עניין של הודעות שמתארות את עצמן, כלומר קריאות ללא ידע מוקדם. לדעתי זה עוזר בעיקר ל visibility ו debug – עקרון שהייתי מגדיר כנכון אוניברסלית ולא רק ל REST.
טעויות נפוצות של מימושי REST
  • שימוש ב POST לביצוע פעולות read (היה צריך להיות GET) או כל פעולה שאינה "create new". העניין הוזכר כבר למעלה. תתי בעיות:
    • התעלמות מ Caches (הוזכר למעלה)
    • נסיון להעביר XML שכולל מידע / פעולות מעורבות או שלא קיימות בסט המצומצם. לדוגמא פרמטר POST בשם operation ואז חזרה לעולם ה Services הוא טעות נפוצה מאוד של מתחילים.
  • בניית URI בעזרת Query Parameters (רמז: Query param אמור לשמש ל… Query)
  • שימוש לא נכון או שיכפול יכולות שקיימות כבר ב HTTP. דברים כמו:
    • Status / Error Code. תזכורת: פרוטוקול HTTP מותיר להוסיף לשגיאה טקסט חופשי (= גוף ההודעה).
    • Cookies
    • Headers
    • MIME Types
  • נסיון לשמור Server Side State על כל client. בעיה אוניברסלית ל Web.
  • המנעות מהוספת לינקים לתשובה: אמנם שימוש בלינקים (hypermedia) לניהול כל ה state הוא עקרון שנוי במחלוקת, אך המנעות מלינקים בכלל – נשמעת טעות. לא טוב להסתמך על קוד ב client שמתאר את מבנה ה API לשוא וגם חבל ליצור קריאות מיותרות.
  • נסיון לבצע Implicit Transactions.
    במוקדם או במאוחר תזדקקו ל Transactions. הדרך המומלצת לטעמי הוא לייצר resource שמתאר את ה transaction. כל דרך אחרת לעשות זאת בצורה לא מפורשת שנתקלתי בה – נגמרה בכאב.
מקווה שנהנתם.
 
[1] על מנת לדייק זה לא חייב להיות XML, ועקרונות ה REST יכולים להיות מיושמים גם ללא HTTP – פשוט אפשר להשתמש בהרמוניה בפרוטוקול אחר ובאותה הרוח ש REST "מתלבש" על HTTP.
[2] יצא לי להיתקל במערכת "REST" דיי גדולה ומורכבת שלא הקפידה על הכלל והיו לה בעיות עם Caches לא מעודכנים. המפתחים שלה התחכמו והוסיפו HEADER לכל קריאות ה GET שציין שאסור לשמור את הקריאה ב Cache. בעולם ה Security קוראים לזה (SDoS (Self Denial of Service
[3] ניתן להסתכל על זה כ interface דינאמי. ב Web Services הייתי קורא WSDL וה IDE היה יוצר לי stub – שזה מאוד נחמד. מצד שני, לכאורה, לא הייתי יכול להגיב לשינויי Interface ללא שינויי קוד.
אני אומר לכאורה מכיוון שיש כמה דרכים לבצע זאת בכל זאת (בצורה קצת יותר מסורבלת)

לינקים רלוונטים:
http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api

מה הביג-דיל ב Big Data? (או NoSQL)

הקדמה
אם הייתם עסוקים במיוחד בשנה-שנתיים האחרונות, ייתכן והרמתם את הראש ונוכחתם שהמושג Big Data מוזכר לעתים קרובות, מבלי שאתם מבינים בדיוק על מה מדובר.פעם (לפני כעשור), מערכות ה High Scale היו בעיקר מערכות ה Enterprise המיועדות לארגונים גדולים. לחברת Siemens יש 120,000 משתמשים (users)? וואהו – אתגר ארכיטקטוני מרשים. מערכת המסחר של וול סטריט מבצעת מאות פעולות (transactions) בשנייה אחת? – בלתי נתפס!
חברות ענק כמו SAP, Oracle ו IBM הן אלו שבד"כ התמודדו עם אותם scales , בעיקר בזכות העובדה שאותם לקוחות-ענק קנו מערכות מורכבות שיצרנים אלו סיפקו. יצרן קטן – scale קטן, יצרן גדול – scale גדול.

מאז שנת 2000 האינטרנט פרץ למיינסטרים. מוצרים כמו MySpace ו SecondLife ואח"כ Facebook ו Youtube עקפו בסיבוב את חברות הענק וטיפלו במיליוני משתמשים, petabytes של שטחי אחסון ואלפי (tps (transactions per second. בהדרגה, עוד ועוד חברות קטנות נאלצו להתמודד עם scales אדירים. הכלים להתמודד עם כמויות גדולות של נתונים (כמו פתרונות של Teradata וOracle) גם היו יקרים מדי עבור אותן חברות קטנות, וגם לעתים לא עמדו בקיבולת המבוקשת – וכך אותם ארגונים החלו לייצר חלופות זולות ויעילות להתמודדות עם scale ענק. לא סתם Facebook, Amazon או Twitter שרדו מבין חברות דומות (שלעולם לא שמענו או נשמע עליהן). מלבד הרעיון המגניב, היה צריך להתמודד עם אתגרים טכנולוגיים יוצאי-דופן. רעיון מגניב + ניהול נכון + מצוינות טכנולוגית הוא שילוב נדיר אשר היה דרוש להצלחה.
בסולם ה Scale הוגדר ערך חדש הגבוה מהערך הגבוה הקודם ("High Scale"). מעתה, אמרו "Internet Scale".

הבעיות
הערה:כמו שצוין למעלה, ב Big Data יש עניין מוצהר – טיפול בכמויות אדירות של נתונים, ועניין לא מוצהר, אך לא פחות חשוב – פתרון זול המתאים גם לחברות קטנות. עדיף Open Source, עדיף מאוד Commodity Hardware (שרתים במחיר של, נאמר, עד 10K$ כל אחד)

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

הגדילה בתעבורה של אתר Netflix כמו שדווח על ידם

כאשר יש לכם כמויות גדולות של נתונים, אתם צריכים:

  • מספיק CPU לקבל ולטפל בו.
  • שיטה לאחסן אותו (אחסון "פיזי" – מערכת קבצים, או אחסון "לוגי" – בסיס נתונים).
  • יכולת לבצע שאילתות / חיפושים על חלקים גדולים מה data.

למרות ש MySql (או כל בסיס נתונים רלציוני) הוא מוצלח, ישנו גבול של נתונים שהוא יכול לטפל בו. הגדילה הראשונה היא כנראה לקנות שרת יותר חזק (vertical scalability הקרוי גם scale-up). השרת יטפל בפי n כמויות מידע ויעלה לרוב משמעותית יותר מפי-n.
השלב הבא, ברוב בסיסי הנתונים, הוא ליצור cluster של בסיסי נתונים (horizontal scalability הקרוי גם scale-out), בחוות שרתים שלכם או ב Cloud.
איך מטפלים בכפילות מידע? דרך אחת היא ששרת אחד מוגדר לכתיבה בלבד (ומעדכן את השאר) ושאר השרתים רק מבצעים שאילתות קריאה ומשחררים עומס מהכותב. כמו שאתם מבינים זהו בסה"כ קצת אוויר לנשימה.
Partitioning של המידע: לאחסן על כל בסיס נתונים (או cluster כמתואר למעלה) פלח מידע שונה, לדוגמה: פילוח ע"פ מדינות, אות ראשונה בשם המשתמש וכו"
offloading to data warehouse – מדי כמה זמן להעביר את המידע שאליו ניגשים פחות (לרוב מידע ישן) לבסיס נתונים אחר ("מחסן") כדי לגרום לבסיס הנתונים עליו המידע החשוב להיות זמין יותר.

בשיטת ה Partitioning יש שתי בעיות עקרוניות:
א. אם מפצלים נתונים לשרתים שונים, יש להתפשר או על זמינות (availability) או על עקביות הנתונים (consistency) – עקרון הידוע כ CAP Theorem (כלומר – ללא ACID).
ב. שאילתות הכוללות מספר בסיסי נתונים אחד הן מורכבות למימוש ואיטיות במיוחד (במיוחד אם דורשות נעילות). למרות שלספקי בסיסי הנתונים היו שנים רבות ללא תחרות קשה בתחום – הם לא פיתחו את התחום בצורה משמעותית.

Big Data
אותן חברות סטארט-אפ שלא יכלו לשלם על פתרונות יקרים ונזקקו לכלים לטפל בכמויות אדירות של נתונים פיתחו כמה מהפלטפורמות הבאות שרובן הגדול זמין כיום כ Open Source (מסודר ע"פ תחום):

  • CPU – לרוב אין צורך בפתרון מיוחד. מקימים Cluster עם שרתים זהים ובעזרת Load Balancer מחלקים לכל אחד מנה מהתעבורה. פתרון שקיים כבר שנים.
  • אחסון פיזי: S3 של אמזון, GFS של גוגל או HDFS* של אפאצ'י (היחידי שזמין לקהל הרחב).
  • אחסון לוגי: אלו בסיסי הנתונים המפורסמים (הסבר בהמשך) השייכים לאחת מארבע קטגוריות:
    • Document Oriented
    • Columnar DB
    • Graph Database
    • Key-Value DB
  • יכולת לבצע שאילתות מבוזרות: Hive, Hadoop Map-Reduce, Cascading, MrJob ועוד.
בסיסי נתונים NoSQL

פירוש השם NoSql התחיל כ "לא צריך SQL", אולם עם הזמן התפכחו הדוברים והבינו שאין כאן Silver Bulltet – לרוב המקרים בסיס נתונים רלציוני עדיין מתאים. היום ההסבר השגור לשם הוא: "Not Only SQL".
הרעיון פשוט למדי: בסיסי הנתונים הרלציונים הם עשירים ומורכבים: הם מאפשרים שאילתות SQL מורכבות (שאילתות מקוננות, סיכומים סטטיסטים ועוד), הבטחת פעולות ACID**, אכיפה של constraints, טריגרים ו Stored Procedures ועוד. מה היה קורה אם היינו מוותרים על רוב היכולות ומתמקדים רק במערכת היכולה לטפל ב Scale מרבי למקרה הספציפי?
Disclaimer: ההסברים הבאים הם קצת פשטניים, אבל עדיין יכולים להסביר הרעיון העיקרי עליו מבוססת כל קטגוריה של בסיס נתונים NoSql-י.
Document(-Oriented) DB
לעתים קרובות אנו רוצים לייצג רשומות היסטוריות רבות: לדוגמה סיכום עסקאות ברשת סופרמארקט.
אם נלך לפי העיקרון האקדמי של בסיס נתונים רלציוני ומנורמל תהיה לנו כנראה טבלת סניפים (נאמר 20) המקושרת לטבלת עסקאות (נאמר מיליון בשנה, עשר שנים = 10 מיליון), המקושרת לטבלת פריטים בעסקה (Line Item), נאמר 20 בממוצע בעסקה.
טבלת הפריטים של כל המערכת תהיה בגודל של 4 מיליארד רשומות. כלל האצבע בבסיסי נתונים הוא שמעל 10 מיליון רשומות בטבלה – בסיס הנתונים מתחיל להגיב לאט. כלומר-  לא מעשי. בנוסף הכנסה מקבילית של הרבה קופאיות לאותן טבלאות (מקושרות) ייצור רצף נעילות שיגביל מאוד את המערכת (עוד קצת על מקביליות ונעילות – בתחתית הפוסט) מצד שני אנחנו יכולים לקחת את ההנחות המקלות:
  • אנו שולפים או שומרים כמעט תמיד עסקה בודדת – ואנו רוצים ששליפה זו תהיה מהירה.
  • שאילתות רחבות הן נדירות ואנו מסכימים שיקחו הרבה מאוד זמן.
פיתרון קיים (יצא לי פעם להתנסות בו) הוא במקום טבלת הפריטים – לייצר בטבלת העסקאות עמודה מסוג "string" המכילה XML או JSON עם פרטי העסקה. זהו שיפור משמעותי ב scale, מכיוון שיש לנו פחות rows, פחות אינדקסים לתחזק ופחות joins להריץ. מצד שני יש יותר custom code שצריך לכתוב – עבור דברים שהיינו מקבלים קודם בשאילתה. יתרונות אחרים של גישת ה Document Oriented DB הן שניתן לשנות את הסכמה מבלי לבצע Alter table יקר ואם ישנן עמודות דלילות ב DB – לא נבזבז עליהן מקום מיותר (מה שנקרא Semi-structured data).
בסיסי נתונים Document Oriented נותנים פיתרון מובנה לעקרון זה שכולל לרוב API פשוט לשימוש וכלי שאילתות מבוזר נוסח Map-Reduce. דוגמאות הן MongoDB ו CouchDB. במקום להמציא את הגלגל – קחו Open Source.
Columnar DB
קטגוריה זו, הידועה גם כ Vertical DB פותרת בעיקר בעיות של Data Warehousing: איך לבצע שאילתות יעילות על מחסני נתונים. דמיינו טבלת ענק עם 10 עמודות בגודל x בתים (bytes) כל אחת.
לרוב בשליפות ה SQL אנו שולפים עמודות בודדות. אם הרצתי שאילתה על 3 עמודות, בסיס הנתונים עדיין צריך לקרוא מהדיסק לזיכרון את כל עשרת העמודות – כלומר פי 3 מידע ממה שנדרש. הקריאה מהדיסק היא הפעולה היקרה. אם הייתי מאחסן כל עמודה בקובץ נפרד בדיסק, אולי הייתי מוגבל בשאילתות מורכבות מאוד, אולם השאילתות הפשוטות היו דורשות משמעותית פחות עבודה של הדיסק.
במחסני נתונים, לעתים קרובות, רוצים לבצע חישוב ממוצע / התפלגות ערכים / whatever על עמודה בודדת (ומספרית) מתוך טבלה הכוללת הרבה עמודות שחלקן הגדול הוא מחרוזות (התופסות נפח גדול בהרבה). היכולת לטעון מהדיסק עמודה בודדת יכולה להאיץ את ביצוע השאילתה בעשרות מונים.
דוגמאות בולטות הן Vertica או InfoBright. חברת SAP זכתה לתשואות כאשר באופן מפתיע הצטרפה לחגיגה עם HANA – גרסה In-Memory של בסיס נתונים columnar. כולם פתרונות שאינם open source.
Graph DB
בסיסי נתונים רלציונים הם גרועים במיוחד בתיאור גרפים. נניח שהייתם מפתחים אפליקציה כמו LinkedIn והייתם רוצים לדעת מה הקשר בין שני אנשים, מתוך מאגר של 100 מיליון משתמשים.
ישנו כלל מקל האומר שכל שני אנשים מקושרים ע"י שרשרת של 6 הכרויות. מה נעשה? Join משושה של 100 מיליון משתמשים?? קחו טיול חצי שנה לדרום אמריקה לפני שהשאילתה תסתיים***.
אם היה לכם בסיס נתונים שמייצג גרפים בצורה נבונה, ייתכן והוא היה יכול לעשות שאילתה כזו בפחות משנייה. יש הרבה שימושים ואלגוריתמים נוספים לגרפים שבסיסי נתונים אלה מספקים.דוגמאות בולטות הן Neo4J ו HyperGraphDB.
Key-Value DB
האם הייתם רוצים להשתמש ב HashTable בגודל של מיליארדי רשומות – שגם יאפשר מקביליות גבוהה? כמה נוח. Key-Value DB יספקו זאת, רק אל תבנו על (O(1 בזיכרון. זוהי פרדיגמה פופולרית במיוחד בקרב חברות אינטרנט:
גוגל יצרה את BigTable ושחררה מסמך מפורסם החושף ארכיטקטורה מהפכנית, אמזון יצרה את Dynamo ושחררה מסמך מפורסם אחר – לא פחות מרשים.
פרויקט Hadoop מימש את התכנון של גוגל ויצר את HBase ו LinkedIn מימשו את התכנון של אמזון ויצרו את Voldemort (כן, הנבל מהארי פוטר).
פייסבוק ערבבה רעיונות של גוגל ואמזון ויצרה את Cassandra שזוכה להצלחה רבה. יש גם את Redis שהוא In-Memory Database המבוסס על אותה פרדיגמה.
סיכום
Big Data היא מגמה. היא הולכת ותופסת תאוצה ומציגה סט חדש של כלים. מה שחשוב הוא להבין כיצד כלים אלו עובדים, מה המגבלות שלהם (ויש!) ולהתאים כלי – למשימה. בסיסי הנתונים הרלציונים עדיין מצויינים ומתאימים לפתור את רוב בעיות המידע. המסר הכי חשוב לדעתי הוא: הרשו לעצמכם לחשוב ולפעול מחוץ לקופסה. אם פתרון לא מרגיש לכם מתאים, אל תפחדו לצאת מהזרם וליצור משהו הגיוני שיעשה לכם את העבודה. אולי תגלו שאתם שותפים, עם חברות רבות אחרות, לאותה החלטה.

עדכון 1: תודה ליקיר שהזכיר את RavenDB, בסיס נתונים Document עבור פלטפורמת .NET שפותח בארץ ע"י אורן עיני – מפתח ישראלי בולט.

עדכון 2: ripper234 העיר ובצדק (בפרסום ב newsgeek): "מה שהייתי שם בפתיחה למאמר כזה שאסור להתאהב בטכנולוגיות וב-"Big data". פגשתי יותר מדי אנשים שהחלום הרטוב שלהם זה Big Data / NoSql / Technology Buzzwords, אבל שוכחים בדרך את האג'יליות ואת העבודה שבסטארט-אפ עובדים בשביל ליצור value אמיתי, כמה שיותר מהר, ולא מערכת מטורפת שתחזיק 100 מיליון יוזרים 5 שנים קדימה אבל תיקח שנתיים לפתח."

במאמר זה ניסיתי להתמקד בטעות נפוצה אחרת: המחשבה ש Big Data הוא Silver Bullet, שהוספת בסיס נתונים NoSQL תפתור תכנון בסיסי לקוי. ניסיתי להציג את אותם בסיסי נתונים ולהסביר "מה הטריק שלהם", כי לפעמים בצורה מאוד נקודתית ניתן לממש את הטריק הזה לבד ללא מעבר full-fledged לבסיס נתונים שכזה.

* Hadoop Distributed File System השייך לפרויקט העל Hadoop. השם Hadoop הוא שמו של פיל-הצעצוע האהוב של בנו הקטן של מפתח הפרויקט – ומכאן לוגו הפילון.**  ACID – Atomic, Consistent, Isolated and Durable הרי הן ה transactions.

*** סתם. ה DB יקרוס אחרי כמה עשרות דקות.