2012-02-26

חומרה: מבט מפוכח על מהפכת ה SSD

טוב, אני יודע שרוב הקוראים של הבלוג הם אנשי תוכנה. מה לעזאזל יש למתכנת הג'אווה / #C להתעסק בברזלים של המחשב שמתקין בכלל איש ה-IT?
"מבחינתי שיהיה בצבע ירוק עם כוכבים, העיקר שהקוד של IFile יעבוד" - יכול לומר מתכנת אפליקציה שאינה דורשת ביצועים גבוהים.

התמונה משתנה כאשר אנו זקוקים לביצועים טובים יותר. המערכת שלנו היא לא רק התוכנה אלא גם החומרה - והבנה מה קורה שם למטה יכולה להיות כלי רב-עוצמה.

הנה 37Signals הראו שלא תמיד זקוקים לארכיטקטורה מתוחכמת כדי להשיג Internet Scale, צריך רק טרה זיכרון וכונני SSD על כל מכונה. רגע! טרה זיכרון?? - מסתבר שטרה זיכרון עולים כיום 12 אלף דולר - משכורת וחצי של מתכנת בארה"ב.

חכו רגע... אני שומע קולות ביקורת:
"בעיות ביצועים יש לפתור בתוכנה - לא בחומרה"
"הממ לא מבינים כלום, זה טוב להתחלה - אבל הם לא ישרדו"
"SSD יקרים מדי"
"SSD נשחקים. פתאום כל המידע שנשמר עליהם יעלם - ואז מה?"

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

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

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


כך רכיבי המחשב הבסיסיים:
מעבד מרכזי (CPU), זיכרון (memory), כונן קשיח (HD) ורשת (network).
בואו נבחן את קצב ההתפתחות שלהם:

בראש אצים-רצים המעבדים, המכפילים את כוחם פי 2 כל 18 חודשים, או פי 100 בעשור. כיום הם כ"כ מהירים כך שהם הפכו במידת מה למשאב עודף: רוב המחשבים הבייתיים לא זקוקים ליותר מהמעבד הבסיסי ביותר. "גיימרים" משקיעים את כספם במאיצים גרפיים במקום במעבד ובחוות השרתים יש מגמה לקנות המון זיכרון על חשבון כח העיבוד (כל נושא ה In Memory Database).
אינטל - שחייה ממכירת מעבדים החלה להוסיף יכולות של כרטיס גרפי ישר לתוך ה CPU ולהתמקד בצריכת חשמל נמוכה [1]. מהירות המעבד הפכה כמעט ל Commodity.

