שיטות מומלצות לשיפור הביצועים של שאילתות Spanner Graph

במאמר הזה מתוארות שיטות מומלצות לשיפור הביצועים של שאילתות 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 מסיק אילו צמתים ותוויות קצה מתאימים. מומלץ לציין תוויות לכל הצמתים והקצוות, כי יכול להיות שלא תמיד אפשר יהיה להסיק את התוויות האלה, וסריקה של יותר תוויות מהנדרש עלולה להתבצע.

הצהרת התאמה יחידה

בדוגמה הבאה מוצגים חשבונות שמקושרים באמצעות 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 הרבה העברות נכנסות.

המאמרים הבאים