סטארטאפ על מסילות: סוגיית המהירות/איטיות של Ruby on Rails

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

Shutterstock

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

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

במשך הרבה מאוד זמן, היה חשש לבנות מוצרים ולבסס את המוצר שלך על Rails מכיוון שהדעה הרווחת בשוק היא ש-Rails Can’t Scale וש-Rails ו-Ruby לא מהירות מספיק בשביל לספק את הצורך במהירות שכל הגולשים פיתחו ובעיקר את חוסר הסבלנות שלהם לאיטיות במוצר.

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

ש-Rails מעודדת אותך לבנות מוצרים, ואז צריכים לעשות Scale up וצריכים לשרת יותר ויותר אנשים בקצבים גבוהים יותר.

 

ברוכים הבאים ל-FooBar

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

(אגב אפשר לעשות את זה ב-FQL פשוט, אבל לצורך הדוגמה נניח שה-API של mutual friends לא קיים לשימושנו ב-Facebook).

נתחיל באיך התחילו ב-FooBar ובנו את הדברים (זה יראה לכם מאוד מוכר לדברים שעשיתם או ראיתם בעבר) ואז נעבור ונראה איך בונים את זה בצורה נכונה יותר ומאפשרים Scale הרבה יותר נכון למוצר.

חיפוש

את החיפוש התחילו ובנו ב-SQL בדיוק כמו שעשו אותו המייסדים עשרות פעמים בעבר וזה נראה בערך ככה:

[code lang=”RUBY”]
User.where("full_name LIKE ‘%{params[:full_name]}%’")
[/code]

כמו שניתן לשים לב, החיפוש נעשה ב-SQL בLIKE.
נניח לצורך הדוגמה שכבר יש Index על השדה full_name

מה הבעיה עם זה?

הבעיה עם זה שזה מאוד איטי, במיוחד כשזה מחזיר קצת יותר תוצאות. למשל, אם נריץ את זה על טבלה עם 2-3 מיליון רשומות (שזה לא המון לכול הדעות) נקבל תוצאות תוך 900-1200ms שזה נצח מבחינת המשתמשים שלנו.

הפתרון

אינדקס בלתי תלוי בDatabase.

ישנם כמה פתרונות טובים שעובדים עם Ruby, אחד מהם הוא Elastic Search והשני הוא SOLR. בכל הפעמים האחרונות שהטמעתי Search עשיתי את זה עם SOLR ולכן ניקח אותו כדוגמה כאן, הוא ותיק ואמין מאוד ואני ממליץ גם לכם לבחון אותו לפתרון שלכם.

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

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

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

להלן דוגמא לאיך מחפשים ב SOLR:

[code lang=”RUBY”]
User.search do
full_name ‘Avi Tzurel’
end
[/code]

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

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

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

דוגמה ל-Scale חכם של אינדקסים עם SOLR:

Drawing1

דוגמה ניתן לראות שיש Slave של האינדקס על כל אחד משרתי האפליקציה, ככה שככל שמגדילים את ה-Cluster גדל גם כוח החיפוש שלנו, מול Master אחד גדול שאליו הולכים כל ה-Writes.

הדוגמא כאן לוקחת בחשבון 80% קריאות ו-20% כתיבות, ולכן ה-Scale תוכנן בצורה הזו, לכל מספר אחר יכול להיות שנצטרך לתכנן קצת אחרת.

הצלבה של רשימת חברים

untitled-1

לאחר שחיפשנו ומצאנו את המשתמש מולו אנחנו רוצים להצליב את רשימת החברים, הגיעו ב-Foobar לפתרון הבא:

מה שאנחנו רואים כאן זה קשר one_to_many בין טבלת users לטבלת user_friends. כלומר, על כל משתמש בטבלת users ישנם הרבה רשומות בטבלת user_friends.

מה הבעיה עם זה?

הבעיה היא ראשית שאנחנו שוב מסתמכים על בסיס הנתונים שלנו מבחינת Scale. בעיה נוספת היא שהגדילה של הטבלה הזו היא מאוד פרוגרסיבית, כל משתמש מגדיל את הטבלה ב-X מספר החברים שלו.

