המחיר של IO.

ב־27.11.2007, מאת ארתיום; פורסם תחת: תכנה חופשית, אינטרנט, לינוקס, פיתוח, תכנה ומחשבים, CppCMS; ‏6 תגובות

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

  • להשתמש בבסיס נתונים רגיל כמאגר איכסון ולכתוב מערכת caching פנימית המשותפת ל־threads שונים (למשל מבוססת על STL map).
  • להשתמש בפתרונות קיימים -- SQL עם memcached.
  • להשתמש ב־Berkeley DB.

הפתרון הראשון נראה כמעניין ביותר אבל הוא בעייתי ברגע שצטרך לחשוב על scaling -- אז תמיד צריכה להיות אופציית "גיבוי" של SQL+memcache. בנוסף הוא נוטה להפוך ליותר מורכב אם נרצה לשתף את המידע בין תהליכים דרך זכרון משותף, מפני ש־map רגיל לא יעבוד.

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

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

הכנתי קוד ++C פשוט שמעלה 20 thread־ים שכל אחד מהם מצבע משיכות/כתיבות אקראיות של מידע כשהיחס ביניהם הוא כתיבה אחת לכל 10 פעולות. הכנסתי 10,000 מפתחות ונתונים של 256 בתים כל אחד, הרצתי 1,000,000 פעולות כאלה ובדקתי זמנים עם time.

  1. עבודה עם std::map הביאה לכ־710,000 פעולות השניה (ללא SQL).
  2. עבודה עם Berkeley DB סיפקה כ־204,000 פעולות בשניה. שזה קצת יותר איטי בהשוואה ל-std::map אבל יש הבדל משמעותי בכך שמדובר בבסיס נתונים שמספק תכונות הרבה יותר חזקות מ־map הפשוט.
  3. עבודה מול memcached בלבד (ללא גישה לבסיס נתונים בכלל!) סיפקה רק 22,100 פעולות בשניה שזה הבדל של סדר גודל בהשוואה ל־BDB. במהלך הרצת הבדיקה, המעבד שהה כ־50% מהזמן שלו ב־kernel mode.

המשמעות היא פשוטה: I/O הוא יקר, כל עוד אפשר להמנע ממנו, עשה זאת! אין תחליף לגישה ישירה לזכרון.

אני מתחיל לחשוב יותר ויותר לכיוון של BDB על מנת לעבוד ישירות עם מבנים ומחלקות שונות ב־++C במקום להשתמש בכל מיני "wrappers" מסביב ל־SQL. כי הוא נותן פתרון אידיאלי כמעט לכל דבר שתרצה.

הקוד שהשתמשתי בו נמצא כאן (א+ב).

תגובות

מאיר, ב־27.11.2007, 11:50

לכל גישה יש יתרונות וחסרונות (דוגמא: memcached ניתן לבזר בין מספר שרתים, cache בבסיס נתונים אפשר לשמור עם clustering וכן הלאה).

האופטימלי היה להפריד בין הגישה ל-caching בקוד לבין היישום שלו. דוגמא היא למה ש-Django עשו: http://www.djangoproject.com/documentation/cache/

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

ארתיום, ב־27.11.2007, 16:46
הייתי רוצה ב-Django את היכולת לקשר בין אובייקט cache למודל וכל פעם שהוא משתנה לבטל את ה-cache, שך שלא יהיה תלוי בזמן בלבד

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

מבחינה זו, ברגע שאתה משתמש ב־BDB אתה מקבל consistent cache מאוד יעיל ומהיר וכל הרכיבים החשובים של בסיס נתונים -- כמו אמינות, Journaling, טרנזקציות (אם אתה רוצה) וגם גישה יעילה לרכיבים סדרתיים שאי אפשר לשלוף אותם סתם לפי מפתח (כמו ב־memcached).

היתרון המובהק הנוסף של BDB, זה שאין לך שכבת תקשורת בכלל ושאתה עובד עם מבני C++/C במקום לעבוד עם ORM שעושה שכבת הדבקה של מבני C/Python/Perl ל־SQL ובחזרה. בגלל זה רעיון שימוש ב־BDB הוא כל כך מפתה.

