קטגוריה: פיתוח

ניתוח מחרוזות חלק א' התאוריה (על רגל אחת)

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

על פניו, זה נשמע די קל ופשוט, אני מוצא משהו המוגדר כ"גבול", ואז אני מחפש על זה משמעות נכון? ובכן, מסתבר שזה לא כזה פשוט. יותר מזה, גיליתי שישנם 2 סוגים של "שפות" כאשר רוצים לנתח מידע – אחת נקראת "שפה דטרמיניסטית" והשנייה "שפה לא דטרמיניסטית". באנגלית הם קיבלו את השמות Deterministic language ו Nondeterministic . כאשר מדברים עליהם, מדברים על הבנת ההקשר (context). להמשיך לקרוא

מתכנת IT

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

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

עכשיו אשאל אתכם שאלה: האם איש DBA שייך לקבוצה הזו? התשובה היא כן. האם הוא איש DevOps? התשובה היא לא.

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

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

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

איש DBA יודע למשל המון על IO ועל דיסקים, וזיכרון, אבל זה לא אומר שהוא יודע מספיק בשביל להחליף ציוד, או לבחור חומרה בצורה נכונה. הוא רק יודע מה מסוגל לתת ביצועים טובים, אבל האם לוח מסוים עדיף? האם שרת מסוג מסוים? האם בקר מסוים? האם מעבד מדגם אחד טוב יותר וכיוב', הם פחות מהמומחיות שלו. זה לא אומר שהוא אינו יודע, זה אומר שהוא פחות חי את זה. זה יותר סוג של by-product של העבודה, מאשר המומחיות.

להמשיך לקרוא

הבנת תבניות זמן בשפת Go

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

כאשר מדובר בשפת Go (או Golang למחפשים), הגישה בנויה מעט שונה. במקום place holder רגיל שבו "m" מייצג חודש בעל תו בודד (כלומר אם החודש הוא "5", אז הוא יופיע כ"5", אך אם החודש הוא "10", הוא יופיע כ"10") או "mm" שהוא חודש דו ספרתי (כלומר אם החודש הוא "5", אז הוא יופיע כ "05", וכאשר מדובר בחודש שהמספר שלו הוא "10", הוא עדיין יופיע כ"10") אינה מתקיימת.

התבניות האלו מוחלפות בגישה אחרת, שאותי לפחות מאוד בלבלה במשך הרבה מאוד זמן. הגישה אומרת כי יש לנו offset holders. מה הכוונה? ובכן תאריך ושעה בGo נשמרים בברירת המחדל כמספר שלם בגודל 64 ביט (כלומר int64). בנוסף, ישנו מספר בגודל 32 ביט (int32) שמחזיק בנונו השניות לשנייה מסוימת., ובנוסף לזה, יש גם מערכת לשמור מיקום.
כל המשתנים האלו מאוגדים ברשומה בשם Time.

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

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

בשביל המבנה, יצרו בגו סוג של Fixed Date שהוא המייצג את הימדע הזה:

Mon Jan 2 15:04:05 MST 2006

המידע הזה הוא נקודה קבועה היודעת להיות מתורגמת ל Unix Epoch 1136239445.
להמשיך לקרוא

יצירה וטעינה של ספרייה משותפת בגו

גו היא שפת תכנות מהודרת, אשר יוצרת קבצי ריצה טבעיים של מערכת ההפעלה. מאז גרסה 1.5, נכנסה האפשרות ליצור ספרייה משותפת דינמית (DSO). בגרסה 1.5, התמיכה הייתה ניסיונית, ובגרסה של 1.6, התמיכה נכנסה כתמיכה רשמית של המהדר.

מה זו בעצם ספרייה משותפת?
בקצרה, על רגל אחת, ספרייה משותפת זהו קובץ "ריצה" (למשל בפורמט PE בווינדוז או ELF ביוניקס), אשר אינו מחזיק בחלק ריצה כפרוסס (לרוב נקרא main).
לקובץ הריצה יש רשימה של פונקציות אשר "מיוצאות", כלומר הכתובות שלהם נגישות כלפי חוץ כאשר רוצים להשתמש באותן הפונקציות.
השמות של אותן הפונקציות נקראות symbols והגישה אליהן מתרחשת על ידי כתובות זיכרון.
במידה ופרוסס מסויים מקשר אליהן בצורה סטטית, כלומר בזמן הידור, יודעים את המיקום שלו ביחס לשאר הפונקציות של הפרוסס, כאילו היה חלק מהפרוסס עצמו.
כאשר הטעינה מתבצעת בזמן ריצה, יש אתחול זיכרון דינאמי אשר מחזיק את הכתובת.