אם ניקח לדוגמא מצב שבו לכול משתמש יש בממוצע 200 חברים, ויש לנו 2.5 מיליון משתמשים, אנחנו מגיעים ל-500,000,000 רשומות בטבלה הזו, וזו כבר טבלה שממש קשה לעבוד איתה, קשה לאנדקס אותה, קשה לעשות עליה Query ועוד.

הפתרון 1

אני יודע שלכולכם קופץ לראש עכשיו Graph Database, אבל זה ממש לא מה שאני חותר אליו. הפתרון שאנחנו נבחר הפעם יהיה פתרון NoSQL בצורה של Redis.

למה Redis?

Redis הוא פתרון NoSQL מאוד מהיר ומאוד אמין, משתמשים בו בכל מקום היום והוא בפיתוח אקטיבי מאוד. ל-Redis ישנם Sets וגם Sorted Sets שהם מושלמים בדיוק למה שאנחנו צריכים כאן.

מה שאנחנו נעשה בעצם, זה לשמור Set של כל החברים של כל משתמש ב-Redis. ב-Key שמכיל את ה-user_id שלנו.

 למשל :

[code lang=”RUBY”]
user_friends:100
10
20
30
44
9
12

user_friends:101
10
22
33
43
8
12
[/code]

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

עכשיו אחרי שיש לנו את ה-Sets שלנו, אפשר להשוות אותם ב-Ruby Intersections פשוט בין שני מערכים ולראות מי החברים המשותפים בין שני המשתמשים האלה.

הפתרון 2

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

אופציה נוספת היא לשמור את רשימת החברים על SOLR בצורה של Array. כך, אנחנו יכולים גם לחפש (כפי שהזכרנו קודם) וגם לשמור על אותה מהירות (give or take 2-3 ms).

איך עושים את זה ב-SOLR עם Sunspot?

מה שאנחנו נעשה זה לממש Module שנקרא SearchableUser שאותו נכלול בתוך המחלקה User ואז נגדיר את השדות שעליהם אנחנו רוצים לחפש ואת השדות שאחנו רוצים לאנדקס:

[code lang=”RUBY”]
class User
include User::SearchableUser
end

class User
module SearchableUser
extend ActiveSupport::Concern

included do
searchable :auto_index => false do
long(:item_id, stored: true) { self.send(:id) }
string(:item_type, stored: true) { self.class.name }
text :display_name, boost: 100

long(:friend_ids, multiple: true, stored: true) do
# Getting the friend ids from the database
end
end
end
end
end
[/code]

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

בואו ונראה דוגמה לאיך נראה חיפוש שכזה

[code lang=”RUBY”]
search = "Avi"
friend_ids = [1, 2, 3]
without_friend = [5]

Sunspot.search(User) do
keywords(search)
with(:friend_ids, friend_ids)
without(:friend_ids, without_friend)
paginate :page => params[:page]
end.results
[/code]

מה אנחנו רואים כאן?

חפש את כל המשתמשים שמתאימים ל-Avi שיש להם ברשימת החברים את 1,2,3 ואין להם ברשימת החברים את 5. גם כשמריצים את החיפוש הזה על עשרות מיליוני משתמשים, אנחנו נקבל תוצאות במהירות מדהימה. (כן, אני יודע משתמשים במילה הזו הרבה היום בתרבות הריאליטי).

אנחנו יכולים להניח שבמידה ו-SOLR מותקן ומקונפג כמו שצריך (לא נרחיב כאן) נקבל תוצאות בפחות מ-50ms.

Gotchas

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

ב-SOLR ישנו רק Eventual Consistency, ולכן רשומה שהכנסנו, לא תהיה זמינה מיד, לא נוכל לחפש משתמש מיד אחרי שהכנסנו את הרשומה שלו.

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

סטרטאפים אחרים…

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

מתחילים לסבך עניינים – המלצות חברים

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

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

Neo4J הוא NoSQL Graph Database שמעבר ל-3 מילות באז מפוצצות, הוא באמת עושה עבודה טובה.