מצד שני, הדרך היחידה לעשות scaling היא להשתמש ב־DB Replication כשיש אחד הראשי שאליו מבצעים כתיבות ויש את העותקים שלו שזה ידרוש שינויים ביישום עצמו. זה גם משכפל את ה־cache של היישומים.

...

מה שכן, כל הנושא של ארכיטקטורות של מערכות כאלה נורא מעניין :)

מאיר, ב־27.11.2007, 22:28

לא הבנתי היכן כאן הקשר ל-ORM ?

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

ככל הזכור לי, גם ה-cache שמתבצע שם ל-backend של בסיס הנתונים (בין כל תשתיות ה-cache הזמינות) אינו עובר דרך שכבת ה-ORM.

ארתיום, ב־27.11.2007, 23:02

ראשית כל אני רוצה להפריד בין שני סוגים של cache. - שמירת נתונים מעובדים (כמו שאמרת פלט של View כלשהו למשל). - שמירת נתוני DB, למשל sessions, פרופילים של משתמשים, שאילתות.

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

הקשר ל־ORM ל־BDB הוא כזה: א. אתה שומר מידע במשתנים של SQL אלא מידע מזכרון של ממש. למשל struct או class של C. ב. הגישה שלך למידע דרך API של C++/C ולא דרך שאילתות בשפה אחרת (SQL).

כך שהמבנים שאתה רוצה לשמור ממופים ישירות למידע שאתה שומר בבסיס הנתונים ואתה לא צריך לעשות תרגום C->SQL ו־SQL->C. אפשר למעשה להגיד שאתה שומר ב־BDB אובייקטים מוכנים (עם מגבלות מסוימות). לכן, כל שכבת ORM שמאפיינת יישומים OOP שרוצים לעבוד מול SQL היא חסרת משמעות.

מאיר, ב־29.11.2007, 13:13

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

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

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

עדיין לא ברור לי איך שכבת ה-ORM קשורה כלל ל-cache. העובדה ש-bdb ישמש לשמירת מבנים ישירים היא חסרון ולא יתרון (מספיק שתשנה את המבנה).

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

ארתיום, ב־29.11.2007, 23:21

לגבי BDB, לא התכוונתי להשתמש בו בתור מערכת cache אלא רציתי להשתמשו בו במקום SQL+cache.

במילים אחרות, אני לא רוצה לעבוד מול xyzSQL כלשהו בתור תשתית שמירת הנתונים אלא לעבוד מול BDB ישירות (שממש את החלק של cache בצורה יעילה מאוד).

כלומר במקום למשל להשתמש בתשתית PHP/Python/Perl + MySQL + memcached אני משתמש ב־C++ + Berkeley DB כש־BDB מספק לי גם cache יעיל וגם בסיס נתונים על כל דבר.

אם בפתרון memcached+SQL אני עובד: - תשלח בקשה לשרת memcached עבור נתון - קבל תשובה - יש תעבוד איתו - אין תשלח שאילתת SQL - בסיס נתונים מסתכל אם יש את זה ב-cache שלו בזכרון - אם יש החזר - אם אין תביא דף מהדיסק - תחזיר תשובה.

כשאני עובד עם BDB זה מתבצע אחרת: - אם יש נתון בזכרון ב-cache - תחזיר אותו - אם אין תביא אותו מהקובץ

שזה הרבה יותר מהיר ופחות עמוס.

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

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

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

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

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

כלומר, אני כן חושב שיהיה מקום לקימפול.

תשים לב, אני לא מייעד את המערכת למשהו כללי וקל. אין שום משמעות להריץ אותה אלא אם יש לך Dedicated או לפחות Virtual Server. אם אתה רוצה מערכת עבור 1,000 מבקרים ביום מוטב להשתמש בפתרונות LAMP.

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

הוסף תגובה:

 
 כתובת דוא"ל לא תוצג
 

ניתן לכתוב תגובות עם שימוש בתחביר Markdown.

חובה לאפשר JavaScript כדי להגיב.

דפים

נושאים