החבילות המוכנות של כרטיסיות [Anki](https://apps.ankiweb.net/) משתמשות בשיטת **חזרתיות מבוססת מרווחים (Spaced Repetition)** כדי לעזור לך לזכור מושגים חשובים בתכנון מערכות.
<uldir="rtl">
<li><ahref="https://github.com/donnemartin/system-design-primer/tree/master/resources/flash_cards/System%20Design.apkg">חבילת תכנון מערכות</a></li>
**ש: עבור הראיונות, האם אני אמור לדעת כל מה שכתוב כאן?**
**ת: לא, אתה לא צריך לדעת הכול כדי להתכונן לריאיון**.
מה שאתה תישאל עליו בריאיון תלוי בדברים כגון:
<uldir="rtl">
<li>כמה ניסיון יש לך</li>
<li>מה הרקע הטכני שלך</li>
<li>לאילו משרות אתה מתראיין</li>
<li>באילו חברות אתה מתראיין</li>
<li>מזל</li>
</ul>
לרוב מצופה ממועמדים מנוסים יותר לדעת יותר על ארכיטקטורה ותכנון מערכות. ארכיטקטים או ראשי צוותים מצופים לדעת יותר מאשר עובדים בודדים. חברות טכנולוגיות מובילות לרוב יערכו ריאיון אחד או יותר של ארכיטקטורה.
רצוי להתחיל רחב ולהעמיק במספר תחומים. זה עוזר לדעת קצת בנוגע למספר נושאי מפתח בתכנון מערכות. תתאים את המדריך לפי לוח הזמן שלך, הניסיון, המשרות שאתה מתראיין אליהן, והחברות שבהן אתה מתראיין.
<uldir="rtl">
<li><strong>לוח זמנים קצר</strong>– התמקד ב<strong>רוחב</strong> של נושאים בתכנון מערכות. תרגל פתרון של <strong>כמה</strong> שאלות ריאיון.</li>
<li><strong>לוח זמנים בינוני</strong>– התמקד ב<strong>רוחב</strong>ו<strong>קצת עומק</strong> של נושאים בתכנון מערכות. תרגל פתרון של <strong>הרבה</strong> שאלות ריאיון.</li>
<li><strong>לוח זמנים ארוך</strong>– התמקד ב<strong>רוחב</strong>ו<strong>יותר עומק</strong> של נושאים בתכנון מערכות. תרגל פתרון של <strong>רוב</strong> שאלות הריאיון.</li>
ראיון ארכיטקטורה הוא **שיחה פתוחה**. מצופה ממך להוביל אותה.
אתה יכול להיעזר בצעדים הבאים כדי להנחות את הדיון. כדי לחזק את ההבנה של התהליך, תעבור על [שאלות ריאיון בתכנון מערכות עם פתרונות](#system-design-interview-questions-with-solutions) אל מול הצעדים הבאים:
### תאר מקרי שימוש, אילוצים והנחות עבודה
אסוף דרישות והגדר את ה-scope של הבעיה.
שאל שאלות כדי להבהיר את מקרי השימוש והאילוצים. דון בהנחות העבודה שאתה עושה.
שירות הוא **סקילבילי (scalable)** אם הוא משתפר **בביצועים (performance)** שלו באופן פרופורציונלי למשאבים שנוספו. באופן כללי, שיפור בביצועים פירושו היכולת לתת שירות ליותר יחידות עבודה, אך הוא יכול גם לבוא לידי ביטוי ביכולת להתמודד עם יחידות עבודה גדולות יותר, ככל שהדאטא גדל.<sup><ahref=http://www.allthingsdistributed.com/2006/03/a_word_on_scalability.html>1</a></sup>
דרך נוספת להסתכל על ביצועים מול סקילביליות
<ul>
<li>אם יש לך בעיית <strong>ביצועים</strong>, המערכת איטית עבור משתמש בודד.</li>
<li>אם יש לך בעיית <strong>סקילביליות</strong>, המערכת מהירה עבור משתמש בודד אך איטית בעומס כבד.</li>
</ul>
### מקורות וקריאה נוספת
* [A word on scalability](http://www.allthingsdistributed.com/2006/03/a_word_on_scalability.html)
<li><strong>עמידות לפיצול (Partition Tolerance)</strong> - המערכת ממשיכה לתפקד גם במקרים בהם נאבדות או מתעכבות מספר הודעות בין שרתי המערכת בגלל בעיות תקשורת.</li>
המתנה לתשובה מהמערכת (אשר סובלת מ-network partition) עלולה להסתיים בשגיאת timeout. לכן, CP הוא בחירה טובה במידה ויש הצדקה עסקית לקריאות וכתיבות אטומיות.
תשובות לבקשות מהמערכת מחזירות את הגרסה הזמינה ביותר של הנתונים הזמינים בשרת הרלוונטי, שאינה בהכרח האחרונה. כתיבה עשויה לקחת זמן מסוים עד שתסתיים, עד אשר התקשורת הבעייתית תיפתר.
לכן, AP הוא בחירה טובה במידה ויש הצדקה עסקית לעבוד במצב של [eventual consistency](#eventual-consistency) או במידה והמערכת צריכה להמשיך לשרת למרות שגיאות בלתי-תלויות.
כאשר קיימים מספר עותקים של אותם נתונים, עלינו להחליט כיצד לסנכרן ביניהם כדי שלקוחות יקבלו תצוגה עקבית של המידע.
ניזכר בהגדרה של עקביות מתוך [משפט CAP](#cap-theorem): כל קריאה מקבלת את הכתיבה העדכנית ביותר או שגיאה.
### עקביות חלשה (Weak Consistency)
לאחר כתיבה, קריאות עשויות לראות או לא לראות את הערך החדש שנכתב. הגישה כאן היא של best effort - המאמץ הטוב ביותר.
גישה זו נפוצה במערכות כמו memcached. עקביות חלשה מתאימה למקרים של מערכות זמן-אמת, כמו VoIP, שיחות וידאו, ומשחקים מרובי משתתפים.
לדוגמה, אם אתה בשיחת טלפון ומאבד קליטה לכמה שניות, כשאתה חוזר אתה לא שומע מה שנאמר בזמן שלא הייתה קליטה.
### עקביות לא מיידית (Eventual Consistency)
לאחר כתיבה, הקריאות יראו בסופו של דבר את מה שנכתב (בדרך כלל תוך מספר מילישניות). הנתונים משוכפלים באופן אסינכרוני.
גישה זו נפוצה במערכות כמו DNS ומייל. עקביות לא מיידית מתאימה למערכות ששומרות על זמינות גבוהה במיוחד.
### עקביות חזקה (Strong Consistency)
לאחר כתיבה, הקריאות יראו תמיד את הערך החדש שנכתב. השכפול מתבצע באופן סינכרוני.
גישה זו נפוצה במערכות קבצים ובמסדי נתונים רלציוניים (RDBMS). עקביות חזקה מתאימה למערכות שדורשות טרנזקציות.
### מקורות וקריאה נוספת
- [Transactions across data centers](http://snarfed.org/transactions_across_datacenters_io.html)
</div>
## דפוסי זמינות (Availability Patterns)
<divdir="rtl">
קיימים שני דפוסים משלימים לתמיכה בזמינות גבוהה: **מעבר אוטומטי (fail-over)** ו-**שכפול (replication)**.
### גיבוי בזמן כישלון (Fail-Over)
#### אקטיבי-פסיבי (Active-Passive)
במבנה אקטיבי-פסיבי, נשלחים heartbeat-ים בין השרת הפעיל לשרת הרזרבי (הפסיבי). אם ה-heartbeat נקטע, השרת הפסיבי לוקח את כתובת ה-IP של הפעיל וממשיך את השירות.
משך זמן ההשבתה תלוי אם השרת הפסיבי פועל מראש במצב 'חם' (hot standby), או שיש להפעילו ממצב 'קר' (cold standby). רק השרת הפעיל מקבל תעבורה.
סוג זה נקרא גם Master-Slave.
#### אקטיבי-אקטיבי (Active-Active)
במבנה אקטיבי-אקטיבי, שני השרתים מקבלים תעבורה ומחלקים ביניהם את העומס.
אם השרתים חשופים פומבית, שרת ה-DNS צריך לדעת על כתובות ה-IP הציבוריות של שניהם. אם השרתים פנימיים, על לוגיקת האפליקציה להכיר את שניהם.
סוג זה נקרא גם Master-Master.
### חסרונות של מעבר אוטומטי
<uldir="rtl">
<li>מעבר אוטומטי מוסיף חומרה ועלות תפעולית.</li>
<li>קיימת אפשרות לאובדן נתונים אם המערכת הפעילה קורסת לפני שהספיקה לשכפל את המידע למערכת הפסיבית.</li>
</ul>
### שכפול (Replication)
#### עבור Master-Slave/Master-Master
נושא זה נדון בפירוט נוסף בחלק על [מסדי נתונים](#database):
נהוג למדוד זמינות לפי זמן פעילות (uptime) או חוסר פעילות (downtime)כאחוז מהזמן שבו השירות פועל. המדד הנפוץ הוא לפי מספר התשיעיות — לדוגמה: שירות עם זמינות של 99.99% מתואר כבעל ארבע תשיעיות.
#### זמינות של 99.9% — שלוש תשיעיות
| Duration | Acceptable downtime|
|---------------------|--------------------|
| Downtime per year | 8h 45min 57s |
| Downtime per month | 43m 49.7s |
| Downtime per week | 10m 4.8s |
| Downtime per day | 1m 26.4s |
#### זמינות של 99.99% — ארבע תשיעיות
| Duration | Acceptable downtime|
|---------------------|--------------------|
| Downtime per year | 52min 35.7s |
| Downtime per month | 4m 23s |
| Downtime per week | 1m 5s |
| Downtime per day | 8.6s |
#### זמינות במקביל לעומת בטור
אם שירות מורכב ממספר רכיבים שעלולים להיכשל, הזמינות הכוללת של השירות תלויה באופן שבו הם מסודרים - בטור או במקביל.
###### בטור
כאשר שני רכיבים עם זמינות קטנה מ-100% מסודרים בטור, הזמינות הכוללת יורדת:
מערכת שמות דומיינים (DNS) ממירה שם דומיין כמו `www.example.com` לכתובת IP.
ה-DNS בנוי בהיררכיה: כאשר ישנם כמה שרתי ניהול בשכבה העליונה. ה-router שלך או ספק האינטרנט (ISP) מספקים כתובות לשרת(י) DNS שיש לפנות אליהם בעת ביצוע חיפוש של כתובת.
שרתי DNS בשכבה נמוכה יותר, מבצעים cache למיפויים, שעלולים להיות לא עדכניים (stale) אם הרשומה המעודכנת עוד לא עברה מהשרת בשכבה הגבוהה עד למטה (propagation delays).
המיפוי של ה-DNS יכול להישמר על ידי הדפדפן או מערכת ההפעלה לפרק זמן מסוים, הנקבע על ידי [TTL – Time To Live](https://he.wikipedia.org/wiki/Time_to_live).
<uldir="rtl">
<li><strong>NS record (Name Server)</strong>– מגדירה את שרתי DNS עבור הדומיין/תת-דומיין.</li>
<li><strong>MX record (Mail Exchange)</strong>– מגדירה את שרתי הדואר לקבלת הודעות.</li>
<li><strong>A record (Address)</strong>– ממפה שם לכתובת IP.</li>
<li><strong>CNAME (Canonical Name)</strong>– ממפה שם לשם אחר (למשל example.com → <code>www.example.com</code>) או לרשומת <code>A</code>.</li>
</ul>
שירותים מנוהלים כמו [CloudFlare](https://www.cloudflare.com/dns/) ו-[Route 53](https://aws.amazon.com/route53/) מספקים DNS מנוהל.
<li>פנייה לשרת DNS מוסיפה עיכוב קל, אם כי ברוב המקרים הוא מתמתן בזכות caching כפי שתואר קודם.</li>
<li>ניהול שרתי DNS יכול להיות מורכב, ולרוב מנוהל על-ידי <ahref="http://superuser.com/questions/472695/who-controls-the-dns-servers/472729">ממשלות, ISPs וחברות גדולות</a>.</li>
<li>שירותי DNS היו יעד לאחרונה ל-<ahref="http://dyn.com/blog/dyn-analysis-summary-of-friday-october-21-attack/">מתקפות DDoS</a>, מה שמנע מגולשים להגיע לאתרים (למשל Twitter) בלי להכיר כתובות IP ידניות.</li>
<i><ahref="https://www.creative-artworks.eu/why-use-a-content-delivery-network-cdn/">מקור: Why use a CDN</a></i>
</p>
רשת הפצת תוכן (CDN) היא רשת גלובלית ומבוזרת של שרתי proxy, אשר מנגישים תכנים ממיקומים הקרובים יותר למשתמש הקצה.
בדרך כלל, קבצים סטטיים כמו HTML/CSS/JS, תמונות וסרטונים, מונגשים על ידי CDN, למרות שיש כאלו כמו CloudFront של Amazon התומכים גם בתכנים דינמיים.
מיפוי ה-DNS שיתקבל ינחה את הלקוחות לאיזה שרת להתחבר.
הגשת תוכן מ-CDN משפר ביצועים משמעותית בשני אופנים:
<uldir="rtl">
<li>המשתמש מקבל תוכן מ-data center הקרוב אליו פיזית.</li>
<li>השרתים אינם צריכים לשרת בקשות שה-CDN מספק במקומם.</li>
</ul>
### דחיפה (Push)
ב-Push CDN התוכן נדחף אל ה-CDN בכל פעם שהוא משתנה בשרת המקור. האחריות על העלאת הקבצים וכתיבת ה-URLs המופנים ל-CDN היא שלך. אתה מגדיר את הזמן שבו פג תוקפו של תוכן מסוים, ומתי הוא מתעדכן.
תוכן מועלה רק כאשר הוא חדש, או עבר שינוי, מה שמפחית תעבורה אבל ממקסם על האחסון. אתרים עם מעט תוכן או תעבורה, או עם תוכן שלא מתעדכן באופן תדיר עובדים טוב עם Push CDN. התוכן נמצא ב-CDN פעם אחת, במקום שישלפו אותו מחדש בתדירות קבועה.
### משיכה (Pull)
ב-Pull CDN התוכן נשלף מהשרת המקור רק כאשר משתמש מבקש אותו לראשונה. הקבצים נשארים בשרת שלך ו-URLs מפנים ל-CDN.
הבקשה הראשונית איטית יותר עד שהקובץ נשמר ב-CDN.
משך הזמן שבו ישאר ה-cache נקבע על-ידי [TTL – Time to Live](https://he.wikipedia.org/wiki/Time_to_live).
ה-Pull CDN חוסך שטח אחסון על ה-CDN, אך עלול ליצור תעבורה מיותרת אם קבצים פגי-תוקף נשלפים שוב לפני ששונו בפועל.
Pull CDN מתאים לאתרים עתירי תעבורה, שכן העומס מתפזר וה-CDN שומר רק קבצים שנדרשו לאחרונה.
### חסרונות (CDN)
<uldir="rtl">
<li>עלויות CDN עשויות להיות משמעותיות בהתאם לנפח התעבורה, אך יש לשקול אותן מול העלויות שהיית משלם ללא CDN.</li>
<li>תוכן עלול להיות מיושן (stale) אם עודכן לפני שפג תוקף ה-TTL.</li>
<li>יש צורך לשנות את כתובות ה-URL של תוכן סטטי כך שיפנו אל ה-CDN.</li>
<i><ahref=http://horicky.blogspot.com/2010/10/scalable-system-design-patterns.html>Source: Scalable system design patterns</a></i>
</p>
מאזן עומסים מבזר בקשות נכנסות מלקוח בין משאבי חישוב שונים כגון שרתי אפליקציה ומסדי נתונים. עבור כל בקשה, הוא מחזיר את התשובה ממשאב החישוב המתאים, אל הלקוח המתאים. מאזן עומסים יעיל ב:
<uldir="rtl">
<li>מניעת שליחת בקשות לשרתים לא יציבים (unhealthy)</li>
<li>מניעת העמסת־יתר על משאבים</li>
<li>סילוק נקודת כשל בודדת (SPOF)</li>
</ul>
מאזן עומסים ניתן למימוש כחומרה (יקר) או כתוכנה כדוגמת HAProxy.
יתרונות נוספים:
<uldir="rtl">
<li><strong>SSL Termination </strong>– טכניקת אבטחה שבה מפענחים בקשות נכנסות לפני שהן מגיעות לשרת הקצה, ומצפינים את התשובות של השרת כך ששרתי ה-backend לא צריכים לבצע את הפעולות היקרות הללו.
<ul>
<li>מוריד את הצורך להתקין תעודות <ahref="https://he.wikipedia.org/wiki/X.509">X.509</a> על כל שרת.
</li>
</ul>
</li>
<li><strong>Session Persistence</strong>– יצירת עוגיות (cookies) וניתוב בקשות של לקוח מסוים לאותו מופע שהתנהל מולו, אם האפליקציה לא מנהלת סשנים בעצמה.
</li>
</ul>
כדי להגן מפני כישלונות נהוג להקים מספר מאזני עומסים, במצב
[Active-Passive](#אקטיבי-פסיבי-active-passive) או [Active-Active](#אקטיבי-אקטיבי-active-active).
מאזן עומסים יכול לנתב את התעבורה על פי מדדים שונים:
- Random
- Least loaded
- Session/cookies
- [Round robin or weighted round robin](https://www.g33kinfo.com/info/round-robin-vs-weighted-round-robin-lb)
- [Layer 4](#layer-4-load-balancing)
- [Layer 7](#layer-7-load-balancing)
### איזון עומסים בשכבה 4
מאזני עומסים בשכבה 4 בוחנים מידע בשכבת התעבורה ([transport layer](#communication)) כדי להחליט כיצד להפיץ בקשות.
בדרך כלל, מדובר בכתובות ה-IP של המקור והיעד ובפורטים שבכותרת (header), ולא בתוכן הפקטה (packet).
מאזני עומסים בשכבה 4 מעבירים את חבילות הרשת אל ומן השרת הנבחר (upstream server) תוך ביצוע
[תרגום כתובות רשת (NAT)](https://www.nginx.com/resources/glossary/layer-4-load-balancing/).
### איזון עומסים בשכבה 7
מאזני עומסים בשכבה 7 בוחנים את [שכבת האפליקציה](#communication) כדי להחליט כיצד להפיץ בקשות. ההחלטה יכולה להתבסס על תוכן הכותרות (headers), גוף ההודעה, ועוגיות (cookies).
מאזן עומסים בשכבה 7 מסיים (terminates) את תעבורת הרשת אל מול הלקוח, קורא את ההודעה, מקבל החלטת איזון-עומסים, ואז פותח חיבור לשרת שנבחר.
למשל, מאזן כזה יכול לשלוח תעבורת וידאו לשרתים שמאחסנים קטעי וידאו, ובמקביל לנתב תעבורת חיוב משתמשים (billing) לשרתים מוקשחים אבטחתית.
לעומת זאת, איזון עומסים בשכבה 4 דורש פחות זמן ומשאבי מחשוב מאשר שכבה 7, אם כי על חומרה מודרנית ההשפעה הביצועית עשויה להיות מזערית.
### גדילה אופקית (Horizontal Scaling)
מאזני עומסים מסייעים גם בגדילה אופקית (Horizontal Scaling), וכך משפרים ביצועים וזמינות.
הרחבת המערכת באמצעות שרתים זולים חסכונית יותר ומביאה לרמת זמינות גבוהה לעומת **הגדלה אנכית (Vertical Scaling)** – חיזוק שרת יחיד בחומרה יקרה. בנוסף, קל יותר לגייס אנשי מקצוע המיומנים בעבודה עם שרתים סטנדרטיים מאשר כאלה המתמחים במערכות ארגוניות ייעודיות ויקרות.
<li>השרתים צריכים להיות stateless: אין לאחסן בהם מידע משתמש כגון סשנים או תמונות פרופיל</li>
<li>ניתן לשמור סשנים באחסון נתונים מרכזי כגון <ahref="#database">מסד־נתונים</a> (SQL או NoSQL) או <ahref="#cache">מטמון</a> פרסיסטנטי (Redis, Memcached)</li>
</ul>
</li>
<li>שרתים בהמשך השרשרת (downstream) למשל cache ו-DB צריכים להתמודד עם יותר חיבורים בו-זמנית ככל שמספר שרתי האפליקציה גדל</li>
</ul>
### חסרונות: מאזן עומסים
<uldir="rtl">
<li>מאזן העומסים עצמו עלול להפוך לצוואר בקבוק בביצועים אם אין לו מספיק משאבים או אם הוא מוגדר בצורה לא נכונה.</li>
<li>הוספת מאזן עומסים כדי להסיר נקודת כשל בודדת (SPOF) מוסיפה מורכבות למערכת.</li>
<li>מאזן עומסים יחיד הוא SPOF, ועבודה עם מספר מאזני עומסים מגדילה עוד יותר את המורכבות.</li>
פרוקסי הפוך הוא שרת אינטרנט המרכז שירותים פנימיים ומספק ממשק אחיד החוצה. בקשות שמגיעות מלקוחות מועברות לשרת ה-backend המסוגל לטפל בהן, ולאחר מכן הפרוקסי מחזיר ללקוח את תגובת השרת.
יתרונות הכלולים בצורה זו:
<uldir="rtl">
<li><strong>אבטחה מוגברת</strong>– הסתרת מידע על שרתי ה-backend, חסימת כתובות IP, הגבלת מספר חיבורים לכל לקוח</li>
<li><strong>גמישות וסקילביליות מוגברת</strong>– הלקוחות רואים רק את כתובת ה-IP של הפרוקסי, מה שמאפשר להגדיל/לשנות שרתים בלי להשפיע על הלקוחות</li>
<li><strong>SSL Termination </strong>– טכניקת אבטחה שבה מפענחים בקשות נכנסות לפני שהן מגיעות לשרת הקצה, ומצפינים את התשובות של השרת כך ששרתי ה-backend לא צריכים לבצע את הפעולות היקרות הללו.
<ul>
<li>מוריד את הצורך להתקין תעודות <ahref="https://he.wikipedia.org/wiki/X.509">X.509</a> על כל שרת.
הפרדת שכבת הרשת משכבת האפליקציה (ידועה גם כשכבת ה-platform), מאפשרת לבצע scaling ולקנפג את שתי השכבות באופן בלתי תלוי. הוספת API חדש גוררת הוספתי שרתי אפליקציה, מבלי להוסיף בהכרח גם שרתי המטפלים בלוגיקת הרשת.
עקרון האחריות היחידה (**single respoinsibility principle**) מעודד סרביסים עצמאיים וקטנים שעובדים יחד. צוותים קטנים המטפלים שירותים קטנים יכלוים להתכוונן בצורה מיטבית לגדילה מהירה.
במונח [Microservices](https://he.wikipedia.org/wiki/מיקרו-שירותים) הכוונה למערך של שירותים קטנים, מודולריים, הניתנים לפריסה עצמאית. כל שירות רץ כתהליך נפרד ומתקשר באמצעות מנגנון פשוט ומוגדר היטב כדי להשיג יעד עסקי.<sup><ahref="https://smartbear.com/learn/api-design/what-are-microservices">1</a></sup>
לדוגמה, ב-Pinterest יכולים להיות המיקרו-סרביסים הבאים: פרופיל משתמש, עוקבים, פיד, חיפוש, העלאת תמונה וכו'.
### גילוי סרביסים (Service Discovery)
מערכות כמו [Consul](https://www.consul.io/docs/index.html), [Etcd](https://coreos.com/etcddocs/latest), ו-[Zookeeper](http://www.slideshare.net/sauravhaloi/introduction-to-apache-zookeeper) מסייעות לשירותים “למצוא” זה את זה על ידי ניהול ומעקב אחר שמות הסרביסים, כתובות IP, ופורטים.
בדיקות דופק ([Health checks](https://www.consul.io/intro/getting-started/checks.html)) — מאמתות את תקינות השירות, לעיתים קרובות באמצעות endpoint HTTP.
<i><ahref=https://www.youtube.com/watch?v=kKjm4ehYiMs>Source: Scaling up to your first 10 million users</a></i>
</p>
### מסדי נתונים רלציוניים (RDBMS)
מסד נתונים רלציוני כמו SQL הוא אוסף פריטי מידע המאורגנים בטבלאות. **ACID** הוא סט מאפיינים של [טרנזקציות](https://he.wikipedia.org/wiki/טרנזקציה_(בסיס_נתונים)) במסדי נתונים רלציוניים:
<uldir="rtl">
<li><strong>Atomicity</strong>– כל טרנזקציה מתבצעת בשלמותה או שלא מתבצעת כלל (all or nothing).</li>
<li><strong>Consistency</strong>– טרנזקציה מעבירה את ה-DB ממצב תקין אחד למצב תקין אחר.</li>
<li><strong>Isolation</strong>– הרצה במקביל של טרנזקציות שקולה להרצה סדרתית שלהן.</li>
<li><strong>Durability</strong>– לאחר שטרנזקציה הסתיימה, היא תישאר קבועה גם בקריסת מערכת.</li>
</ul>
ישנן טכניקות רבות להגדלת (scaling) מסד נתונים רלציוני:
ה-master משרת קריאות וכתיבות (RW), ומשכפל את הכתיבות ל-slave אחד או יותר שמשרת רק קריאות (R). ה-slaves יכול לשכפל את הדאטא ל-slaves נוספים במבנה של מעין עץ.
אם ה-master נופל, המערכת יכולה להמשיך לרוץ במצב read-only עד שאחד ה-slaves מקודם להיות master, או שמקצים master חדש.
<i><ahref=https://www.youtube.com/watch?v=kKjm4ehYiMs>Source: Scaling up to your first 10 million users</a></i>
</p>
בפדרציה (או חלוקה פונקציונלית) מפצלת DB לפי הפונקציות שלו. לדוגמה, במקום DB מונוליטי בודד, ניתן לנהל 3 DBים נפרדים: **פורומים, משתמשים, ומוצרים**, מה שגורר פחות תעבורת קריאה וכתיבה לכל DB, ועקב כך פחות replication lag.
מסדי נתונים קטנים יותר מאפשרים יותר דאטא שנכנס בזיכרון, שיכול להוביל ליותר cache hits מוצלחים. בלי master אחד מרכזי שאחראי לדאוג לכל הכתיבות באופן סדרתי, אפשר לכתוב במקביל ל-DB שונים עבור כל סוג של נתונים, ובכך להגדיל את ה-throughput.
##### חסרונות: פדרציה
<uldir="rtl">
<li>לא יעיל אם הסכימה דורשת טבלאות עצומות.</li>
<li>הלוגיקה באפליקציה צריכה להתעדכן כדי לדעת לאיזה DB לפנות.</li>
<li>ביצוע פעולת JOIN משני DBים קשה יותר ודורש <ahref="http://stackoverflow.com/questions/5145637/querying-data-by-joining-two-tables-in-two-database-on-different-servers">server link</a>.</li>
<li>דורשת הוספה של עוד חומרה ומורכבות.</li>
</ul>
##### מקורות וקריאה נוספת: פדרציה
- [Scaling up to your first 10 million users](https://www.youtube.com/watch?v=kKjm4ehYiMs)
חלוקה מפזרת את הדאטא בין DBים שונים כך שכל DB מנהל חלק (subset) מסוים של הדאטא. נסתכל למשל על DB של משתמשים, ככל שכמות המשתמשים עולה, יותר חלקים (shards) מתווספים ל-cluster.
בדומה ליתרונות של [פדרציה](), חלוקה גוררת פחות תעבורה של קריאות וכתיבות, פחות שכפול, ויותר cache hits. גודל ה-index גם קטן, מה שלרוב משפר את קצב ביצוע השאילתות.
אם shard אחד נופל, כל שאר ה-shards עדיין פעילים, למרות שנרצה לבצע שכפול מסוים כדי להימנע מאיבוד מידע. כמו פדרציה, אין master מרכזי אחיד שכל הכתיבות עוברות דרכו, מה שמאפשר לכתוב במקביל ל-DBים שונים ולהגביר את ה-throughput.
דרכים נפוצות לבצע sharding לטבלה של משתמשים הן באמצעות האות הראשונה של שם המשפחה, או המיקום הגיאוגרפי של המשתמש.
##### חסרונות: Sharding
<uldir="rtl">
<li>הקוד צריך לדעת באיזה shard הנתונים נמצאים, דבר הגורר שאילתות SQL מורכבות.</li>
<li>התפלגות נתונים עלולה להיות לא אחידה, קבוצה של power users על אותו ה-shard יכולה להביא לעומס מוגבר לאותו ה-shard ביחס לאחרים.
ביצוע rebalance גורר מורכבות נוספת. פונקציית sharding המבוססת על <ahref="http://www.paperplanes.de/2011/12/9/the-magic-of-consistent-hashing.html">Consistent Hashing</a> יכול להקטין את כמות הדאטא שמועבר בין shardים.
<li>ביצוע פעולת JOIN על מספר shardים מורכבת יותר.</li>
<li>sharding מוסיף חומרה ומורכבות.</li>
</ul>
###### מקורות וקריאה נוספת
- [The coming of the shard](http://highscalability.com/blog/2009/8/6/an-unorthodox-approach-to-database-design-the-coming-of-the.html)
דנורמליזציה שואפת לשפר ביצועים של קריאות על חשבון חלק מהביצועים של הכתיבות. עותקים מיותרים (משוכפלים באופן מכוון, Redundant) של הדאטא נכתבים במספר טבלאות שונות כדי להימנע מביצוע JOINים יקרים. חלק מה-RDBMSים כמו [PostgreSQL](https://en.wikipedia.org/wiki/PostgreSQL) ו-Oracle תומכים ב-[materialized views](https://en.wikipedia.org/wiki/Materialized_view) אשר דואגים לשמירה של מידע מיותר ושמירת עותקים מיותרים עקביים.
כאשר הדאטא מבוזר באמצעות טכניקות כמו [פדרציה]() או [חלוקה](), ניהול JOINים בין ריכוזי מידע שונים מגדיר את המורכבות. דנורמליזציה יכולה לייתר את הצורך לבצע JOINים מורכבים.
ברוב המערכות, כמות הקריאות גדולה בהרבה מכמות הכתיבות, ביחס של 100:1 ואף 1000:1. קריה יכולה להוביל לביצוע JOIN מורכב ויקר, מה שעולה בביצוע פעולות דיסק זמן רב.
##### חסרונות: דנורמליזציה
<uldir="rtl">
<li>הדאטא משוכפל.</li>
<li>אף שדנורמליזציה משפרת קריאות, היא דורשת שכבת אילוצים (constraints) וחוקים מסוימים כדי לשמור על עקביות העותקים — מה שמסבך את תכנון ה-DB.</li>
<li>במערכת עם עומס כתיבה כבד ייתכן שדנורמליזציה דווקא תפגע בביצועים.</li>
חשוב לבצע **Benchmark**ו-**Profile** כדי לדמות עומסים ולגלות צווארי-בקבוק.
<uldir="rtl">
<li><strong>Benchmark</strong>– סימולציית עומס כבד באמצעות כלים כמו <ahref="http://httpd.apache.org/docs/2.2/programs/ab.html">ab</a>.</li>
<li><strong>Profile</strong>– הפעלת כלים כגון <ahref="http://dev.mysql.com/doc/refman/5.7/en/slow-query-log.html">Slow Query Log</a> למעקב אחר בעיות ביצועים.</li>
</ul>
התוצאות של השימוש בכלים אלו עשויה להוביל לאופטימיזציות הבאות:
##### הידוק הסכימה (Tighten up the schema)
<uldir="rtl">
<li>MySQL כותב לדיסק בבלוקים עוקבים, לגישה מהירה.</li>
<li>השתמש ב-<code>TEXT</code> למקטעי טקסט גדולים (למשל פוסטים של בלוג); מאפשר גם חיפושים בוליאניים. השדה מאחסן מצביע על הדיסק שמטרתו לאתר את בלוק הטקסט.</li>
<li>השתמש ב-<code>INT</code> למספרים עד 2<sup>32</sup> (≈ 4 מיליארד).</li>
<li>השתמש ב-<code>DECIMAL</code> לערכים כספיים – כדי להימנע משגיאות Floating Point.</li>
<li>הימנע מאחסון <code>BLOB</code>-ים גדולים; שמור רק את המיקום שלהם.</li>
<li><code>VARCHAR(255)</code>– המספר שמנצל בתים בצורה מיטבית בחלק מה-RDBMS-ים.</li>
<li>הגדר <code>NOT NULL</code> כשאפשר כדי <ahref="http://stackoverflow.com/questions/1017239/how-do-null-values-affect-performance-in-a-database-search">לשפר ביצועי חיפוש</a>.</li>
</ul>
##### השתמש באינדקסים טובים (Use good indices)
<uldir="rtl">
<li>עמודות הנשלפות באמצעות פקודות כמו <code>SELECT</code>, <code>GROUP BY</code>, <code>ORDER BY</code>, <code>JOIN</code> יכולות להיות מהירות יותר עם אינדקסים.</li>
<li>אינדקסים מיוצגים בדרך-כלל כ-<ahref="https://en.wikipedia.org/wiki/B-tree">B-Tree</a> מאוזן: שומר על הדאטא ממוין, ומאפשר חיפוש/הוספה/מחיקה בזמן לוגריתמי.</li>
<li>אינדקס שומר את הנתונים בזיכרון – אבל צורך יותר מקום.</li>
<li>כתיבות עלולות להיות איטיות יותר כי צריך לעדכן גם את האינדקס.</li>
<li>בעת טעינת נתונים גדולה ייתכן שיותר מהיר להשבית אינדקסים, לטעון את הנתונים ואז לבנות את האינדקסים מחדש.</li>
</ul>
##### מניעת JOIN יקר
<uldir="rtl">
<li>לשקול <ahref="#">דנורמליזציה</a> כאשר הביצועים דורשים זאת.</li>
</ul>
##### חלוקה לטבלאות (Partitioning)
<uldir="rtl">
<li>חלוקה לטבלאות (Partitioning) מאפשרת לשמור את ה־hot spots (אזורים “חמים” בטבלה, כלומר רשומות שנקראות/נכתבות הכי הרבה) במחיצה קטנה שנשארת בזיכרון, ולכן שאילתות על הנתונים העדכניים רצות מהר יותר ומעמיסות פחות על הדיסק.</li>
</ul>
##### כוונון Query Cache
<uldir="rtl">
<li>במקרים מסוימים, <ahref="https://dev.mysql.com/doc/refman/5.7/en/query-cache.html">query cache</a> עלול לגרום ל<ahref="https://www.percona.com/blog/2016/10/12/mysql-5-7-performance-tuning-immediately-after-installation/">בעיות ביצועים</a>.</li>
- [Tips for optimizing MySQL queries](http://aiddroid.com/10-tips-optimizing-mysql-queries-dont-suck/)
- [Is there a good reason i see VARCHAR(255) used so often?](http://stackoverflow.com/questions/1217466/is-there-a-good-reason-i-see-varchar255-used-so-often-as-opposed-to-another-l)
- [How do null values affect performance?](http://stackoverflow.com/questions/1017239/how-do-null-values-affect-performance-in-a-database-search)
לעומת SQL קלאסי, NoSQL הוא אוסף של מבני נתונים הנשמרים בתור **Key-Value Store**, **Document Store**, **Wide Column Store** או **Graph Database**.
הנתונים מנורמלים פחות, ופעולות JOIN מבוצעות לרוב בקוד האפליקציה עצמה.
רוב האחסונים מסוג NoSQL אינם תומכים בטרנזקציות ACID מלאות ומספקים [עקביות לא מיידית](#eventual-consistency).
מקובל לסמן את מאפייני NoSQL בראשי התיבות **BASE** (תווך שימוש ב[משפט CAP](#cap-theorem), תכונות BASE מתעדפות זמינות (A) על פני עקביות (C)):
<uldir="rtl">
<li><strong>Basically Available</strong>– המערכת מבטיחה זמינות.</li>
<li><strong>Soft State</strong>– מצב המערכת עשוי להשתנות עם הזמן, גם ללא קלט.</li>
<li><strong>Eventual Consistency</strong>– המערכת תהפוך עקבית בסופו של דבר, בהנחה שלא מתקבל קלט נוסף בתקופה זו.</li>
</ul>
מעבר לבחירה בין [SQL ל-NoSQL](#sql-or-nosql), חשוב להבין איזה סוג NoSQL מתאים ביותר לשימוש שלך. נדון בסוגים **Key-Value Stores**, **Document Stores**, **Wide Column Stores**ו-**Graph Databases**.
---
#### אחסון Key-Value
> הפשטה: Hash Table
אחסון Key-Value לרוב מאפשר קריאות וכתיבות ב-O(1) ומגובה באמצעות זיכרון או SSD. ניתן לשמור מפתחות ב[סדר לקסיקוגרפי](), מה שמאפשר שליפה יעילה של טווחי המפתחות.
אחסון זה מאפשר תמיכה ב-metadadta עם ערכים.
אחסון זה מספק ביצועים גבוהים ולרוב בשימוש עבור מודלי דאטא פשוטים או לדאטא שמשתנה במהירות, כמו שכבת cache in-memory. כיוון שסוגי אחסון זה מציעים סט מצומצם של פעולות, המורכבות נמצאת בשכבת האפלקציה אם נדרשות פעולות נוספות.
אחסון זה הוא הבסיס למערכות מורכבות יותר כמו document store ובמקרים מסוימים גם graph db.
- [Disadvantages of key-value stores](http://stackoverflow.com/questions/4056093/what-are-the-disadvantages-of-using-a-key-value-table-over-nullable-columns-or)
אחסון Document מתרכז סביב מסמכים (XML, JSON, binary, etc.) כאשר מסמך שומר את כל המידע שקשור לאובייקט מסוים. אחסון זה מספק API או query language כדי לתשאל על בסיס המבנה הפנימי של המסמך עצמו. נשים לב, כי הרבה אחסונים מסוג Key-Value כוללים פיצ'רים כדי לעבוד עם ה-metadata של ה-value, מה שמטשטש את הגבול בין שני סוגי אחסון אלו.
על בסיס המימוש הספציפי מאחורי הקלעים, מסמכים מאורגנים לפי אוספים, תגים, metadata או תיקיות. למרות שמסמכים יכולים להיות מאורגנים או מקובצים יחדיו, יכולים להיות להם שדות שונים לחלוטין אחד מהשני.
אחסונים כמו [MongoDB](https://www.mongodb.com/mongodb-architecture) ו-[CouchDB](https://blog.couchdb.org/2016/08/01/couchdb-2-0-architecture/) מציעים שפה דמוית SQL על מנת לבצע שאילתות מורכבות.
[DynamoDB](http://www.read.seas.harvard.edu/~kohler/class/cs239-w08/decandia07dynamo.pdf) תומך גם ב-Key-Value וגם במסמכים.
אחסונים אלו מספקים גמישות גבוהה ולרוב בשימוש עבור דאטא שמשתנה בתדירות גבוהה.
אחסון Wide Column עובד עם יחידת דאטא בסיסית שהיא עמודה (זוג שם/ערך).
עמודות מקובצות תחת column families (כמו טבלת SQL). Super column families הן אוסף של column families. אפשר לגשת לכל עמודה בנפרד עם row key, כאשר עמודות בעלות אותו row key יוצרות שורה.
כל ערך מכיל timestamp עבור גרסאות ופתרון קונפליקטים.
דוגמאות: Google הציגה את [Bigtable](http://www.read.seas.harvard.edu/~kohler/class/cs239-w08/chang06bigtable.pdf) כאחסון הראשון מסוג זה. זה השפיע על פרויקט הקוד הפתוח [HBase](https://www.edureka.co/blog/hbase-architecture/) (באקוסיסטם Hadoop) ו-[Cassandra](http://docs.datastax.com/en/cassandra/3.0/cassandra/architecture/archIntro.html) של Facebook.
מערכות כמו Bigtable, HBase ו-Cassandra שומרות מפתחות בסדר **לקסיקוגרפי**, וכך מאפשרות שליפת טווחי מפתחות ביעילות.
אחסונים אלו מציעים זמינות גבוהה, וסקילביליות גבוהה. לרוב משתמשים בהם לאחסון דאטאסטים מאוד גדולים.
##### מקורות וקריאה נוספת: אחסון Wide Column
- [SQL & NoSQL, a brief history](http://blog.grio.com/2015/11/sql-nosql-a-brief-history.html)
ב-DB גרפי כל **צומת** (Node) הוא רשומה, וכל **קשת** (Arc/Edge) היא קשר בין שני צמתים.
ה-DB מותאם לייצוג קשרים מורכבים – עם הרבה מפתחות זרים (Foreign Keys) או יחסי Many-to-Many.
אחסון זה מאפשר ביצועים גבוהים למודלים עם יחסים מורכבים, למשל כמו רשת חברתית. הטכנולוגיה הזו יחסית חדשה ופות נפוצה; ייתכן קושי למצוא כלי פיתוח ומשאבים. הרבה אחסונים מסוג זה נגישים רק באמצעות
- [Explanation of base terminology](http://stackoverflow.com/questions/3342497/explanation-of-base-terminology)
- [NoSQL databases a survey and decision guidance](https://medium.com/baqend-blog/nosql-databases-a-survey-and-decision-guidance-ea7823a822d#.wskogqenq)
<i><ahref=http://horicky.blogspot.com/2010/10/scalable-system-design-patterns.html>Source: Scalable system design patterns</a></i>
</p>
שמירת נתונים ב-cache משפרת את זמן טעינת הדפים, ומפחיתה את העומס על השרתים ועל ה-DBים.
במודל זה, השרת מחפש האם הבקשה שהגיעה כבר נעשתה בעבר, ומנסה למצוא תוצאה מוכנה, כדי לחסוך את זמן העיבוד על אותה ההודעה מחדש.
מסדי נתונים לרוב מרוויחים מהתפלגות אחידה של קריאות וכתיבה על פני החלוקות שלהם (partitions). פריטים שניגשים אליהם הרבה יכולים ליצור זינוק בקריאות וכתיבה לחלוקה מסוימת וליצור צווארי-בקבוק. כאשר שמים שכבת cache לפני ה-DB, פיקים קיצוניים בתעבורה נספגים בזיכרון המהיר של המטמון ולא מעמיסים על ה-DB.
### מטמון בצד לקוח
מטמון יכול להיות ממוקם בצד הלקוח (מערכת ההפעלה או הדפדפן), [צד השרת](#reverse-proxy-web-server), או בשכבה נפרדת.
### מטמון CDN
ניתן להסתכל על [CDN](#content-delivery-network) גם בתור שכבה של מטמון.
### מטמון בשרת
[פרוקסי הפוך](#reverse-proxy-web-server) ומנגנוני cache כמו [Varnish](https://www.varnish-cache.org/) יכולים להנגיש תוכן סטטי ודינמי ישירות. שרתי web יכולים גם לבצע cache לבקשות כדי להחזיר תשובות בלי צורך להגיע עד לשרתי האפליקציה.
### מטמון במסד נתונים
ה-DB לרוב כולל רמה מסוימת של caching באופן דיפולטי, אשר מאופטמת למקרה הכללי. ביצוע כיוונון להגדרות האלה עבור תבניות שימוש ספציפיות יכול להאיץ את הביצועים.
### מטמון באפליקציה
מטמון in-memory כמו Memcached ו-Redis מהווים אחסון key-value בין האפליקציה ובין ה-DB. כיוון שהדאטא מוחזק ב-RAM, זה הרבה יותר מהיר מ-DB טיפוסי שם הדאטא נשמר על הדיסק.
ה-RAM יותר מוגבל מהדיסק, לכן אלגוריתמים של [cache invalidation](https://en.wikipedia.org/wiki/Cache_algorithms) כמו [least recently used (LRU)](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)) עוזרים "לזרוק" את הפריטים שלא נגעו בהן זמן רב (cold) ולהשאיר את הנתונים הרלוונטיים (hot).
ל-Redis יש את הפיצ'רים הבאים:
<uldir="rtl">
<li>אופציה פרסיסטנטית</li>
<li>מבני נתונים כמו sorted sets ו-lists</li>
</ul>
יש הרבה רמות שניתן לבצע בהן cache, והן מתחלקות לשתי קטגוריות עיקריות: **database queries**ו-**objects**:
- Row level
- Query-level
- Fully-formed serializable objects
- Fully-rendered HTML
באופן כללי, עדיף להימנע מביצוע caching לקבצים, דבר המקשה על cloning - שכפול, ועל auto-scaling - הוספה או הורדה של מכונות.
### מטמון ברמת שאילתה
בכל פעם שמבצעים שאילתה ל-DB, נעשה hash על השאילתה בתור key, ונאחסן את התוצאה שלה ל-cache. הגישה הזאת סובלת מבעיות של תוקף:
<uldir="rtl">
<li>קשה למחוק תוצאה שמורה של שאילתה מורכבת (באילו טבלאות הוא משתמש)</li>
<li>שינוי קטן ב-DB מחייב מחיקה של כל השאילתות הרלוונטיות שנשמרו, התוצאות כבר לא מעודכנות</li>
</ul>
### מטמון ברמת אובייקט
במקום לשמור תוצאה של שאילתה, נחשוב על הנתונים כמו על "אובייקט" בקוד.
האפליקציה שולפת את המידע מה-DB ומרכיבה ממנו מופע שמתאר אותו תוך שימוש ב-class כלשהו:
<uldir="rtl">
<li>יש להסיר את האובייקט מה-cache אם אחד מהשדות שלו השתנה</li>
<li>מאפשר עיבוד אסינכרוני: תהליכים ברקע יכולים להרכיב אובייקטים על ידי הדבר האחרון שנמצא ב-cache</li>
<i><ahref=http://www.slideshare.net/tmatyashovsky/from-cache-to-in-memory-data-grid-introduction-to-hazelcast>Source: From cache to in-memory data grid</a></i>
</p>
האפליקציה אחראית לבצע קריאה וכתיבה מול האחסון. ה-cache לא מדבר עם האחסון ישירות. האפליקציה עושה את הדברים הבאים:
<uldir="rtl">
<li>חיפוש הרשומה ב-cache, מה שמוביל ל-cache miss</li>
<li>טוענים את הרשומה מהאחסון</li>
<li>שומרים את התוצאה ב-cache</li>
<li>מחזירים את הרשומה ללקוח</li>
</ul>
```python
def get_user(self, user_id):
user = cache.get("user.{0}", user_id)
if user is None:
user = db.query("SELECT * FROM users WHERE user_id = {0}", user_id)
if user is not None:
key = "user.{0}".format(user_id)
cache.set(key, json.dumps(user))
return user
```
למשל [Memcached](https://memcached.org/) יכול להיות בשימוש בקטע קוד מהסוג הזה, האפליקציה היא זו שקובעת מתי לקרוא ולכתוב ל-cache.
אחרי שהפריט נכתב ל-cache, כל קריאה חוזרת אליו מהירה במיוחד. אסטרטגייה זו נקראת גם lazy loading. רק מידע שנעשתה אליו גישה נכנס ל-cache, מה שמונע שמירה של הרבה דאטא שאין בו שימוש.
##### חסרונות: cache-aside
<uldir="rtl">
<li>כל cache miss עולה לנו ב-3 שלבים, מה שיכול לגרום לעיכוב משמעותי.</li>
<li>הדאטא יכול להתיישן אם הוא מתעדכן ב-DB. הרשומה ב-RAM כבר לא רלוונטית. אפשר לקבוע TTL קצר או לעבור לאסטרטגיה של write-through.</li>
<li>כאשר שרת ה-cache קורס, הוא מוחלף באחד חדש שעולה ריק מאפס, מה שגורם ל-latency גבוה.</li>
האפליקציה מתייחסת אל ה-cache כאחסון המרכזי, מבצעת קריאות וכתיבות מולו, כאשר ה-cache אחרי לבצע את הקריאות והכתיבות מול ה-DB:
<uldir="rtl">
<li>האפליקציה מוסיפה או מעדכנת רשומה ב-cache</li>
<li>ה-cache מסנכרן כל כתיבה ל-DB</li>
<li>הפעולה חוזרת למשתמש</li>
</ul>
Application code:
```python
set_user(12345, {"foo":"bar"})
```
Cache code:
```python
def set_user(user_id, values):
user = db.query("UPDATE Users WHERE id = {0}", user_id, values)
cache.set(user_id, user)
```
אסטרטגיה זו איטית יותר בשל ביצוע ה-write (שתי כתיבות במקום אחת), אבל קריאות שיקרו בהמשך של הדאטא שזה עתה נכתב יהיו מהירות. המשתמשים לרוב יותר סבלניים לגבי שיהוי כאשר מעדכנים את הדאטא לעומת קריאה שלו. הדאטא ב-cache תמיד עדכני.
##### חסרונות: write through
<uldir="rtl">
<li>כאשר עולה node חדש של ה-cache הוא לא יכיר אף רשומה עד שהיא לא תתעדכן ב-DB, המערכת צריכה קודם לבצע עדכונים לאותם הערכים. שילוב של cache-aside עם write-through יכול לצמצם את הבעיה - אם אין ערך, האפליקציה טוענת מה-DB ושומרת ב-cache, ואז העדכון נכנס גם ל-cache וגם ל-DB.</li>
<li>חלק גדול מהנתונים שנכתבים לעולם לא ייקראו, כל כתיבה נשמרת ל-cache ותופסת RAM יקר בלי שבהכרח יש בה שימוש. אפשר למנוע את בזבוז הזיכרון באמצעות TTL על ערכים שאינם נקראים. הערכים שעדיין "חמים" יתעדכנו מחדש ב-TTL בכל קריאה או כתיבה.</li>
<i><ahref=http://www.slideshare.net/tmatyashovsky/from-cache-to-in-memory-data-grid-introduction-to-hazelcast>Source: From cache to in-memory data grid</a></i>
</p>
אפשר לקנפג את ה-cache כך שהוא ירענן כל פריט שניגשו אליו לאחרונה לפני שתפוג תקופת ה-TTL שלו (יבצע fetch מול ה-DB).
אם המערכת יודעת לחזות בצורה טובה אילו פריטים יבוקשו שוב בקרוב, נקבל latency נמוך יותר משיטות read-through רגילות: המשתמש יקבל תשובה מ-cache שכבר עבר עדכון ברקע (ואין cache miss).
##### חסרונות: refresh-ahead
<uldir="rtl">
<li>חיזוי לא מדויק של פריטים שיידרשו בעתיד יכול לגרום לרענון מיותר, ובזבוז של RAM וחיבורים ל-DB, מה שעלול לגרום לתוצאות איטיות יותר מאשר בלי לבצע refresh-ahead.</li>
</ul>
### חסרונות: cache
<uldir="rtl">
<li>חייבים לשמר עקביות בין ה-cache ומקור המידע האמיתי (ה-DB) באמצעות <ahref="https://en.wikipedia.org/wiki/Cache_algorithms">cache invalidation</a>, כללים הקובעים מתי מוחקים או מרעננים ערכים ב-cache.</li>
<li>cache invalidation הוא אתגר לא פשוט, יש מורכבות נוספת להבנה מתי בדיוק ערך ב-cache הוא כבר לא עדכני. </li>
<li>נדרשים לבצע שינויים בקוד ובתשתית להוספת רכיבים כמו Redis או Memcached.</li>
</ul>
### מקורות וקריאה נוספת
- [From cache to in-memory data grid](http://www.slideshare.net/tmatyashovsky/from-cache-to-in-memory-data-grid-introduction-to-hazelcast)
- [Scalable system design patterns](http://horicky.blogspot.com/2010/10/scalable-system-design-patterns.html)
- [Introduction to architecting systems for scale](http://lethain.com/introduction-to-architecting-systems-for-scale/)