אינדקס ScaNN הוא אינדקס קוונטיזציה מבוסס-עץ שנוצר על ידי Google לחיפוש של השכן הקרוב המשוער. הוא מספק זמן בניית אינדקס נמוך יותר והזיכרון שבשימוש קטן יותר בהשוואה ל-HNSW. בנוסף, הוא מספק QPS מהיר יותר בהשוואה ל-HNSW על סמך עומס העבודה.
לפני שמתחילים
לפני שמתחילים ליצור אינדקסים, צריך לוודא שמתקיימים התנאים המוקדמים הבאים.
וקטורים של הטמעה מתווספים לטבלה במסד הנתונים של AlloyDB Omni.
אם מנסים ליצור אינדקס ScaNN בטבלה ריקה או בטבלה עם מחיצות, יכול להיות שתיתקלו בבעיות. מידע נוסף על השגיאות שנוצרות זמין במאמר בנושא פתרון בעיות באינדקס ScaNN. כדי ליצור אינדקס בטבלה ריקה או קטנה, אפשר לעיין במאמר בנושא יצירת אינדקס מושהית לטבלאות ריקות או כמעט ריקות.
התוספים
vectorו-alloydb_scannמותקנים:CREATE EXTENSION IF NOT EXISTS alloydb_scann CASCADE;התקנת התוסף
alloydb_scannבודקת באופן אוטומטי אם התוסףvectorמותקן, ואם לא, היא מתקינה אותו. לא צריך להתקין אתvectorבנפרד באופן ידני.אם רוצים ליצור אינדקס ScaNN ברמה 4, צריך קודם להפעיל את התכונה תצוגה מקדימה במופע AlloyDB Omni. כדי להפעיל את תכונת התצוגה המקדימה, בוחרים באחת משתי השיטות הבאות:
מפעילים את דגל מסד הנתונים
scann.enable_preview_features.מידע נוסף על הגדרת דגלים של מסד נתונים זמין במאמר הגדרת דגלים של מסד נתונים.
מגדירים את הדגל של מסד הנתונים
scann.max_allowed_num_levelsלערך3ברמת הסשן או המופע. כדי להגדיר את הדגל ברמת הסשן, מריצים את הפקודה הבאה:SET scann.max_allowed_num_levels = 3;כדי להגדיר את הדגל ברמת המופע, מריצים את הפקודה
gcloud alloydb alloydb instances updateבאמצעות השדה--database-flags.
יצירת אינדקס עם כוונון אוטומטי
אינדקסים של ScaNN שעברו כוונון אוטומטי מפשטים את יצירת האינדקסים, כי מערכת AlloyDB Omni מנהלת ומכוונת את מבנה האינדקס. אם אתם צריכים שליטה פרטנית בכוונון האינדקס, אתם יכולים ליצור אינדקס של ScaNN שעבר כוונון ידני.
אפשר לבצע אופטימיזציה של אינדקסים שעברו כוונון אוטומטי בשתי דרכים:
- (ברירת מחדל) recall וזמן אחזור של חיפוש וקטורי במחיר של משך זמן של תהליך build של האינדקס
- איזון בין משך זמן של תהליך build של האינדקס לבין ביצועי החיפוש
כדי ליצור אינדקס ScaNN עם כוונון אוטומטי, מריצים את הפקודה הבאה.
CREATE INDEX INDEX_NAME ON TABLE
USING scann (EMBEDDING_COLUMN DISTANCE_FUNCTION)
מחליפים את מה שכתוב בשדות הבאים:
INDEX_NAME: השם של האינדקס שרוצים ליצור. לדוגמה,my_scann_index. שמות האינדקסים משותפים לכל מסד הנתונים. מוודאים שכל שם אינדקס ייחודי לכל טבלה במסד הנתונים.
TABLE: הטבלה שאליה רוצים להוסיף את האינדקס.EMBEDDING_COLUMN: העמודה שבה מאוחסנים נתוניvector.
DISTANCE_FUNCTION: פונקציית המרחק לשימוש עם האינדקס הזה. צריך לבחור אחת מהאפשרויות:מרחק L2:
l2מכפלה סקלרית:
dot_productמרחק קוסינוס:
cosine
הפקודה הזו יוצרת אינדקס ScaNN שעבר אופטימיזציה לביצועי חיפוש. כדי לשנות את ההגדרה הזו, מריצים את הפקודה הבאה:
CREATE INDEX INDEX_NAME ON TABLE
USING scann (EMBEDDING_COLUMN DISTANCE_FUNCTION)
WITH (MODE='AUTO',
OPTIMIZATION='OPTIMIZATION')
מחליפים את מה שכתוב בשדות הבאים:
INDEX_NAME: השם של האינדקס שרוצים ליצור. לדוגמה,my_scann_index. שמות האינדקסים משותפים לכל מסד הנתונים. מוודאים שכל שם אינדקס ייחודי לכל טבלה במסד הנתונים.
TABLE: הטבלה שאליה רוצים להוסיף את האינדקס.EMBEDDING_COLUMN: העמודה שבה מאוחסנים נתוניvector.
DISTANCE_FUNCTION: פונקציית המרחק לשימוש עם האינדקס הזה. צריך לבחור אחת מהאפשרויות:מרחק L2:
l2מכפלה סקלרית:
dot_productמרחק קוסינוס:
cosine
(אופציונלי)
OPTIMIZATION: מוגדר לאחת מהאפשרויות הבאות:(ברירת מחדל)
SEARCH_OPTIMIZED: אופטימיזציה של ההחזרה של חיפוש וקטורי ושל זמן האחזור של חיפוש וקטורי, על חשבון זמני בניית אינדקס ארוכים יותר.
BALANCED: איזון משך זמן של תהליך build של אינדקס וביצועי החיפוש.
אם המדיניות
OPTIMIZATIONמוגדרת, חובה לכלול גם אתMODE='AUTO'.
יצירת אינדקס שכוונן באופן ידני
אם לאפליקציה שלכם יש דרישות ספציפיות לגבי זמני אחזור ובניית אינדקס, אתם יכולים ליצור ולכוון את אינדקס ScaNN באופן ידני.
כדי ליצור באופן ידני אינדקס ScaNN לעמוד שמכיל וקטורים מוטמעים מאוחסנים, אפשר להשתמש בפקודות הבאות.
אינדקס של עץ עם שתי רמות
CREATE INDEX INDEX_NAME ON TABLE USING scann (EMBEDDING_COLUMN DISTANCE_FUNCTION) WITH (mode='MANUAL', num_leaves=NUM_LEAVES_VALUE, quantizer=QUANTIZER);
-
INDEX_NAME: השם של האינדקס שרוצים ליצור. לדוגמה,my_scann_index. שמות האינדקסים משותפים בכל מסד הנתונים. חשוב לוודא שכל שם אינדקס ייחודי לכל טבלה במסד הנתונים. -
TABLE: הטבלה שאליה רוצים להוסיף את האינדקס. -
EMBEDDING_COLUMN: העמודה שבה מאוחסנים נתוני `vector`. -
DISTANCE_FUNCTION: פונקציית המרחק לשימוש עם האינדקס הזה. בוחרים באחת מהאפשרויות הבאות:- מרחק L2:
l2 - מכפלה סקלרית:
dot_product - מרחק קוסינוס:
cosine
- מרחק L2:
-
NUM_LEAVES_VALUE: מספר המחיצות שיחולו על האינדקס הזה. הערך יכול להיות בין 1 ל-30 מיליון. מידע נוסף על בחירת הערך הזה זמין במאמר שיפור מדדScaNN. -
QUANTIZER: סוג הכמת שרוצים להשתמש בו. שימו לב שאפשר לטעון את אינדקס ScaNN לתוך מנוע מבוסס-עמודות כדי להאיץ עוד יותר את חיפוש הווקטורים. בוחרים באחת מהאפשרויות הבאות:-
(ברירת מחדל)
SQ8: מספק איזון בין ביצועי השאילתה לבין אובדן מינימלי של נתונים. השיעור הזה בדרך כלל נמוך מ-1-2%. -
(תצוגה מקדימה)
AH: גיבוב אסימטרי (AH) דחוס עד פי 4 בהשוואה ל-SQ8. כדי לשפר את הביצועים של השאילתות כשמנוע העמודות מופעל והנתונים של האינדקס והטבלה מאוכלסים במנוע העמודות, כדאי לשקול את האפשרות הזו. מידע נוסף זמין במאמר בנושא שיטות מומלצות להתאמת ScaNN. -
FLAT: מספק את הזיכרון הכי טוב, 99% ומעלה, על חשבון ביצועי החיפוש.
-
(ברירת מחדל)
אינדקס עץ ברמה שלישית
CREATE INDEX INDEX_NAME ON TABLE USING scann (EMBEDDING_COLUMN DISTANCE_FUNCTION) WITH (mode='MANUAL', num_leaves=NUM_LEAVES_VALUE, quantizer=QUANTIZER, auto_maintenance=AUTO_MAINTENANCE, max_num_levels = 2);
-
INDEX_NAME: השם של האינדקס שרוצים ליצור. לדוגמה,my_scann_index. שמות האינדקסים משותפים בכל מסד הנתונים. חשוב לוודא שכל שם אינדקס ייחודי לכל טבלה במסד הנתונים. -
TABLE: הטבלה שאליה רוצים להוסיף את האינדקס. -
EMBEDDING_COLUMN: העמודה שבה מאוחסנים נתוני `vector`. -
DISTANCE_FUNCTION: פונקציית המרחק לשימוש עם האינדקס הזה. בוחרים באחת מהאפשרויות הבאות:- מרחק L2:
l2 - מכפלה סקלרית:
dot_product - מרחק קוסינוס:
cosine
- מרחק L2:
-
NUM_LEAVES_VALUE: מספר המחיצות שיחולו על האינדקס הזה. הערך יכול להיות בין 1 ל-30 מיליון. מידע נוסף על בחירת הערך הזה זמין במאמר שיפור מדדScaNN. -
QUANTIZER: סוג הכמת שרוצים להשתמש בו. שימו לב שאפשר לטעון את אינדקס ScaNN למנוע מבוסס-עמודות כדי להאיץ עוד יותר את חיפוש הווקטורים. בוחרים באחת מהאפשרויות הבאות:-
(ברירת מחדל)
SQ8: מספק איזון בין ביצועי השאילתה לבין אובדן מינימלי של נתונים. השיעור הזה בדרך כלל נמוך מ-1-2%. -
(תצוגה מקדימה)
AH: גיבוב אסימטרי (AH) דחוס עד פי 4 בהשוואה ל-SQ8. כדי לשפר את הביצועים של השאילתות כשמנוע העמודות מופעל והנתונים של האינדקס והטבלה מאוכלסים במנוע העמודות, כדאי לשקול את האפשרות הזו. מידע נוסף זמין במאמר בנושא שיטות מומלצות להתאמת ScaNN. -
FLAT: מספק את הזיכרון הכי טוב, 99% ומעלה, על חשבון ביצועי החיפוש.
-
(ברירת מחדל)
-
(אופציונלי)
AUTO_MAINTENANCE: ההגדרה הזו קובעת אם תחזוקה אוטומטית של האינדקס מופעלת או מושבתת. מידע נוסף על תחזוקה אוטומטית זמין במאמר בנושא תחזוקה של אינדקסים של וקטורים.-
(ברירת מחדל)
ON: מערכת AlloyDB Omni מבצעת תחזוקה אוטומטית של האינדקס. -
OFF: ב-AlloyDB Omni לא מתבצעת תחזוקה אוטומטית של האינדקס.
-
(ברירת מחדל)
מדד עץ עם ארבע רמות
אינדקסים של עץ עם ארבע רמות נמצאים בגרסת טרום-השקה.
CREATE INDEX INDEX_NAME ON TABLE USING scann (EMBEDDING_COLUMN DISTANCE_FUNCTION) WITH (mode='MANUAL', num_leaves=NUM_LEAVES_VALUE, quantizer=QUANTIZER, max_num_levels = 3);
-
INDEX_NAME: השם של האינדקס שרוצים ליצור. לדוגמה,my_scann_index. שמות האינדקסים משותפים בכל מסד הנתונים. חשוב לוודא שכל שם אינדקס ייחודי לכל טבלה במסד הנתונים. -
TABLE: הטבלה שאליה רוצים להוסיף את האינדקס. -
EMBEDDING_COLUMN: העמודה שבה מאוחסנים נתוני `vector`. -
DISTANCE_FUNCTION: פונקציית המרחק לשימוש עם האינדקס הזה. בוחרים באחת מהאפשרויות הבאות:- מרחק L2:
l2 - מכפלה סקלרית:
dot_product - מרחק קוסינוס:
cosine
- מרחק L2:
-
NUM_LEAVES_VALUE: מספר המחיצות שיחולו על האינדקס הזה. הערך יכול להיות בין 1 ל-30 מיליון. מידע נוסף על בחירת הערך הזה זמין במאמר שיפור מדדScaNN. -
QUANTIZER: סוג הכמת שרוצים להשתמש בו. שימו לב שאפשר לטעון את אינדקס ScaNN למנוע מבוסס-עמודות כדי להאיץ עוד יותר את חיפוש הווקטורים. בוחרים באחת מהאפשרויות הבאות:-
(ברירת מחדל)
SQ8: מספק איזון בין ביצועי השאילתה לבין אובדן מינימלי של נתונים. השיעור הזה בדרך כלל נמוך מ-1-2%. -
תצוגה מקדימה
AH: גיבוב אסימטרי (AH) דחוס עד פי 4 בהשוואה ל-SQ8. כדי לשפר את הביצועים של השאילתות כשמנוע העמודות מופעל והנתונים של האינדקס והטבלה מאוכלסים במנוע העמודות, כדאי לשקול את האפשרות הזו. מידע נוסף זמין במאמר בנושא שיטות מומלצות להתאמת ScaNN. -
FLAT: מספק את הזיכרון הכי טוב, 99% ומעלה, על חשבון ביצועי החיפוש.
-
(ברירת מחדל)
המרת אינדקס שכוונן באופן ידני לאינדקס שכוונן באופן אוטומטי
כדי להמיר אינדקס עם כוונון ידני לאינדקס עם כוונון אוטומטי, מבצעים את השלבים הבאים:
איפוס של כל הפרמטרים של השאילתה שהוגדרו לאינדקס שעבר כוונון ידני.
ALTER INDEX INDEX_NAME RESET (PARAMETER_NAME);מחליפים את המשתנים הבאים:
INDEX_NAME: השם של האינדקס שרוצים להמיר. לדוגמה,my_scann_index. השמות של האינדקסים משותפים במסד הנתונים. מוודאים שכל שם אינדקס ייחודי לכל טבלה במסד הנתונים.
PARAMETER_NAME: רשימה מופרדת בפסיקים שמכילה את השמות של פרמטרים של שאילתה שרוצים לאפס. לדוגמה,num_leaves, quantization.חשוב לזכור שצריך לאפס את כל פרמטרים של שאילתה האחרים לפני איפוס של
num_leaves.
צריך ליצור מחדש את האינדקס שהותאם ידנית כדי להמיר אותו לאינדקס שהותאם אוטומטית.
REINDEX INDEX CONCURRENTLY INDEX_NAME;
יצירת אינדקס ScaNN ל-real[] סוגי נתונים
כדי ליצור אינדקס לעמודת הטמעה שמשתמשת בסוג הנתונים real[] במקום vector, צריך להמיר את העמודה לסוג הנתונים vector:
CREATE INDEX INDEX_NAME ON TABLE
USING scann (CAST(EMBEDDING_COLUMN AS vector(DIMENSIONS)) DISTANCE_FUNCTION)
מחליפים את מה שכתוב בשדות הבאים:
INDEX_NAME: השם של האינדקס שרוצים ליצור. לדוגמה,my_scann_index. שמות האינדקסים משותפים לכל מסד הנתונים. מוודאים שכל שם אינדקס ייחודי לכל טבלה במסד הנתונים.
TABLE: הטבלה שאליה רוצים להוסיף את האינדקס.
DIMENSIONS: מספר המאפיינים שהמודל תומך בהם.EMBEDDING_COLUMN: העמודה שבה מאוחסנים נתוניvector.
DISTANCE_FUNCTION: פונקציית המרחק לשימוש עם האינדקס הזה. צריך לבחור אחת מהאפשרויות:מרחק L2:
l2מכפלה סקלרית:
dot_productמרחק קוסינוס:
cosine
הצגת התקדמות ההוספה לאינדקס
כדי לראות את התקדמות ההוספה לאינדקס, משתמשים בתצוגה pg_stat_progress_create_index:
SELECT * FROM pg_stat_progress_create_index;
בעמודה phase מוצג המצב הנוכחי של יצירת האינדקס. אחרי ששלב בניית האינדקס מסתיים, השורה של האינדקס לא מוצגת.
יצירת אינדקס מושהה לטבלאות ריקות או לטבלאות עם מספר לא מספיק של שורות
כברירת מחדל, אי אפשר ליצור אינדקס ScaNN בטבלה ריקה או בטבלה עם פחות שורות מהערך של אפשרות האינדקס num_leaves.
כדי לעקוף את המגבלה הזו, מפעילים יצירה של אינדקסים מושהים כדי לאפשר ל-AlloyDB Omni להשהות את יצירת האינדקסים עד שמספר השורות בטבלה יגיע לסף שמוגדר על ידי num_leaves. אחרי שהסף מושג, AlloyDB Omni מתחיל ליצור את האינדקס ברקע.
הפעולה הזו היא תהליך לא חוסם, שמאפשר לפעולות אחרות במסד הנתונים, כמו קריאה וכתיבה, להמשיך ללא הפרעה. מכיוון שהבנייה מחדש של האינדקס מתבצעת ברקע, יצירת אינדקס מושהית מתאימה כשמתבצעת קליטה של שורות נתונים בטבלאות בקבוצות קטנות. הבנייה מחדש של האינדקס מופעלת באופן אוטומטי אחרי שמספר השורות מגיע לסף.
עם זאת, אם אתם מתכננים להוסיף לטבלה מספר גדול של שורות בעסקה אחת, מומלץ לפצל את העסקה לכמה עסקאות או ליצור אינדקס ScaNN בלי להפעיל יצירת אינדקס מושהית.
הפעלת יצירת אינדקסים מושהית
כדי להפעיל יצירת אינדקס מושהית:
מפעילים את התכונה הניסיונית
scann.enable_index_maintenance(מופעלת כברירת מחדל) ואת התכונה הניסיוניתscann.enable_preview_features. הדגלscann.enable_preview_featuresמאפשר גם תכונות אחרות בגרסת טרום-השקה.gcloud alloydb instances update INSTANCE_ID \ --database-flags scann.enable_index_maintenance=on \ --database-flags scann.enable_preview_features=on \ --region=REGION_ID \ --cluster=CLUSTER_ID \ --project=PROJECT_IDמחליפים את מה שכתוב בשדות הבאים:
-
INSTANCE_ID: המזהה של המכונה. -
REGION_ID: האזור שבו המכונה ממוקמת, לדוגמהus-central1. -
CLUSTER_ID: המזהה של האשכול שבו נמצא המופע. -
PROJECT_ID: מזהה הפרויקט שבו נמצא האשכול.
-
יוצרים אינדקס ScaNN. אם יוצרים אינדקס במצב ידני, צריך לוודא שהפרמטר
auto_maintenanceמוגדר לערךon. מידע נוסף זמין במאמר יצירת אינדקס בהתאמה ידנית.
מגבלות
- תהליך הרקע של יצירת אינדקס אוטומטי משתמש בערכי דגלים ברמת מסד הנתונים. גם אם מגדירים דגלים ברמת הסשן באמצעות הפקודה
SET LOCAL, התהליך מתייחס לערך הדגל שהוגדר ברמת מסד הנתונים. - אם אתם מתכננים להוסיף כמות גדולה של נתונים לטבלה ריקה בעסקה אחת, מומלץ להריץ את העסקה של הוספת הנתונים ואז ליצור אינדקס ScaNN.
הפעלת יצירת אינדקס בטבלאות ריקות או קטנות
AlloyDB Omni משתמש באימותים כדי למנוע יצירה של אינדקס ScaNN בטבלה ריקה או בטבלה עם מעט מאוד שורות, מהסיבות הבאות:
אינדקס ScaNN עובר אימון על כמות לא מספקת של נתונים. התוצאה יכולה להיות שליפה לא טובה של נתונים בחיפושים של דמיון וקטורי.
יכול להיות שתהיה ירידה בביצועי הכתיבה למסד הנתונים.
מומלץ לדחות את יצירת האינדקס במקרים של ביצועים לא אופטימליים.
עם זאת, בתרחישים מסוימים של פיתוח או בדיקה, יכול להיות שתצטרכו ליצור אינדקס בטבלה ריקה או קטנה. במקרים כאלה, תוכלו לאלץ יצירת אינדקס. שימו לב שנדרשות הרשאות SUPERUSER כדי לאלץ יצירת אינדקס.
כדי לאלץ יצירת אינדקס, מבצעים את השלבים הבאים:
מגדירים את הפרמטר
scann.allow_blocked_operations creationברמת הסשן לערךtrueבמסד הנתונים:SET scann.allow_blocked_operations = true;אם למשתמש שמשמש להרצת השאילתות האלה אין
SUPERUSERהרשאות, צריך להקצות לו אותן:CREATE USER USERNAME WITH SUPERUSER PASSWORD PASSWORD;מחליפים את המשתנים הבאים:
-
USERNAME: השם של המשתמש שרוצים לתת לו הרשאותSUPERUSER. -
PASSWORD: הסיסמה של המשתמש.
-
יצירת אינדקסים במקביל
כדי ליצור את האינדקס מהר יותר, יכול להיות שמערכת AlloyDB Omni תיצור באופן אוטומטי כמה תהליכי עבודה מקבילים, בהתאם למערך הנתונים ולסוג האינדקס שתבחרו. השגיאה הזו מופיעה בדרך כלל כשיוצרים אינדקס ScaNN ברמה של שלוש או ארבע, או אם מערך הנתונים כולל יותר מ-100 מיליון שורות.
למרות ש-AlloyDB Omni מבצע אופטימיזציה אוטומטית של מספר העובדים המקבילים, אתם יכולים לכוון את העובדים המקבילים באמצעות הפרמטרים הבאים של תכנון שאילתות ב-PostgreSQL:
כדי להימנע מבעיות של חוסר זיכרון כשיוצרים את אינדקס ScaNN, צריך לוודא שדגלי מסד הנתונים maintenance_work_mem ו-shared_buffers מוגדרים לערך שקטן מזיכרון המכונה הכולל.
הרצת שאילתה
אחרי שמאחסנים את ההטמעות ומבצעים להן אינדוקס במסד הנתונים, אפשר להתחיל לשלוח שאילתות לנתונים. אי אפשר להריץ שאילתות חיפוש בכמות גדולה באמצעות התוסף alloydb_scann.
כדי למצוא את השכנים הסמנטיים הקרובים ביותר למחרוזת טקסט, אפשר להשתמש בפונקציה google_ml.embedding() כדי לתרגם את הטקסט לווקטור.
מכיוון שgoogle_ml.embedding() מחזירה מערך אמיתי, עליך להמיר במפורש את בקשת הפעלת הפונקציה ל-vector לפני החלתה על אחד מהאופרטורים של השכן הקרוב ביותר, למשל <-> למרחק L2. לאחר מכן, האופרטורים האלה יכולים להשתמש באינדקס ScaNN כדי למצוא את שורות מסד הנתונים עם ההטמעות הכי דומות מבחינה סמנטית.
SELECT * FROM TABLE
ORDER BY EMBEDDING_COLUMN DISTANCE_FUNCTION_QUERY
google_ml.embedding(
model_id => 'MODEL_ID',
content => 'CONTENT')::vector
LIMIT ROW_COUNT
מחליפים את המשתנים הבאים:
TABLE: טבלה שמכילה את ההטמעה שאליה משווים את הטקסט.EMBEDDING_COLUMN: העמודה שמכילה את ההטמעות המאוחסנות.
DISTANCE_FUNCTION_QUERY: פונקציית המרחק שבה רוצים להשתמש בשאילתה הזו. בוחרים את המקבילה של פונקציית המרחק בשאילתה, כפי שהוגדרה כשנוצר האינדקס:מרחק L2:
<->מכפלה פנימית:
<#>מרחק קוסינוס:
<=>
MODEL_ID: המזהה של מודל ההטמעה הרשום שרוצים להשתמש בו.
CONTENT: מחרוזת הטקסט שרוצים לתרגם להטמעה ולחפש.
ROW_COUNT: מספר השורות שיוחזרו. לדוגמה, מציינים1אם רוצים את ההתאמה היחידה הטובה ביותר.
המאמרים הבאים
- הרצת חיפושים של דמיון וקטורי
- שיפור הביצועים של שאילתות וקטוריות
- מדדים של אינדקס וקטורים
- איך יוצרים עוזר חכם לשופינג באמצעות AlloyDB, pgvector וניהול נקודות קצה של מודלים