יש לו דרייברים לכול השפות בערך (כולל רובי) וגם Rest Interface, ויש לו שפה משלו להגדיר את הגרף ולעשות עליו שאילתות.

graphdb-gve

כפי שניתן לראות כאן, איך בנוי גרף

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

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

[code lang=”RUBY”]
require ‘rubygems’
require ‘neography’

def create_node(name)
Neography::Node.create("name" => name)
end

johnathan = create_node(‘Johnathan’)
lior = create_node(‘Lior’)
yifat = create_node(‘Yifat’)
lee = create_node(‘Lee’)
avi = create_node(‘Avi’)

johnathan.both(:friends) << lior
lior.both(:friends) << lee
lior.both(:friends) << yifat
yifat.both(:friends) << lee
yifat.both(:friends) << avi

def suggest_friends_for(node)
node.incoming(:friends).
order("breadth first").
uniqueness("node global").
filter("position.length() == 2;").
depth(2).
map{|n| n.name }.join(‘, ‘)
end

puts "Johnathan should become friends with #{suggest_friends_for(johnathan)}"

# RESULT
# Johnathan should become friends with Lee, Yifat
[/code]

מה עשינו כאן?

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

סיכום

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

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

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

הסדנה מומלצת מאוד למי שעובד היום בריילס או מתכנן לבסס מוצר על רובי או על ריילס.

 קרדיט תמונה: Shutterstock.

הכתבה בחסות Tikal

טיקל היא חברת תוכנה הפועלת מעל 12 שנים ומסייעת לחברות פורצות דרך להגיע למצוינות בתחומן. אנו מחויבים לשיפור מתמיד של השירותים שלנו ולמענה מקצועי ואיכותי ללקוחותינו.
המומחים של טיקל מנוסים בפיתוח של מגוון מערכות תוכנה, שימוש במגוון כלים וטכנולוגיות בתחומי ה- Java, Python, .NET, Javascript, RoR, Mobile, ALM. לטיקל 3 ערכי ליבה:
קוד הפתוח
גישת הקוד הפתוח שלנו מאפשרת ללקוחותינו פיתוח מהיר והתאמה אישית תוך ביטול התלות בספק אחד, טכנולוגיות מתקדמות ועדכניות, נגישות למידע, תמיכה שוטפת, הוצאות נמוכות.
חדשנות
אנו לא יועצים תאורטיים, אלא מומחים בעלי ידע מעשי וניסיון רב. אנו מנהלים שגרת העשרה ולמידה כדי להחדש ולעדכן את מאגר הידע שלנו. לומדים ומכירים מקרוב כל פיתוח חדש ונמצאים באופן תמידי בחזית המרוץ הטכנולוגי.
אחריות
העקרון המנחה את העבודה שלנו הוא איזון בין אחריות מקצועית וסגירת פערים טכנולוגיים, בין אחריות לתוצאות והשגת יעדים.

Avatar

Tikal

צוות המומחים של טיקל יעזרו לכל צוות פיתוח לסיים כל משימת פיתוח בזמן ובאפקטיביות גבוהה. החל מהתאמה ומעבר לטכנולוגיות חדשות, דרך תכנון ובניית פרוייקטים ואופטימיזציה בתחומי: JAVA, Javascript, Ruby, ALM & .NET

הגב

3 תגובות על "סטארטאפ על מסילות: סוגיית המהירות/איטיות של Ruby on Rails"

avatar
Photo and Image Files
 
 
 
Audio and Video Files
 
 
 
Other File Types
 
 
 

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

סידור לפי:   חדש | ישן | הכי מדורגים
Jובה
Guest

“בואו נעזור רגע את FooBar …”
צריך להיות
“בואו נעזוב רגע את FooBar …”

איתי
Guest

הכתבה ממש לא תואמת לכותרת. לא נאמרה מילה על ruby או rails בכתבה.

עמרי
Guest

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

מה שמעניין כאן הוא scalable persistency, וזה נכון לא רק לרובי. חבל .

wpDiscuz

תגיות לכתבה:

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