במאמר הזה מתוארות שיטות מומלצות לשיפור הביצועים של שאילתות Spanner Graph, כולל האופטימיזציות הבאות:
- אל תבצעו סריקה מלאה של טבלת הקלט עבור צמתים וקצוות.
- להקטין את כמות הנתונים שהשאילתה צריכה לקרוא מהאחסון.
- הקטינו את נפח הנתונים הזמניים.
התחלה מצמתים עם קרדינליות נמוכה
כדאי לכתוב את ה-Path traversal כך שהוא יתחיל בצמתים עם קרדינליות נמוכה יותר. הגישה הזו שומרת על קבוצת התוצאות הביניים קטנה ומאיצה את הביצוע של השאילתה.
לדוגמה, לשאילתות הבאות יש את אותה סמנטיקה:
מעבר קדימה בין קצוות:
GRAPH FinGraph MATCH (p:Person {name:"Alex"})-[:Owns]->(a:Account {is_blocked: true}) RETURN p.id AS person_id, a.id AS account_id;מעבר הפוך בין קצוות:
GRAPH FinGraph MATCH (a:Account {is_blocked:true})<-[:Owns]-(p:Person {name: "Alex"}) RETURN p.id AS person_id, a.id AS account_id;
בהנחה שיש פחות אנשים בשם Alex מאשר מספר החשבונות החסומים, מומלץ לכתוב את השאילתה הזו בחיפוש קדימה.
התחלה מצמתים עם קרדינליות נמוכה חשובה במיוחד למעבר בנתיב באורך משתנה. בדוגמה הבאה מוצגת הדרך המומלצת למצוא חשבונות שנמצאים במרחק של עד שלוש העברות מחשבון נתון.
GRAPH FinGraph
MATCH (:Account {id: 7})-[:Transfers]->{1,3}(a:Account)
RETURN a.id;
הגדרת כל התוויות כברירת מחדל
אם לא מציינים תוויות, Spanner Graph מסיק את הצמתים והתוויות של הקצוות שעומדים בדרישות. מומלץ לציין תוויות לכל הצמתים והקצוות, כי יכול להיות שלא תמיד אפשר יהיה להסיק את המסקנה הזו, וסריקה של יותר תוויות מהנדרש עלולה להתבצע.
הצהרת MATCH יחידה
בדוגמה הבאה מוצגים חשבונות שמקושרים באמצעות עד 3 העברות מהחשבון הנתון:
GRAPH FinGraph
MATCH (src:Account {id: 7})-[:Transfers]->{1,3}(dst:Account)
RETURN dst.id;
בכל הצהרות MATCH
מציינים תוויות בצמתים ובקשתות כשהן מתייחסות לאותו רכיב אבל מופיעות ב-MATCH הצהרות.
בדוגמה הבאה אפשר לראות את הגישה המומלצת:
GRAPH FinGraph
MATCH (acct:Account {id: 7})-[:Transfers]->{1,3}(other_acct:Account)
RETURN acct, COUNT(DISTINCT other_acct) AS related_accts
GROUP BY acct
NEXT
MATCH (acct:Account)<-[:Owns]-(p:Person)
RETURN p.id AS person, acct.id AS acct, related_accts;
שימוש ב-IS_FIRST כדי לבצע אופטימיזציה של שאילתות
אפשר להשתמש בפונקציה
IS_FIRST
כדי לשפר את ביצועי השאילתות על ידי דגימת קצוות והגבלת המעברים בגרפים. הפונקציה הזו עוזרת לטפל בצמתים עם קרדינליות גבוהה ולבצע אופטימיזציה של שאילתות מרובות קפיצות.
אם גודל המדגם שציינתם קטן מדי, יכול להיות שהשאילתה לא תחזיר נתונים. לכן, יכול להיות שתצטרכו לנסות גדלים שונים של דגימות כדי למצוא את האיזון האופטימלי בין הנתונים שמוחזרים לבין שיפור הביצועים של השאילתות.
בדוגמאות האלה של IS_FIRST נעשה שימוש ב-FinGraph, תרשים פיננסי עם Account צמתים ו-Transfers קצוות להעברות כספים. כדי ליצור את FinGraph ולהשתמש בו להרצת השאילתות לדוגמה, אפשר לעיין במאמר הגדרה ושליחת שאילתות ב-Spanner Graph.
הגבלת הקצוות שמועברים כדי לשפר את ביצועי השאילתות
כששולחים שאילתות לגרפים, יכול להיות שלחלק מהצמתים יהיה מספר גדול משמעותית של קשתות נכנסות או יוצאות בהשוואה לצמתים אחרים. הצמתים האלה עם הקרדינליות הגבוהה נקראים לפעמים צמתים ראשיים או צמתים מרכזיים. צמתים ראשיים עלולים לגרום לבעיות בביצועים כי מעבר דרכם עשוי לכלול עיבוד של כמויות עצומות של נתונים, מה שמוביל לחלוקת נתונים לא מאוזנת (partition skew) ולזמני ביצוע ארוכים.
כדי לבצע אופטימיזציה של שאילתה של גרף עם צמתים ראשיים, משתמשים בפונקציה IS_FIRST בתוך סעיף FILTER כדי להגביל את מספר הקשתות שהשאילתה עוברת מצומת. יכול להיות שבחשבונות ב-FinGraph יש מספרים גבוהים משמעותית של טרנזקציות בהשוואה לחשבונות אחרים, ולכן כדאי להשתמש ב-IS_FIRST כדי למנוע שאילתה לא יעילה. הטכניקה הזו שימושית במיוחד כשלא צריך ספירה מלאה של כל החיבורים מצומת על.
השאילתה הבאה מוצאת חשבונות (a2) שמקבלים העברות ישירות או עקיפות מחשבונות חסומים (a1). השאילתה משתמשת ב-IS_FIRST כדי למנוע ביצועים איטיים כשיש בחשבון הרבה העברות, על ידי הגבלת מספר הקצוות Transfers שצריך לקחת בחשבון לכל Account.
GRAPH FinGraph
MATCH
(a1:Account {is_blocked: true})
-[e:Transfers WHERE e IN
{
MATCH -[selected_e:Transfers]->
FILTER IS_FIRST(@max_transfers_per_account) OVER (
PARTITION BY SOURCE_NODE_ID(selected_e)
ORDER BY selected_e.create_time DESC)
RETURN selected_e
}
]->{1,5}
(a2:Account)
RETURN a1.id AS src_id, a2.id AS dst_id;
בדוגמה הזו נעשה שימוש ב:
@max_transfers_per_account: פרמטר של שאילתה שמציין את המספר המקסימלי של קצוותTransfersשיש לקחת בחשבון לכל חשבון (a1).PARTITION BY SOURCE_NODE_ID(selected_e): המגבלה שלIS_FIRSTחלה בנפרד על כל חשבון (a1).
ORDER BY selected_e.create_time DESC: מציין שההעברות האחרונות יוחזרו.
דוגמה לצמתים ביניים לאופטימיזציה של שאילתות מרובות קפיצות
אפשר גם לשפר את יעילות השאילתות באמצעות IS_FIRST כדי לדגום צמתים ביניים בשאילתות מרובות קפיצות. הטכניקה הזו משפרת את היעילות על ידי הגבלת מספר הנתיבים שהשאילתה בודקת עבור כל צומת ביניים. כדי לעשות את זה, צריך לפצל שאילתה מרובת קפיצות לכמה משפטי MATCH שמופרדים באמצעות NEXT, ולהחיל IS_FIRST באמצע הדרך, במקום שבו רוצים לדגום:
GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->(a2:Account)
FILTER IS_FIRST(1) OVER (PARTITION BY a2)
RETURN a1, a2
NEXT
MATCH (a2)-[e2:Transfers]->(a3:Account)
RETURN a1.id AS src_id, a2.id AS mid_id, a3.id AS dst_id;
כדי להבין איך IS_FIRST מבצע אופטימיזציה של השאילתה הזו:
הסעיף
FILTER IS_FIRST(1) OVER (PARTITION BY a2)חל על המשפט הראשוןMATCH.לכל צומת של חשבון ביניים (
a2),IS_FIRSTמתייחס רק לTransfersקצה הראשון (e1) שמגיע, וכך מצמצם את מספר הנתיבים שצריך לבדוק בהצהרה השנייהMATCH.היעילות הכוללת של שאילתת שני השלבים משתפרת כי
MATCHהשני לא מעבד נתונים מיותרים, במיוחד אם יש ל-a2הרבה העברות נכנסות.
שימוש בהרצה עם פירוק לגורמים כדי לבצע אופטימיזציה של שאילתות
כדי לשפר את ביצועי השאילתות ב-Spanner Graph, אפשר לבצע פקטוריזציה של שאילתה שעוברת על דפוס גרף ויוצרת תוצאות ביניים כפולות.
שאילתת גרף יכולה לפעול במצב פירוק לגורמים, שמבטל כפילויות של קידומות נתיב עם מספר גבוה של ערכים ייחודיים לפני מעבר על שאר הנתיב. האופטימיזציה הזו משפרת את הביצועים של השאילתות על ידי צמצום ההשוואות החוזרות של מפתחות או הבדיקות של טבלאות הגיבוב במהלך הביצוע של שאילתת הגרף. כדי לבצע פירוק לגורמים של שאילתה, מוסיפים את הרמז @{factorized_mode} למעבר על התבנית הספציפית או ברמת השאילתה.
מגדירים את factorized_mode לאחת מהאפשרויות הבאות:
factorize_left: אופטימיזציה של מעברים בגרף שבהם נתיבים רבים מתכנסים לכמה צמתים. ב-Spanner Graph, המערכת מסירה כפילויות מנתיבים לפי מזהה צומת היעד, ואז מאחזרת את מאפייני הצומת כדי לצמצם עבודה מיותרת.
factorize_both: אופטימיזציה של שאילתות לא לינאריות עם כמה נתיבי משנה שחולקים צמתים. ב-Spanner Graph לא נוצרות תוצאות ביניים לכל נתיב בנפרד. במקום זאת, המערכת מחשבת כל נתיב משנה פעם אחת ואז משלבת אותם, כך שהמכפלה הקרטזית של התוצאות מתבצעת רק בסוף. אפשר להשתמש בזה לדוגמאות כמו משולשים או ענפים.
מידע נוסף על רמזים למעבר זמין במאמר בנושא רמזים לגרף.
בדוגמאות הבאות אפשר לראות איך משתמשים ברמזים של מצב פירוק לגורמים כדי לשפר את הביצועים של שאילתות גרף. כדי להריץ את קוד הדוגמה, צריך ליצור את סכימת FinGraph במאמר הגדרה של Spanner Graph וביצוע שאילתות.
הפחתת תוצאות ביניים בשאילתה לינארית
בדרך כלל מתבצעות בחשבונות מזויפים עסקאות בהיקף גבוה. שאילתות שמיועדות לזיהוי החשבונות האלה יכולות ליצור תוצאות ביניים גדולות, שרבות מהן כפולות כי הן מתכנסות לקבוצה קטנה של חשבונות יעד. שליפת מאפייני צמתים שמקורם בתרמית מתוצאות הביניים המיותרות האלה דורשת חישובים מיותרים, שעלולים להאט משמעותית את ביצועי השאילתות.
הרמז @{factorized_mode} פותר את הבעיה הזו על ידי אופטימיזציה של ביצוע השאילתה.
בדוגמה הבאה אפשר לראות איך משתמשים ברמז הזה כדי לאחזר פרטים על החשבון ופרטי עסקה. הכלי עוקב אחרי העברות שמקורן בחשבון חסום ידוע, אל חשבונות שעלולים להיות חשבונות שמקורם בתרמית, שנמצאים במרחק של צעד אחד עד שלושה צעדים. הגרסה הלא מותאמת של השאילתה הזו זמינה בכרטיסייה השנייה.
שאילתה שעברה פירוק לגורמים
GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->{1,3}@{factorized_mode=factorize_left}(a2:Account)
LET total_amount = SUM(e1.amount)
RETURN a2.id, a2.create_time, total_amount;
שאילתה לא אופטימלית
GRAPH FinGraph
MATCH (a1:Account {is_blocked: true})-[e1:Transfers]->{1,3}(a2:Account)
LET total_amount = SUM(e1.amount)
RETURN a2.id, a2.create_time, total_amount;
אם מספר צמתי היעד השונים a2 קטן בהרבה ממספר הנתיבים שמובילים אל a2, הטכניקה הזו מונעת עבודה מיותרת. זה נפוץ ברשתות הונאה, שבהן מתווכים מעבירים כסף לכמה נמענים. הרמז לפירוק לגורמים מבצע את הפעולות הבאות:
הפונקציה מוצאת את כל הנתיבים שכוללים קשת אחת עד שלוש קשתות שמתחילות בצומת חסום, ואת סכום ההעברות לאורך הנתיבים האלה.
קובעת את מספר צמתי היעד השונים
a2.הפונקציה מאחזרת את
create_timeלכל צומת יעד נפרדa2רק פעם אחת.משלבת את
create_timeשל כל צומתa2עם רשימת הנתיבים שמובילים אלa2כדי ליצור את תוצאת השאילתה.
דחיית יצירת תוצאות ביניים בשאילתות מורכבות לא ליניאריות
לשאילתות גרף יש לרוב מבנה מורכב, שכולל כמה דפוסי נתיבי משנה לינאריים. בדוגמה הבאה מוצגת שאילתה שמנתחת טרנזקציות שמקורן בחשבון עם id ששווה ל-1. השאילתה הזו מסווגת את כל חשבונות היעד לפי create_time שלהם לשלוש קטגוריות נפרדות:
- במהלך 30 הימים האחרונים
- לפני 30 עד 90 ימים
- לפני יותר מ-90 יום
השאילתה מחזירה את כל השלשות ואת סכום העסקה הכולל של העסקאות האלה.
הרמז @{factorized_mode=factorize_both} מבצע אופטימיזציה של ביצוע השאילתה על ידי מניעת יצירה מוקדמת של תוצאות ביניים. היא מתבססת על העובדה שכל נתיבי המשנה בסעיף MATCH הם בלתי תלויים באופן מותנה, בהינתן הערך של צומת ההתחלה a. factorize_both בודקת את שני נתיבי המשנה הראשונים בנפרד ולא חוזרת על הבדיקה של נתיב המשנה האחרון.
שאילתה שעברה פירוק לגורמים
GRAPH FinGraph
MATCH (a:Account {id: 1})-[e1:Transfers]->(a1:Account),
@{factorized_mode=factorize_both}(a:Account)-[e2:Transfers]->(a2:Account),
(a:Account)-[e3:Transfers]->(a3:Account)
WHERE e1.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
AND e2.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
AND e2.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND e3.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
LET total_amount = e1.amount + e2.amount + e3.amount
RETURN a1.id as a1_id, a2.id as a2_id, a3.id as a3_id, total_amount;
שאילתה לא אופטימלית
GRAPH FinGraph
MATCH (a:Account {id: 1})-[e1:Transfers]->(a1:Account),
(a:Account)-[e2:Transfers]->(a2:Account),
(a:Account)-[e3:Transfers]->(a3:Account)
WHERE e1.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
AND e2.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 90 DAY)
AND e2.create_time < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
AND e3.create_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
LET total_amount = e1.amount + e2.amount + e3.amount
RETURN a1.id as a1_id, a2.id as a2_id, a3.id as a3_id, total_amount;
עבור חשבון התחלתי נתון עם id ששווה ל-1, כל קצה יוצא Transfers
מוביל לכמה צמתי יעד עצמאיים. דוגמה:
Mהוא מספר החשבונות (a1) שכוללים העברות בטווח(-inf, 90 DAYS)
Nהוא מספר החשבונות (a2) שכוללים העברות בטווח[90 DAYS, 30 DAYS)
Kהוא מספר החשבונות (a3) שכוללים העברות בטווח[30 DAYS, +inf)
השאילתה הלא ממוטבת יוצרת תוצאות ביניים מוקדם, ומחשבת את נתיב המשנה השני M פעמים ואת נתיב המשנה השלישי M * N פעמים. לעומת זאת, גרסה עם פירוק לגורמים מבצעת אופטימיזציה של ביצוע השאילתה על ידי הפעולות הבאות:
קבלת צמתי יעד
M(a1) עבור נתיב המשנה הראשון, ואחסון זמני של קבוצת צמתיa1.קבלת צמתי יעד
N(a2) עבור נתיב המשנה השני רק פעם אחת ואחסון זמני של קבוצת צמתיםa2.קבלת
Kצמתי יעד (a3) עבור נתיב המשנה האחרון רק פעם אחת.ביצוע מכפלה וקטורית בין תוצאות ביניים זמניות כדי ליצור את המכפלה של
MxNxKרשומות.