כיצד יוצרים ספרייה משותפת בגו? להמשיך לקרוא

מימוש RDP טבעי בשפת רובי

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

הפרוטוקולים עצמם הם פרוטוקולים בינאריים, ולא פרוטוקולים טקסטואליים (כדוגמת HTTP למשל), וככאלו יש עבודה רבה עם bit manipulation והבנה של מיקומים שונים ומה המשמעות שלהם.
הרעיון של RDP הוא להצליח להעביר את כל סביבת המיקרוסופט למחשב מרוחק, כולל העתקת קבצים, שיתוף מדפסות, שיתוף כוננים, שיתוף קול ואפילו לבצע הזרמה של ווידאו.

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

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

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

כתובת הפרויקט: https://github.com/Safe-T/rdp-rb

דברים מציקים בגו

לאחרונה אני כותב הרבה ב golang מספר actors או workers (אם תרצו) שעושים מספר דברים בצורה מאוד גמישה מצד אחד, ומצד שני מאוד מהירה.
השימוש שלי הוא בכך שאני יכול להריץ הרבה instance של אותו הדבר (בין אם זו אותה מכונה או מכונה נפרדת), לקבל אירועים מ AMQP ולבצע משימה.
עד כאן הכל יפה ונחמד.

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

הסיבה לכך די ברורה, אבל שגויה ביסוד שלה – הם מניחים כי אם אתה שומר slice של בתים, אתה כנראה מנסה לשמור מידע בינארי או שונה ממה שצריך.
רצית רק לשמור סדרה של ערכים, שאף אחד מהם לא יהיה גדול מ255? לא, זה מידע אחר.

אם ההתנהגות הזו היתה זהה למשל ל uint16, אז הייתי אומר "אוקי, כל slice שהוא מספרי, ייחשב כמידע". אבל כאן רק אם זה byte, או uint8 אשר הם למעשה זהים אני אקבל המרה ל base64.

ההחלטה הזו עבור המשתמש מה נכון עבורו היא בעייתית.
בראש ובראשונה כי אני למשל במקרה הזה עובד עם API חיצוני שאני לא שולט בו, והדרישה היא "מערך של מספרים היכולים להיות בין 0 ל255". מן הסתם יש להם משמעות מסויימת, אבל לא בהכרח מה ש go חושב שהיא.

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

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

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

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

וזה כרגע הדבר הכי מעצבן שמצאתי ב golang. זה לא מונע ממני להמשיך לעבוד, אבל זה בעיני מאוד מעצבן.

פיתון מול רובי, לא מה שחשבתם

הקדמה

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

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

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

ואז אני רואה שאלות (הדגמה בלבד: 1, 2, 3, 4) שונות על פיתון (כי הרי דוחפים אותה לכל שיחה בערך), ואני רואה איפה למעשה אנשים מסתבכים איתה, ואני מנסה להשוות את זה לרובי, ואני יודע שבמקומות האלו לא ממש יפלו לי שם עם רובי (וזה לא בגלל שאין מקומות ליפול בהם ברובי), והייתי רוצה להסביר גם למה.

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

שאלה ראשונה

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

a = 10.times.select{ |x| x % 2 == 0 }
a
=> [0, 2, 4, 6, 8]