מקום שני אצה-רצה הרשת, עם קצב גדילה של מרשים 50% בשנה או פי 57 בעשור.
אמנם הרשת החלה בנקודת פתיחה אומללה (למי שזוכר מודמים של 1200 סק"ש - שבהתחשב בסיביות הביקורת זה 120 בתים בשנייה) - אך היא מתקדמת בקצב מרשים ביותר וכיום: "אצלנו - אינרנט של 20Mbps זה סטנדרט!" [2]

מקום שלישי נמצאים הכוננים הקשיחים. אין לי נתונים מדוייקים, אך שיערוך גס שעשיתי מכמה גרפים מציג התקדמות בקצב ההעברה (transfer rate) של פי 15 עד 20 בעשור. קצב העברה של כוננים מגנטיים דיי נבלם בשנים האחרונות והוא עומד על כ 100MBps בערך [3]. יש לציין שנפח האיכסון, פרמטר חשוב אחר, גדל מעריכית ובקצב גדול בהרבה - בערך פי 100 בעשור.

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

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

מהו כונן SSD? (בקצרה)
SSD (קיצור ל Solid State Drive) הם כוננים מבוססי טכנולוגיה של זכרונות פלאש (בניגוד לדיסקים מגנטיים).
מכיוון שאין להם חלקים מכניים הם שקטים יותר, צורכים פחות חשמל, ויכולים לספוג זעזועים קשים מבלי להפגע (לא באחרויותי!).

ברשת ניתן להתקל במספרים לא מבוקרים שמצוטטים גם באתרי חדשות חשובים כגון "SSD מהיר פי 1000 עד 10000 מכונן קשיח רגיל". זה לא מקרה נדיר שאתרי חדשות מובילים מפרסמים מידע לא רציני. את הדיוק העיתונאי הם שומרים בעיקר למדור הפוליטיקה - שם יקנסו ב 300 אלף ש"ח על כל טעות - על פי חוק חדש. אם תתקינו כונן SSD חדיש במחשב שלכם תזכו לזמן טעינה של תוכנות הקצר בממוצע פי 2. כלומר, וורד יטען ב 2 שניות במקום ב 4 שניות. מי שקנה כונן SSD בעקבות כתבה "מהיר פי 1000 ויותר " פשוט עלול להתאכזב.

שלא תבינו לא נכון: קרוב לוודאי ש SSD הוא השדרוג הכי משמעותי שאתם יכולים לבצע כיום במחשב שלכם.


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


הבלוף של יצרני ה SSD (או למה RAID 0 מת)
עם הזמן, הופכים כונני ה SSD למוצר שיכול לרכוש גם המשתמש הביתי / הסטארט-אפ הקטן. בפחות מ 1000 שקל ניתן לקנות כונן בגודל 120GB שיכול להגיע לקצב קריאה של כמעט 600MBps! פי שש מכונן מגנטי מהיר!

SSD - שימו לב כמה זמן הגישה הוא מהיר! כך זה שכל מה שזקוקים לו הוא זרם חשמלי ולא זרוע מכנית שצריכה לנוע.

ווהו וואו - בטח אתם חושבים לעצמכם "אני אהיה ממש טיפש עם לא אקנה אחד כזה". "אולי אפילו אקנה שניים".

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

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

מקרא: Sequential - הוא קצב קריאה רציפה (נאמר קובץ של 1M ומעלה) ויש גם נתונים על קריאה של קבצים קטנים יותר.

מקור: theSSDReview.com

מקור: theSSDReview.com

ברור לי שאתם קוראים נבונים ותבחרו את הדיסק שמופיע במסגרת הכחולה. בעוד כונני SSD מציגים קצב קריאה של 500MBps לקריאה רציפה - נתון זה הוא לא מעניין כי פחות מ1% מהפעילות הרגילה של כונן קשיח היא פעילות על קבצים בגודל 512K ומעלה (למשל: העברת סרט וידאו מדיסק לדיסק. בתוך אותו הדיסק השינוי הוא רק ברישום של מערכת הקבצים) קריאה וכתיבה של קבצים בגודל 4K קוראת בקצב של עשרות MBps בכוננים המהירים ביותר.

שני נתונים החשובים על מנת להבין את התמונה השלמה:
  • דיסק מגנטי קורא קבצים בגודל 4K בקצב של פחות מ 1MBps.
  • בשימוש ממוצע בכונן הקשיח - כ 70% מהפעולות הם על קבצים (ליתר דיוק "בלוקים") בגודל 4K או 8K - כך שנתון זה הוא בעל משמעות מכריעה.
כאשר מציבים את המשקולות של אחוזי השימוש - ברור למדי שדיסק קשיח יש לבחור ע"פ פעולות הכתיבה של בלוקים ("קבצים") בגודל 4K ו 8K.

כונן SSD לא יעתיק קבצי ענק בצורה מהיר הרבה יותר והוא לא יגרום לתוכנות להטען מהר הרבה יותר.
אך אם יש לכם בסיס נתונים או Cache שמבצע המון פעולות קריאה וכתיבה של בלוקים קטנים וללא הרף - ההבדל המעשי הוא בין כ 300 פעולות לשנייה האפשריות על דיסק מגנטי מהיר במיוחד מול כמה עשרות אלפי פעולות לשנייה האפשריות על דיסק SSD מהיר במיוחד. פאקטור של פי 100-200 יותר פעולות. מהפכה.

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

אם הייתי עושה זאת הייתי מתאכזב מ 2 סיבות:
א. בשעה 11 וחצי בלילה קשה למדי למצוא מישהו שימכור לי כונן SSD.
ב. אם הייתי שם את המערכת שלי על כונן SSD ממוצע במחיר 1000 ש"ח קרוב לודאי שהוא לא היה שורד חצי שנה. יותר גרוע: הוא היה הופך איטי יותר ויותר ופתאום המידע היה נעלם.  חתיכת באסה!

רק בן חצי שנה - אבל נראה כמו בנג'מן באטן
העניין הוא שכונני SSD עוברים שחיקה מהירה.

מכיוון שאין זה פוסט חומרתי טהור אדלג על הפרטים למה. הנה הסבר מדוע ולמה.
בחלונות 7 הוסיפו תמיכה בפקודת TRIM המאפשרת מחיקה של בלוקים גדולים יותר על כונן ה SSD - ולכן מפחיתה משמעותית את השחיקה שלהם.

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

לעצם כך ישנם כונני SSD המוגדרים כ Enterprise Class - כוננים אשר לרוב משתמשים בטכנולוגיית SLC היקרה והנשחקת פחות ומשתמשים ב"ספיירים" של תאי זיכרון - ומתוכננים להחזיק מעמד כמה שנים תחת עבודה ממושכת. הם יעלו בערך פי 4 יותר, אם מדובר בשרת ממוצע. עבור מקרי קצה של כתיבה אינטנסיבית במיוחד ודרישות לכונן יציב כסלע - טושיבה תמכור לכם כונן 400GB אותו תוכלו לטחון ללא-אבחנה במשך 11 שנה במחיר פעוט של 7000$ לחתיכה.

וריאנטים אחרים של כונני SSD לשרתים יכולים לספק מהירויות גבוהות במיוחד ולרוב מתחברים ישירות לחריץ ה PCI-Express על מנת לדלג על בקר הדיסקים (חיבור SATA) על מנת לספק בערוץ ישיר נתונים לתוך המעבד במהירות מסחררת.

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


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

מדוע שלא תקנו דיסק מגנטי גדול וזול ועוד דיסק SSD קטן, נאמר 64GB, ואת כל הגישות שצריכות מהירות גבוהה תבצעו רק עליו? תאזנו בין נפח אכסון (דיסק מגנטי) למהירות (כונן SSD).

ייתכן ויהיו שימליצו לכם "לקנות דיסק SSD עבור מערכת ההפעלה". זוהי גישה מיושנת (אולי בת שנתיים) ולא כ"כ יעילה - רוב רובם של הקבצים של מערכת ההפעלה שוכבים כאבן שאין לה הופכין. הכנסו למשל בחלונות 7 לתיקיה C:\Windows\winsxs והסבירו לי מה מאכסנת שם מערכת ההפעלה בנפח של קרוב ל 10GB.

הגישה החדשה יותר (שנות העשרה) היא לתת לתוכנה לסדר לכם את הקבצים בהם משתמשים בתדירות גבוהה על ה SSD בעצמה: הקבצים אליהם ניגשים הרבה - על SSD וכל השאר - על הדיסק הגדול והאיטי. ניתן לעשות זאת בעזרת כונן SSD יעודי לתפקיד Caching כגון OCZ Synapse או Crucial Adrenaline המסופקים עם תוכנה מתאימה [5].

ישנם כוננים המגיעים בחבילה אחת האורזת דיסק מגנטי ודיסק SSD כגון Seagate Momentus XT (מה שנקרא דיסק היברידי - אם אחרי הפרסומות לבנק מזרחי אתם יכולים עוד להתייחס למילה הזו ברצינות).

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

עדכון 11.11.12: אפל הציגה גרסה משלה לכונן היברידי.

כך נראה הדיסק ההיברידי הראשון. פשוטו כמשמעו.

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

בסיסי נתונים ימשיכו להזרים מזומנים לקופות יצרני ה SSD הגדולים על מנת לשמר שרת יחיד לבסיס הנתונים - פיתוח מערכת מבוזרת עדיין תעלה יותר מכל מכל SSD שהם יכולים להציע...

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




[1] היום, בחדרי השרתים, בוחרים מעבדים ע"פ כח חישוב לוואט. על כל וואט משלמים לא רק בחשבון החשמל. מכיוון שקירור הוא כבר קרוב לאופטימום על וואט עולה גם בחשמל של מערכת המיזוג ובנדל"ן - יש גבול כמה ניתן לקרר מטר מעוקב מבלי שהעלויות יזנקו.

[2] חבר סיפר לי שבקליפורניה, לפני שנתיים, 100 מגה היה דיי מקובל בקרב חובבי מחשבים. הנה נתונים עולמיים של הממוצע הביתי. לא שחשבתי שלקחתם את הפרסומת ברצינות.

[3] יש להבחין שנפח איכסון / קצב העברה של רכיבי אכסון נוהגים למדוד בבתים בשניה (Bps), בעוד מהירות של רשת מודדים בביטים לשנייה (bps) - מידה הקטנה פי 8 (כי יש 8 ביטים בבית). יש לשים לב האם האות B היא קטנה או גדולה על מנת לא להתבלבל.
לכן, כשאתם גולשים באינטרנט "5 מגה", או 5Mbps, - אתם יכולים להעביר רק "625KBps".

[4] שחיקה זו היא הסיבה מדוע ממליצים לא לבצע Defrag על כונני SSD. התמורה מאיחוד קבצים היא זניחה עבור כונן SSD (שקורא קבצים קטנים במהירות) - והשחיקה היא רבה.

[5] התוכנה, בשם Dataplex, יודעת לנהל ברזולוציה אפילו יותר מדוייקת מקבצים: רזולוציה של בלוקים של מערכת הקבצים - שגודלם לרוב 4K.


2012-02-19

הורשה היא הפרה בוטה של עקרונות ה Object-Oriented!

כן. הורשה היא הפרה בוטה של עקרונות תכנות מונחה-עצמים (OO).

אני מבין שאמירה כזו היא בגדר שחיטת פרה קדושה - הרי הורשה הוא העיקרון הכי מזוהה עם OO.
בואו נבחן את הנושא לעומק.


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

נקודה בהולה לתשומת-לב האוניברסיטאות? (כנראה שלא... מבחינתם העיקר שתדעו משוואות דפירנציאליות חלקיות ואוטומאטים).

אז בואו נעשה קצת סדר: בצורה דיי עקבית, בספרי תכנות בעברית (למי שיצא לעיין), מופיעים שלושת העקרונות בסדר הבא:
  • הורשה (inheritence)
  • ריבוי-צורות (polymorphism)
  • הכמסה (encapsulation) - או כימוס, כפי שהעירו. תודה.
זהו בדיוק סדר החשיבות ההפוך של עקרונות ה OO!


אין לי מושג מה הסיבה לסדר ההפוך. אולי ספר אחד חטא וכל השאר העתיקו. בכל מקרה: הנה סדר החשיבות הנכון:
  • הכמסה (private)
  • ריבוי-צורות (implements / instanceof / interface / upcasting / downcasting)
  • הורשה (extends / protected / super / @Override)
(בסוגריים ציינתי את הכלים בג'אווה המשרתים כל עקרון. #C הוא דומה למדי).

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

זוהי קפיצת המדרגה העיקרית מול שפות פרוצדורליות שניהלו את המידע בצורה גלובלית (נאמר C).

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

הסבר קצר: שפת ++C הכילה יכולת מקבילה ל abstract בג'אווה / #C שנקראה virtual. מפתחים יכלו להגדיר מחלקות שכל המתודות שלהן virtual (מה שנקרא pure virtual class) וכך לקבל באופן עקיף משהו מקביל ל interface בג'אווה / #C. לא היה לממשק, כפי שאנו מכירים אותו בשפת ג'אווה, שום ייצוג רשמי בשפה - זה היה [1] Pattern.

האבחנה הברורה בין ריבוי-צורות והורשה הייתה לנחלת הכלל רק כאשר הגיעה ג'אווה, שפה שלקחה את עקרונות ה OO צעד אחד קדימה והגדירה אלמנט חזק בשפה לממשק, הרי הוא ה interface.

אט אט, שמו לב עוד ועוד מפתחים שב interface הם משתמשים המון והוא עובד מצוין, אבל בהורשה הם משתמשים פחות - ויש עם הורשה כמה בעיות.

מה הבעיה עם הורשה?
התובנה על המגבלות/בעיות בהורשה לא נתגלו עם ג'אווה. עוד בשנת 1992 הציג סקוט מאיירס, בספרו האלמותי "++Effective C" מספר בעיות עם הורשה והזהיר: "use inheritance judiciously". שנתיים אחר-כך, בספרם המפורסם של GoF, הדבר נאמר יותר במפורש: "Favor object composition over inheritance" - נדבר על כלל זה בהמשך.

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

רוב הסיכויים שמפתחים ותיקים ומנוסים ירצו עדיין להשתמש בהורשה [2] ועבור מפתחים צעירים (הייתי אומר 5 שנים או פחות) - מומלץ לדסבלה (disable it).

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

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


public class InstrumentedHashSet<E> extends HashSet<E> {
  private int addCount = 0;
  ...
  @Override public boolean add(E e) {
    addCount++;
    return super.add(e);
  }

  @Override public boolean addAll(Collection<? extends E> c) {
    addCount += c.size();
    return super.addAll(c);
  }

  public int getAddCount() {
    return addCount;
  }
}



InstrumentedHashMap היא הורשה שנוצרה על מנת לספור את מספר הפעולות על הרשימה.הריצו לרגע את הקוד בראש: אם אוסיף אוסף של עשרה איברים, מה תהיה התוצאה של getAddCount?

התשובה היא: It Depends. 
אם אני רץ ב JDK 5 או ישן יותר התוצאה תהיה 20, אולם החל מ JDK 6 התוצאה תהיה 10. באופן רשמי ה API של HashSet לא השתנה כלל בין גרסאות ה JDK, אולם כאשר אני משתמש בהורשה אני נחשף ל API לא מפורש ולא מתועד: הקשר בין המתודות. במקרה שלנו זוהי הנקודה האם addAll קורא למתודת add או לא. כאשר אני יורש, אני יורש גם את הקשר הזה בין המתודות. בJDK 6 החליטו לוותר על הקריאה ל add מתוך addAll (עבור שיפור ביצועים) וכך שינו את החוזה הלא מפורש - אך שמשפיע בהחלט על המימוש שלי ל InstrumentedHashSet.
זהו בדיוק ה"שינוי הפנימי" שלא צריך להפריע לאף אחד אך במקרה שלנו, בעקבות ההורשה, הוא שובר פונקציונליות.

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

הורשה פוגעת בהכמסה: אם מחלקת האב הכריזה על Serilazible, Clonable, Entity וכו' - המחלקה היורשת יכולה לשבור הגדרה זו בכל רגע בלי יכולת להכריז על ההיפך. יותר גרוע: ירשתי ממחלקה שלא הייתה Serializable ולאחר שנה מחלקת האב הפכה לכזו - הקומפיילר לא יאמר לי דבר. הבעיה תשאר בעינה.

נו... אני מקווה שהבהרתי את הנקודה.

מה הפתרון? הפתרון הוא להשתמש בקשר הרכבה בין אובייקטים, מה שנקרא Composition. הוא כ"כ נפוץ ושימושי כך שהוא קיבל סימון משלו ב UML. אני אמנע מלכתוב על נושא שכבר תועד רבות. למי שמתעצל לחפש, הנה מקור קצת ישן, אך שנראה טוב:
http://www.artima.com/designtechniques/compoinh.html

סיכום
המסקנה ברורה: אם העיקרון החשוב (הכמסה) סובל מעקרון משני (הורשה) - זו סיבה מספיקה להחרים את העיקרון הסורר והפחות חשוב.

לצערי, עם כל זרם השפות החדשות שנוחת עלינו בשנים אלו (לינק) אף אחד לא הכחיד את ההורשה. הייתי שמח לראות וריאנט של ג'אווה או #C שהופך את extends, ברמת השפה, לפעולת composition במקום הורשה. זה פשוט ואפשרי.

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


-----

[1] אם רוצים לדייק יש לקרוא לזה Idiom, שזה Pattern שנכון לשפת תכנות ספציפית. Patterns הם נכונים בכלל ולא רק לשפה ספציפית.

[2] הנה בשפת סקאלה הם החזירו לחיים את ההורשה המרובה. אבוי!



2012-02-11

הו-נתונים! - (AtomPub) חלק 2

זהו פוסט המשך לפוסט ההקדמה על ODATA.

במידה ואתם תוהים כיצד אמור להראות מימוש REST - אז AtomPub הוא דוגמא טובה. הוא תקן ותיק (יחסית, אתם יודעים) שהוכיח את עצמו ורץ בהרבה מערכות, גם של סטארט-אפים קטנים וגם מערכות Enterprise גדולות. יתרה מזו, הוא זכה לכמה הרחבות שהחשובות בהן הן GDATA ו ODATA. ללמוד ולהבין את AtomPub הוא תרגיל טוב על מנת להבין את ODATA - ולשם אנחנו חותרים. GDATA דומה בהרבה ל"מקור", אם כי כמה דברים מעניינים לדבר עליהם.

AtomPub - תקן פיצוץ

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

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

המודל הבסיסי של אטום הוא פשוט למדי, פיד המכיל מספר רשומות:


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

הנה דוגמא אמיתית של קובץ Atom מינימלי (הכולל רשומה אחת):



<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Example Feed</title> 
  <link href="http://example.org/"/>
  <updated>2003-12-13T18:30:02Z</updated>
  <author> 
    <name>John Doe</name>
  </author> 
  <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>

  <entry>
    <title>Atom-Powered Robots Run Amok</title>
    <link href="http://example.org/2003/12/13/atom03"/>
    <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
    <updated>2003-12-13T18:30:02Z</updated>
    <summary>Some text.</summary>
  </entry>

</feed>

אטום מחייב במינימום את השדות שיש בדוגמא זו (Author וכו). זוהי החלטה דיי הגיונית לפורמט של פיד חדשות/בלוג.
הערה קטנה: בדוגמא למעלה הרשומה כוללת שדה Summary. אטום מחייב או Summary או Content (התוכן המלא).

כאשר קובץ הפיד (סיומת .xml. או atom.) יושב על שרת ווב - כל שיש לעשות על מנת לקרוא את תוכנו (עבור Feed Reader כדוגמת Outlook או Google Reader) הוא לבצע קריאת Http get פשוטה לכתובת הפיד, לקבל את הקובץ שלמעלה ולהציג אותו בצורה יפה.

Atompub
AtomPub מוסיף מימד של עריכה לפיד, ע"פ עקרונות ה REST ומאפשר לבצע פעולות CRUD שונות:

אם ארצה להוסיף רשומה לפיד, פשוט אשלח Http Post לכתובת הפיד, המכילה XML עם הרשומה להוספה וכל השדות המתאימים.
תזכורת: פעולת POST ב REST משמעה: "שלח מידע למשאב קיים". הפיד קיים ואנחנו שולחים לו עוד Entry והוא מוסיף אותו לפיד.

להלן הפעולות המוגדרות בתקן (ניתן להרחיב):
על פיד (ליתר דיוק Collection - עוד מעט אסביר) ניתן לבצע:
       GET כדי לקרוא אותו
       PUT על מנת להוסיף רשומה.
על רשומה (ליתר דיוק Member - פריט) ניתן לבצע:
      GET (קריאת רשומה בודדת)
      PUT (עדכון רשומה)
      DELETE (מחיקת רשומה).
על Service Document ניתן לבצע:
      GET.

מדוע אנחנו מדברים על Collection ו Member ולא על פיד ורשומה? מהו ה Service Document??

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

לא כ"כ משנה התמרון האקדמי הזה, האלמנט החשוב לדעתי הוא ש AtomPub תוכנן לצרכים רחבים יותר ממה שתוכנן אליהם Atom. הוא בחר להשתמש הפורמט הקיים והמוכר על מנת לא "להמציא את הגלגל". האם תמרון כזה מסתיים בהצלחה? כארכיטקט, אני נוטה לענות שלא - על "תמרונים" משלמים. אנו עוד נראה שגרורה זו היא אחת המגבלות של AtomPub ונדון האם היא הייתה נכונה.

Service, או בעצם ה Service Document, הוא מסמך המתאר מספר אוספים (Collection) הקשורים לעולם זהה (Service). המסמך מחלק אותם לקבוצות הנקראות Workspaces - אין להם משמעות רבה.

הנה המודל של AtomPub בצורה ויזואלית:



עקרון של REST שאני רוצה להזכיר הוא Hypermedia Transformations. מה שעומד מאחורי השם המפחיד הזה הוא הרעיון לפיו בכל רשומה, (כלומר: בכל פריט. אדבר AtomPub) יש קישורים לפעולות האחרות האפשריות כגון האפשרות לעריכת פריט. הנה דוגמא:

<entry>
  <title>Some Report</title>
  <link href="http://host/products/some_report.atom"/>
  <link href="http://host/products/some_report.atom“ rel="edit"/>
  ...


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

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

מעבר לצרכים הבסיסיים (פעולות CRUD)
AtomPub מכיל יכולות נוספות, אגע בעיקריות:

Paging
ברור שאוסף פריטים יכול להיות דיי ארוך ואנו לא תמיד נרצה לקבל (או לשלוח - בצד השרת) את כל הרשימה - לפני שברור שזקוקים לכולה. אולי היא ארוכה מאוד וכוללת אלפי פריטים? ב AtomPub יש מנגנון Paging (השם הרשמי הוא Partial Lists) שנשלט ע"י צד השרת - הוא מחליט כמה פריטים להעביר בכל Chunk. מסמך האטום שיחזור יכיל קישור עם rel של next האומר "אם אתה רוצה עוד - גש ללינק הבא":

<link rel="next" href="http://example.org/entries/2" />

למפתחי UI של מערכות סגורות הבחירה לתת לצד השרת להכתיב את גודל ה paging יכולה להראות קצת תמוהה - הרי ה UI יודע הכי טוב מה הוא רוצה. אולם, חשבו REST ועל החשיבות של דפים אחידים על מנת להשתמש ב caches (של HTTP) - יש פה פוטנציאל ממשי.
השרת יכול גם להחליט אם הוא מותיר גם ללכת אחורה (קישור Pervious) או לקפוץ להתחלת או סוף האוסף. אם דיברנו על ההבדלים בין אוסף (collection) לפיד (של פורמט Atom) אז אוסף הוא הסט השלם והפיד הוא view או page שקיבלנו בכל פעם.

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

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

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

עוד נקודה מעניינת היא שיכולת זו מעודדת לייצר פידים שונים המתארים אותם פריטים - עם שוני קטן שהוא הפריטים שבעריכה. מימושים רבים המבוססים על AtomPub נוטים להגדיר מספר מצומצם של שאילותות קבועות (פידים) עם URI קבועים אותם ניתן לתשאל. זו גישה שמתאימה ל REST ולניצולת טובה של ה caches אך היא קצת מגבילה את הלקוח ולעיתים דורשת ממנו "לבחור את פיד-הבסיס" ועליו לעשות עוד מעבר של "פילטור ידני". זו לא גישה רעה - יש פה  tradeoff. בהמשך נראה ש ODATA דוגל בגישה אחרת.

מדיה (Media Library Entries (MLE
כפי שציינו Atom הוא פורמט מבוסס XML. מה קורה אם אני מעוניין לנהל מידע שלא יכול להיות מתואר בצורת XML? כגון תמונות, מסמכים (נאמר PDF) וכו'? כמובן שאפשר לעטוף את המידע הבינרי ב XML בעזרת בלוקים של CDATA - אך אז פעולת Http Get של פיד תגרום לקריאת כל התוכן הבינרי של כל הרשומות - כמות גדולה של נתונים.
הפתרון של AtomPub הוא לייצר אוספים של רשומות מדיה (מידע בינרי) רשומות אלו נקראות Media Members.

הפיד של רשומת המדיה יכלול רשומות XML המתארות את הפריט וכוללות URI שלו. מעין metadata. אם אתם זוכרים, פורמט atom מחייב מספר שדות חובה כגון "Author". האם מעניין אותי לנהל את היוצר של התמונה? אם לא - האם נספק מסמך Atom לא תקני?

ההחלטה של AtomPub היא להחזיר את שדות אלו למיטב יכולתו של השרת, כנראה על בסיס המידע שיש לו על הפיד, וסביר שקצת יפוברקו. רשומות ה Media Library Entries הן בעצם מצביע לקובץ המדיה. הרשומות יכללו לינק אחת לעריכת ה MLE - כלומר ה metadata (בצורת "rel="edit) ו URI נפרד לעריכת קובץ ה מדיה עצמו (בצורת "rel="media-edit)



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