Tuesday, April 5, 2011

Smart Cache: Improvement over memcached

You all know memcached. It's a key-value lightweight caching mechanism that can be distributed over multiple machines. 

Basically you have two main methods: set and get. When you set a value you can define the expiration time (ttl). When time expires, the key-value pair is removed from memory.

However, this can have several downsides, depending on the use case.
Let's say you have a recommendation engine, and after a user bought an item, you want to suggest what else he might be interested in. Now let's say running the recommendation algorithm takes five seconds of computation time. As a performant website, you don't want to hold the HTML generation for five seconds. So what some sites usually do, is setting the cache for 24 hours (cache per item), and accepting the fact that the first user in every 24h will be the scapegoat, and will "suffer" these five seconds.

This is problematic. What if your website works on the "Long tail" concept, and you have many items? This can lead to a situation that almost every moment you get this cache "miss".
You could solve this problem by running a chron job or a background timer task that will compute every 24 hours the recommended items for every item in the pool. This way, the real-time user will not have to wait. But this has problems as well:
  • Your CPU consumption won't be balanced: Every 24 hours you will have a serious task running.
  • What's the point to run the algorithm for every item? What if some items are frequently visited while others are not?
I took a different approach on Top7.  We created a mechanism that we call Smart Cache, which sits on top of memcached. It is being implemented in PHP, but could be easily implemented in other languages as well (PHP is more challenging because it doesn't support threads). The idea is that the underlying cache never expires. When the time-to-live supplied by the programmer expires, the smart cache returns the value, and renew the value asynchronously, on the background. This ensures that users won't wait at all.
The smart cache has one c'tor method, and one get function, which is a proxy function (or magic function)

The c'tor: SmartCache(boolean blockOnEmptyCache, int timeToLive):
blockOnEmptyCache indicates what to do when the underlying cache is empty. If it is false, on first invocation the get function simply throws an exception. It is up to the programmer to decide how to handle it. For example, you can choose not to return the recommended items, only for the first user. If you set it to true, the function blocks (In our use case for 5 seconds), update the underlying cache, and return the result.
timeToLive indicates how long the value is valid. When subsequent get function occur, the smart cache checks if the cache value is valid. If it is valid, it returns the result. If not, it immediately returns the value, and updates the cache in the background.

How the cache value is updated? The get function is a magic function, so for example when you call:
$smartCache->getRecommendedItems(itemId, userId)
and the timeToLive is not valid, the smart cache simply calls in the background this function, and updates the cache. It also ensures that if concurrent get invocations occur, only one API function invocation will execute. So, to summaries, it ensures that no matter what happens only one API function, per set of function parameters, will occur in every timeToLive frame. This has a great benefit of reducing load from API server, and can mitigate high loads of traffic. If you use regular memcached, and get on first invocation 10 request at the same time, probably what would happen is that you would execute the API function 10 times, and then update the cache 10 times. Only then you will get your timeToLive function-free time, then the cache value will die, and so on.

So basically the Smart Cache encapsulate the function that should be called when the time expires.

Let's summaries what we get from Smart Cache
  • Instead of caching ourselves the results coming back from API functions, it encapsulate both the cache and the API invocation, so typical usage is like that:
    $smartCache = new SmartCache(true, 3600);
    $smartCache->getRecommendedItems(itemId, userId);
    That's about it.
  • Users don't block when cache revokes
  • API is immediately cached.
  • Protect API invocation against peeks of concurrent users.
  • API will be executed only when needed. If only 25% of the items is reached in a week, then you save 75% of CPU time.

Author is the CTO of top7.com

Sunday, April 3, 2011

תורת היחסים

כל זוג חווה מריבות/ויכוחים מדי פעם.

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

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

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

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

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

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

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

    ההצעה שלי היא כזו: משרואים שויכוח/ריב/דיון, לא צולח את שלב הדיבורים, רשאי כל אחד מבני הזוג להשתמש במנגון ההצעה- להלן מנגנון ה"ביד" (Bid).
    הרעיון שמאחורי מנגנון הביד פשוט. תמיד למישהו אחד נושא הויכוח יהיה אקוטי יותר. זאת אומרת, שתמיד אחד מבני הזוג יסכים לשלם עבורו "מחיר" גבוה יותר. כך, ברגע שויכוח הופך להיות עקר, אחד מבני הזוג יכול להפעיל את מנגנון ה"ביד". או אז, כל אחד מהם כותב על פתק מספר מ-1 עד 10, שמשקף את מידת חשיבות הנושא לגביו. לאחר מכן, ובו זמנית, שולפים את הפתקים ודעתו של בעל המספר הגבוה היא הקובעת. בתום הדיון, הסכום של הזוכה 'מופחת מהקרדיט שלו. מטעמי נוחות, נכון לקבוע את הקרדיט על סכום של 100 נקודות. מספיק כדי להשתמש בו בחוכמה ולא גדול מדי מכדי לאפס את המונים מעת לעת.


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

    האלגוריתם המתמטי

    נסמן בc1 ובc2 את הקרדיט שיש לצד א' וצד ב' בהתאמה, נסמן בb1 וb2 את הבידים בדיון מסוים ונסמן בw1  וw2 את המשקלים הסופיים של הבידים, שיקבעו מי ניצח בדיון.
    המשקלים הסופיים יחשובו בצורה הבאה:
    •  w1 = b1 + (c1-c2)/10  
    •  w2 = b2 + (c2-c1)/10. 
    כאשר בין דיון לדיון הקרדיט מופחת לצד שניצח בגובה w (ולא בגובה b).
    שיטה זו פותרת את מרבית הבעיות- הקרדיט לעולם לא נגמר (כי אפשר לרדת למינוסים, או פשוט להוסיף סכום זהה לשני הצדדים) ובנוסף הוא מתוכנן כך שהוא נותן משקל יותר גבוה למי שמוביל בקרדיט, כך שבסופו של דבר יהיה יותר כוח למי שמוותר יותר, ולמעשה השיטה מבטיחה את הסימטריה הממוצעת בין הצדדים.


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


    דוגמא


    שני הצדדים מתחילים מקרדיט 100. בויכוח הראשון צד א' שם 8 וצד ב' שם 2. כיון שהקרדיט זהה, המשקלים יהיו גם 8 ו2 בהתאמה, לכן צד א' ינצח. לאחר מכן הקרדיט שלו יהא 92, ולצד ב' יוותרו 100 קרדיט. נניח בויכוח ב' שני הצדדים שמו 6. כיון שההפרש בין הקרדיטים הוא 8, לצד א' יהיה משקל של 5.2 ולצד ב' יהיה משקל של 6.8, לכן למרות ששמו אותו דבר, צד ב' ינצח (כי הוא למעשה ויתר בויכוח הקודם). לאחר מכן הקרדיט של צד א ישאר 92, ולצד ב' יהא 93.2 קרדיט. צורה זו מבטיחה שלא יתכן שצד אחד ינצח כל הזמן, רק בדברים שמאוד חשובים לו. למעשה השיטה "לומדת" את הבנאדם, ומאזנת אותו.
    הוויאנס במחירים הכי זולים
    אופטימיות היא לראות את חצי הכוס המלאה במקום שאין בו כוס
    הכותב הינו מקים האתר החברתי citip.co.il לביקורות על דירות להשכרה