זהו, זה כל מה שצריך, שורה אחת 🙂
אסביר אותה בקצרה. ברובי כל דבר הוא אובייקט, אין כזה דבר משהו שהוא "פרימיטיבי" (בהשוואה לג'אווה למשל).
מספר שלם "רגיל", הוא אובייקט מסוג Fixnum. יש לו מתודה אשר מקבלת ירושה ממחלקת האב – Integer בשם times, אשר מבצעת כמות הפעמים של המספר המבוקש איטרציה.
במידה ולא יוצרים block או proc בשביל לרוץ כמניית האיטרציה, חוזרת לנו מחלקה מסוג Enumerator אשר היא מחלקת בסיס לדברים בעלי יכולת מנייה (כדוגמת מערכים). במחלקת ה Enumerator ישנה מתודה בשם select, המאפשרת להגדיר אילו ערכים באיטרציה כלשהי יחזרו אלי כמערך, כאשר אני יצרתי כאן למעשה proc עם הוראות.
היות וברובי השורה האחרונה בהכרח תמיד חוזרת (אם אין explicit return), אז הוא יודע שכאשר יש מספר זוגי, אז אני רוצה שזה הערך אשר יחזור אלי.
עכשיו הנה משהו מעניין על רובי תוך כדי ההסבר – אין ברובי אופרטורים. כל מה שנראה כאופרטור (כדוגמת חילוק השארית ופעולת השוויון), הם למעשה מתודות, ולכן מתודה כדוגמת even?‎ זהה למה שהייתי צריך, ויתר מזה, אין צורך בסוגריים, ולכן זה יכול גם להראות כך:

a = 10.times.select{ |x| x.even? }
a
=> [0, 2, 4, 6, 8]

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

שאלה ראשונה – תת שאלה

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

a = []
10.times do |i|
  a.push(i) if i.even?
  puts a.inspect
end

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

ובנוסף, לרובי גם יש תמיכה בשני תחבירים של משפט טרינארי אשר שם נפל השואל:

i = 1
a = if i.even? then 't' else 'f' end
b = i.even? ? 't' : 'f'

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

c = unless i.even? then 'f' else 't' end

שאלה שניה

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

a = [1, 2, ['a', 'b', [0xa, 0xb] ]]
=> [1, 2, ["a", "b", [10, 11]]]
a.flatten
=> [1, 2, "a", "b", 10, 11]

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

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

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

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

שאלה שלישית

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

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

str1 = 'yoyo'
print 'sdfsdfsfsdfsfsdf' if str1.eql?('aaaaa') || str1 == 'yopo' || str1 === 'yoyo'

ישנם כאמור עוד דרכים לביצוע השוואת מחרוזות, אבל אלו מספיקות, לא?
על רגל אחת (בלי באמת למצות את ההסבר עד הסוף שלדעתי דורש פוסט שלם רק על זה):
המתודה (בדיקה) הראשונה של eql?‎ בודקת האם האורך והתוכן זהים.
המתודה (הבדיקה) השנייה של ==, בודקת האם זה תוכן זה הוא מסוג מחרוזת, ובמידה וכן, בודקת אורך ורק אז את התוכן.
המתודה (הבדיקה) השלישית של ===, בודקת גם היא את סוג המחלקה, ובמידה ושניהם מחרוזות אז מבצעים פעולה הזהה ל==, במידה ולא, תהה בדיקה האם יש מכנה משוטף בניהם הניתן להשוואה באמצעות פעולת case.

שאלה רביעית

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

require 'open-uri'

image = open('https://www.google.com/images/srpr/logo11w.png')
open('/tmp/image.png', 'wb+') { |a| a.write(image.read) }
image.close

קצת פירקתי את הדברים לקריאות.
אני טוען את הספרייה open-uri, אשר תפקידה הוא לדעת לפתוח קישורים של ftp, http ו https כאילו היו file descriptor של רובי.

אני במקרה הזה ניגש לקובץ תמונה של גוגל, ושומר אותו כקובץ בינארי בספריית /tmp/ .
שימו לב כי אינני סוגר את הקובץ תמונה שאני שומר בדיסק, והוא נסגר לבד, בזכות פעולת ה proc, אשר בעצם מתבצעת "מאחורי הקלעים" באמצעות yield. וכאשר פקודה זו מסתיימת, הפונקציה של open סוגרת את עצמה לבד.

סיכום

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

ניסויים בתורי הודעות

יש מערכת שאחרי load test הבנתי כי אני חייב לבזר את המערכות שבה. אז התחלתי לבצע מחקר על סוגים שונים של message queue בשביל לבזר את המערכת. עשיתי ניסויים עם המון מערכות שונות, כולל התייעצות עם אנשים שממשים כבר מערכות המשתמשות בהם, והגעתי להבנה כי כנראה ש RabbitMQ מתאים לצרכים שלי.

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

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

למשל אני עשיתי 2 הגדרות בלבד לRabbit להתאים למכונת הפיתוח החלשה שלי, והגעתי ללמעלה מ7 מליון בקשות בשניה אחת, עד שרוחב הפס נחנק, אבל ה load היה על 0.02 לאורך זמן רב. כלומר לא הגעתי ל load שהם דיברו עליו. אבל יכולתי לבדוק לאורך זמן, רק כאשר ירדתי בערך ל5 מליון בקשות בשניה, ואז הרוחב פס כבר לא נחנק לי.

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

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

כך שלי חסר מאוד מידע איך ומה בוצע בפוסט, אבל אני לא מצליח לשחזר את מה שהם כן מזכירים.
העניין הוא, שאני לא בוטח ברדיס לפעולות ה mq שאני צריך, כי אני צריך מחסנית יציבה של worker..
אם חלילה וחס הוא נפל, אסור שהמידע יאבד, ויותר מזה, במידה ואני צריך להוסיף workers, אני צריך מצב של ביזור המידע, בלי שworker אחד ידרוך על השני, ואני מקבל את זה בצורה טבעית ב Rabbit, (ועוד מספר סוגים של MQ שבדקתי), אבל רדיס מעולם לא תוכנן לזה. הוא נמצא בזיכרון, אני כמובן שיכול להכריח אותו לכתוב לדיסק (איטי מאוד, אבל זה מה שקורה עם persistence queue בRabbit), אבל בשביל לעשות תורים בגישה של Round Robin, ובכן, בלי לכתוב משהו ב lua, אני לא מכיר תמיכה לזה ברדיס, למרות שאשמח לגלות שיש.

ככול שאני עובד כרגע (אמנם עדיין בפיתוח) עם MQ, ככה אני אוהב יותר את הרעיון של השימוש ב Rabbit.
במערכת שאני בונה, יש לי מערכת חיצונית שהיא של ספק אחר שמדברת עם HorentQ, דרך הפרוטוקול שנקרא STOMP (בניגוד לRabbit שברירת המחדל שלו היא AMQP אך תומך גם ב STOMP), והיתרונות של מערכת כזו שהיא robust מאוד, מאפשרת להתמודד עם בעיות שונות, כדוגמת קריסת מערכת, ניתוק התקשורת, התנהלות של load balance של מספר שרתים וכיוב'…

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

 

 

שבוע עם Atom

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

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

העורך משתמש בחלקים של כרומיום, ב node.js, ומשתמש גם ב coffeescript עבור התצוגה של דברים (הנעשים בcss).

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

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

העניין הוא ש 1.0 קרוב להיות משהו טוב, אבל ישנם מספר דברים שעדיין לא אפויים מספיק:

  • היכולת להגדיר מה הוא API תקין מבחינת תוספות ולא לאפשר תוספות שאינן כאלו
  • היכולת להגדיר תוספים על בסיס מה שאני עובד כרגע
  • קבלת תוספים איכותיים מבית github לשפות שונות, כדוגמת PHP, רובי וכיוב', מעבר למה שמגיע כברירת מחדל.

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

recursive tail

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

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

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

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

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

הגישה המוכרת ביותר לכך נקראת tail recursion. או רקורסיית זנב בעברית טכנית.

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

מה הכוונה? אשתמש בקוד רובי (שהוא לדעתי קריא מאוד) לשם כך.

רקורסיה בגישה ה"רגילה" תהיה כתובה כך:

def recursive(n)
  if n <= 1
    1
   else
     n * recursive(n - 1)
   end
end

recursive(4)
=> 24

רקורסיית זנב, תראה כך:

def tail_recursive(current, n, result)
  return result if current <= n

  new_result = result * current
  tail_recursive(current + 1, n, new_result)
end

tail_recursive(1, 4, 1)
=> 24

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

אבל לא הכל ורוד בזה. קשה יותר לדבג בעיות ברקורסיית זנב, היות ולמעשה אין לנו stack frames רבים, שיהיה ניתן להבין באיזה שלב יש בעיה. ובכך אין לנו stack trace שיסייע לנו בנושא.

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

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