מניעת race condition ב-DB
-
לא הבנתי למה לא נעילה ברמת הקוד?
גם את הנעילה הזאת אתה מקבל בחינם מהקוד..
ולגופו של ענין, הרי אתה לא ניגש לdb מחוץ לקוד, אז אני לא רואה סיבה למה לא להשתמש בזה.
אתה גם יכול לעשות נעילה בקוד פר ארגון, כך שלא יהיו לך הרבה התנגשויות.אני מממש דבר כזה בחנות אינטרנטית, כשיש צורך להגביל מוצר לפי כמות במלאי, ואני לא רוצה שיווצר מצב דומה למה שתיארת.
-
@yossiz אמר במניעת race condition ב-DB:
@clickone עזוב טבלאות אחרות, בטעות הזכרתי שיש טבלה של org.
יש רק טבלה אחת של charges עם 3 עמודות (שקשורות לנושא שלנו): id, orgId, chargeNumber
ה-chargenumber אמור להיות מספר ה-charge לפי הארגון. זה לא יכול להיות autoincrement כי אפשר שתיים עם אותו מספר כאשר הם משוייכים לארגון אחרלא שיש לי פתרון. אבל הרי הפרנציפ נמצא בכל טבלה עם מספור ID אוטומטי, רק שכאן אתה צריך לממש את זה ידנית, אבל מסתמא אפשר להעתיק את המימוש הזה משם?
-
יש לי טבלה של
charges
. כלcharge
משוייך ל-org
על ידי עמודתorgId
. אני אמור לתת לכלcharge
מספר. המספר הזה הוא לפי ארגון.
היום כאשר אני מכניס שורה חדשה אני עושה ככה:- מושך מה-DB את מספר ה-
charge
הגבוה ביותר של הארגון. - מוסיף עליו 1.
- שומר
charge
חדש עם מספר המעודכן.
עכשיו, ברור לכל שיש פה race condition אם מתווסף
charge
חדש לטבלה בין שלב 1 לשלב 3.יש לי כרגע 5 רעיונות איך לפתור את הבעיה. אשמח לשמוע את דעתכם. יצויין שמדובר ב-postgres, ואני משתמש ב-Sequelize ORM לכן יש עדיפות לפתרון שאפשר לממש ב-Sequelize.
- לעשות את התהליך בתוך טרנזקציה כאשר רמת הבידוד של הטרנזקציה הוא
SERIALIZABLE
. (רמת הבידוד ברירת המחדל לכאורה לא יפתור את הבעיה). - לעשות נעילה על הטבלה לפני ביצוע שלב 1.
- לעשות עבור כל ארגון
SEQUENCE
ולקבל את מספר ה-charge
הנוכחי מה-SEQUENCE
. - לעשות את כל התהליך בפקודת SQL אחד.
- לממש את הנעילה ברמת התוכנה בלי להזדקק לשירותי ה-DB.
אני לא אוהב את פתרון 1 כי זה מסבך את הקוד, כי זה לא מונע בעיית מקביליות אלא מכשיל את הטרנזקציה כאשר יש בעיה ואז אני צריך לטפל בזה בקוד ולנסות שוב.
לגבי פתרון 2, אני לא יודע עדיין על סוגי הנעילה שקיימים אבל ברור שאני רוצה נעילה הכי זולה. האם יש אפשרות לנעול רק הכנסת שורות חדשות עבור ארגון זו?
פתרון 3 דורש עשיית טריגר או הוספת קוד לייצרSEQUENCE
עובר כל ארגון חדש.
פתרון 4, האם זה זהה ל-1 או ל-2? או שיש הבדלים?
פתרון 5: אני לא אוהב את הרעיון של נעילה ברמת התוכנה כאשר אני מקבל את הנעילה בחינם מה-DB (אם רק הייתי יודע איך אני אמור לממש את זה) אבל אולי זה הפתרון הכי פשוט ונקי?אשמח לדעתכם.
@yossiz אמר במניעת race condition ב-DB:
- לעשות את כל התהליך בפקודת SQL אחד.
@dovid אמר במניעת race condition ב-DB:
אני יודע שבSQL SERVER נכון להיום אתה מוגן ב4
אתה בטוח בזה? יש לך מקור?
@clickone אמר במניעת race condition ב-DB:
לכאורה בשיטה הזו הוא ינעל את השינויים כי זה נעשה בפעם אחת
מכיון שהנושא מאוד סקרן אותי והתחלתי לחשוש שזה לא נכון עשיתי נסיון,
- יצרתי טבלה פשוטה:
create table test (id SERIAL, i INTEGER);
ואז הרצתי בלולאה במקביל בשתי סשנים את הפקודה הזאת:
INSERT INTO test (i) VALUES ( (SELECT max FROM (SELECT MAX(i) AS max, pg_sleep(1) FROM test) AS i) + 1 );
(הוספתי את ה-
pg_sleep
כדי למקסם את הסיכויים להתנגשות)התוצאה היתה שהיו מספר שורות עם אותו מספר, בניגוד לצפיה שכל שורה תהיה מספר אחד מעל לשורה הקודמת, כלומר שהיה race condition.
אח"כ שניתי את הפרמטרים של הקליינטים:SET default_transaction_isolation TO serializable;
והרצתי את זה שוב, וחלק מהפעולות נכשלו בגלל התנגשויות
לכאורה זה מלמד שרמת הבידוד בתוך פקודה בודדת שווה לרמת הבידוד של טרנזקציה - לא יותר. ואם כן אני לא מרוויח הרבה מפתרון 4 חוץ מזה שמשך הזמן שחשוף לבעיות התנגשות אולי יהיה קצת יותר קצר, אבל זה לא מגן לגמרי מהתנגשויות.
אני חושש שזה המצב בכל המנועים ואם כן זו נקודה חשובה להיות מודע לזה.
- מושך מה-DB את מספר ה-
-
@yossiz אמר במניעת race condition ב-DB:
- לעשות את כל התהליך בפקודת SQL אחד.
@dovid אמר במניעת race condition ב-DB:
אני יודע שבSQL SERVER נכון להיום אתה מוגן ב4
אתה בטוח בזה? יש לך מקור?
@clickone אמר במניעת race condition ב-DB:
לכאורה בשיטה הזו הוא ינעל את השינויים כי זה נעשה בפעם אחת
מכיון שהנושא מאוד סקרן אותי והתחלתי לחשוש שזה לא נכון עשיתי נסיון,
- יצרתי טבלה פשוטה:
create table test (id SERIAL, i INTEGER);
ואז הרצתי בלולאה במקביל בשתי סשנים את הפקודה הזאת:
INSERT INTO test (i) VALUES ( (SELECT max FROM (SELECT MAX(i) AS max, pg_sleep(1) FROM test) AS i) + 1 );
(הוספתי את ה-
pg_sleep
כדי למקסם את הסיכויים להתנגשות)התוצאה היתה שהיו מספר שורות עם אותו מספר, בניגוד לצפיה שכל שורה תהיה מספר אחד מעל לשורה הקודמת, כלומר שהיה race condition.
אח"כ שניתי את הפרמטרים של הקליינטים:SET default_transaction_isolation TO serializable;
והרצתי את זה שוב, וחלק מהפעולות נכשלו בגלל התנגשויות
לכאורה זה מלמד שרמת הבידוד בתוך פקודה בודדת שווה לרמת הבידוד של טרנזקציה - לא יותר. ואם כן אני לא מרוויח הרבה מפתרון 4 חוץ מזה שמשך הזמן שחשוף לבעיות התנגשות אולי יהיה קצת יותר קצר, אבל זה לא מגן לגמרי מהתנגשויות.
אני חושש שזה המצב בכל המנועים ואם כן זו נקודה חשובה להיות מודע לזה.
@yossiz אמר במניעת race condition ב-DB:
אתה בטוח בזה? יש לך מקור?
כעת בדקתי, זה לא נכון (נוצר לי כפילות..) אז אני לא מחפש מקור
מהלינקים שהבאתי כבר למעלה עולה שחובה לעשות נעילה ברמת SERIALIZABLE בדיוק כפי שכתבת.
יש פתרון פה לא הבנתי אותו, מריצים את זה באותו פקודה של ההכנסה?
סתם ככה אשמח לדעת מה הרקע של הצורך. -
@yossiz אמר במניעת race condition ב-DB:
אתה בטוח בזה? יש לך מקור?
כעת בדקתי, זה לא נכון (נוצר לי כפילות..) אז אני לא מחפש מקור
מהלינקים שהבאתי כבר למעלה עולה שחובה לעשות נעילה ברמת SERIALIZABLE בדיוק כפי שכתבת.
יש פתרון פה לא הבנתי אותו, מריצים את זה באותו פקודה של ההכנסה?
סתם ככה אשמח לדעת מה הרקע של הצורך.@dovid אמר במניעת race condition ב-DB:
סתם ככה אשמח לדעת מה הרקע של הצורך.
אין באמת צורך כל כך חשוב, אבל אני שמח לדעת את הפתרון בלי קשר.
מדובר בטבלה של כסף שנכנס לארגון (תרומות), לצרכי תצוגה אנחנו רוצים לתת לכל charge מספר ידידותי במקום לזהות אותו על ידי ה-PK. לצורך תצוגה אנחנו מעדיפים שהמספרים של ה-charges של כל ארגון יהיו עוקפים. אנחנו רוצים גם שהמספרים יהיו קבועים, כלומר גם אם נמחק תרומה מאיזה סיבה זה לא אמור לשנות את כל המספרים.
האם זה סיבה מספקת להכריח נעילה על ה-DB לכל הכנסת תרומה? אני לא יודע... נחיה ונראה... -
@yossiz אמר במניעת race condition ב-DB:
אתה בטוח בזה? יש לך מקור?
כעת בדקתי, זה לא נכון (נוצר לי כפילות..) אז אני לא מחפש מקור
מהלינקים שהבאתי כבר למעלה עולה שחובה לעשות נעילה ברמת SERIALIZABLE בדיוק כפי שכתבת.
יש פתרון פה לא הבנתי אותו, מריצים את זה באותו פקודה של ההכנסה?
סתם ככה אשמח לדעת מה הרקע של הצורך. -
@dovid אמר במניעת race condition ב-DB:
סתם ככה אשמח לדעת מה הרקע של הצורך.
אין באמת צורך כל כך חשוב, אבל אני שמח לדעת את הפתרון בלי קשר.
מדובר בטבלה של כסף שנכנס לארגון (תרומות), לצרכי תצוגה אנחנו רוצים לתת לכל charge מספר ידידותי במקום לזהות אותו על ידי ה-PK. לצורך תצוגה אנחנו מעדיפים שהמספרים של ה-charges של כל ארגון יהיו עוקפים. אנחנו רוצים גם שהמספרים יהיו קבועים, כלומר גם אם נמחק תרומה מאיזה סיבה זה לא אמור לשנות את כל המספרים.
האם זה סיבה מספקת להכריח נעילה על ה-DB לכל הכנסת תרומה? אני לא יודע... נחיה ונראה... -
פתרון ברמת האפליקציה:
אתה צריך כנראה לעשות שרת בעל מופע אחד שיהיה אחראי להוציא קבלות/מספרי תרומות.
תהיה רשימה שתחזיק את כל הבקשות, ובאמצעות לולאה תיקח כל פעם בקשה אחת ותטפל בה.
כך לא יהיו לעולם התנגשויות. (וכמובן תשתמש בהצעה של תת שאילתה).
אני הייתי מיישם את זה ככה.אפשר להקצות את כל השרת רק לזה, אם אתה רוצה למנוע מצבים של עומס (כי זה תהליך אחד שעושה את כל הקבלות).
שאר הפתרונות שיש כאן הם ברמת הDB, ומשתנים ממנוע אחד למשנהו ואפילו מגירסה לגירסה.
-
פתרון ברמת האפליקציה:
אתה צריך כנראה לעשות שרת בעל מופע אחד שיהיה אחראי להוציא קבלות/מספרי תרומות.
תהיה רשימה שתחזיק את כל הבקשות, ובאמצעות לולאה תיקח כל פעם בקשה אחת ותטפל בה.
כך לא יהיו לעולם התנגשויות. (וכמובן תשתמש בהצעה של תת שאילתה).
אני הייתי מיישם את זה ככה.אפשר להקצות את כל השרת רק לזה, אם אתה רוצה למנוע מצבים של עומס (כי זה תהליך אחד שעושה את כל הקבלות).
שאר הפתרונות שיש כאן הם ברמת הDB, ומשתנים ממנוע אחד למשנהו ואפילו מגירסה לגירסה.
@מנצפך תודה, הפתרון שלך דומה מאוד לפתרון 3 שהצעתי, רק שאני הצעתי להשתמש בשירות מובנה של ה-DB ואתה מציע לממש את זה בעצמי.
אני רואה יתרונות במימוש עצמי של השירות, אבל לעומת זה אני רואה חסרונות גם כן, ובפרט בפרוייקט בגודל זה (כלומר קטן מאוד כרגע) לא נראה לי ששוה לי ההשקעה.
לעצם הפתרון, אחרי מחשבה אני תופס שלכאורה זה הפתרון הכי זול (מבחינת נעילות) שקיים. הנעילה תהיה רק על הפעולה שחייבת נעילה.
-
@מנצפך תודה, הפתרון שלך דומה מאוד לפתרון 3 שהצעתי, רק שאני הצעתי להשתמש בשירות מובנה של ה-DB ואתה מציע לממש את זה בעצמי.
אני רואה יתרונות במימוש עצמי של השירות, אבל לעומת זה אני רואה חסרונות גם כן, ובפרט בפרוייקט בגודל זה (כלומר קטן מאוד כרגע) לא נראה לי ששוה לי ההשקעה.
לעצם הפתרון, אחרי מחשבה אני תופס שלכאורה זה הפתרון הכי זול (מבחינת נעילות) שקיים. הנעילה תהיה רק על הפעולה שחייבת נעילה.
-
@yossiz אמר במניעת race condition ב-DB:
בל לעומת זה אני רואה חסרונות גם כן, ובפרט בפרוייקט בגודל זה (כלומר קטן מאוד כרגע) לא נראה לי ששוה לי ההשקעה.
איזה חסרונות קיימים בנעילה בקוד?
(באיזה שפה הקוד כתוב?) -
@yossiz אמר במניעת race condition ב-DB:
בל לעומת זה אני רואה חסרונות גם כן, ובפרט בפרוייקט בגודל זה (כלומר קטן מאוד כרגע) לא נראה לי ששוה לי ההשקעה.
איזה חסרונות קיימים בנעילה בקוד?
(באיזה שפה הקוד כתוב?)@nigun אני לא בטוח שיש שום חסרונות בנעילה בקוד. (עריכה: עיין מה שכתב דוד למעלה)
זה nodejs.מה ש@מנצפך מציע הוא (אולי הרבה) יותר זול מנעילה בקוד.
אבל מצד שני יש חסרון שכאשר מדובר בעשרות אלפי ארגונים וצריך לשמור מספר לכל אחד זה יתפוס משאבים. וגם, צריך לכתוב את זה בצורה נכונה שהמספרים תמיד יהיו מסונכרנים עם מה שיש ב-DB. (אני לא אומר שזה קשה, רק זה עוד חחתיכת קוד שצריך לוודא שהוא פועל נכון).
אגב, @מנצפך חושב מאוד גדול... מכיון שכרגע הגישה ל-DB אצלי הוא רק על ידי תהליך אחד אין צורך בשרת נפרד שיעשה את זה, אלא אפשר לעשות את זה בתוך התהליך. -
@nigun בנעילה בקוד יש שתי בעיות:
א. בתחזוקת הקוד לעולם צריך לזכור את האחריות הזאת
ב. צריך לוודא שאין מצב של ריצה מקבילה של מופעים, אסור שהתוכנה תרוץ פעמיים. -
@dovid אמר במניעת race condition ב-DB:
ב. צריך לוודא שאין מצב של ריצה מקבילה של מופעים, אסור שהתוכנה תרוץ פעמיים.
מה הבעיה? ריצה מקבילה של מספר מופעים באותו אפליקציה או ריצה של מספר אפליקציות?