ניהול אינדקסים של וקטורים

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

אינדקס וקטורי הוא מבנה נתונים שנועד לאפשר לפונקציה VECTOR_SEARCH ולפונקציה AI.SEARCH לפעול בצורה יעילה יותר, במיוחד במערכי נתונים גדולים. כשמשתמשים באינדקס, פונקציות החיפוש האלה משתמשות באלגוריתמים של Approximate Nearest Neighbor‏ (ANN) כדי לצמצם את זמן האחזור של השאילתות ואת עלות החישוב. למרות ש-ANN מציג מידה מסוימת של קירוב, כלומר יכול להיות שהאחזור לא יהיה 100%, השיפורים בביצועים בדרך כלל מציעים יתרון לרוב האפליקציות.

תפקידים והרשאות

כדי ליצור אינדקס וקטורי, אתם צריכים את הרשאת ה-IAM‏ bigquery.tables.createIndex בטבלה שבה אתם יוצרים את האינדקס. כדי להסיר אינדקס וקטורי, נדרשת ההרשאה bigquery.tables.deleteIndex. כל אחד מהתפקידים הבאים שמוגדרים מראש ב-IAM כולל את ההרשאות שנדרשות כדי לעבוד עם אינדקסים של וקטורים:

  • בעלים של נתונים ב-BigQuery‏ (roles/bigquery.dataOwner)
  • עריכה של נתוני BigQuery‏ (roles/bigquery.dataEditor)

בחירת סוג של אינדקס וקטורי

ב-BigQuery יש שני סוגים של אינדקסים וקטוריים, IVF ו-TreeAH, שכל אחד מהם מתאים לתרחישים שונים. ב-BigQuery אפשר לבצע חיפוש וקטורי באצווה, על ידי עיבוד של כמה שורות של נתוני הקלט ב-VECTOR_SEARCH. עבור קבוצות קטנות של שאילתות, מומלץ להשתמש באינדקסים של IVF. לשאילתות באצוות גדולות, עדיף להשתמש באינדקסים של TreeAH, שמבוססים על אלגוריתם ScaNN של Google.

מדד IVF

‫IVF הוא אינדקס קבצים הפוך, שמשתמש באלגוריתם k-means כדי לקבץ את נתוני הווקטורים, ואז מחלק את נתוני הווקטורים על סמך האשכולות האלה. הפונקציות VECTOR_SEARCH ו-AI.SEARCH יכולות להשתמש במחיצות האלה כדי להפחית את כמות הנתונים שהן צריכות לקרוא כדי לקבוע את התוצאה.

מדד TreeAH

השם של סוג האינדקס TreeAH מורכב מהמבנה דמוי העץ שלו ומהשימוש שלו ב-Asymmetric Hashing‏ (AH), טכניקת קוונטיזציה בסיסית מאלגוריתם ScaNN שעליו הוא מבוסס. כך פועל אינדקס TreeAH:

  1. מסד הנתונים הטבלאי מחולק למקטעים קטנים יותר שקל יותר לנהל.
  2. מודל אשכולות מאומן, כאשר מספר האשכולות נגזר מהאפשרות leaf_node_embedding_count בארגומנט tree_ah_options של ההצהרה CREATE VECTOR INDEX.
  3. הווקטורים נדחסים באמצעות כימות מוצרים, שיטה שמפחיתה את השימוש בזיכרון. הווקטורים הדחוסים מאוחסנים בטבלאות האינדקס במקום הווקטורים המקוריים, וכך מצטמצם גודל אינדקס הווקטורים.
  4. כשהפונקציה VECTOR_SEARCH או AI.SEARCH פועלת, רשימת מועמדים לכל וקטור שאילתה מחושבת ביעילות באמצעות גיבוב אסימטרי, שעבר אופטימיזציה של חומרה לחישובי מרחק משוערים. לאחר מכן המערכת נותנת למועמדים האלה ציון חדש ומדרגת אותם מחדש באמצעות הטמעה מדויקת.

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

מומלץ לנסות את סוג האינדקס TreeAH אם תרחיש השימוש שלכם עומד בקריטריונים הבאים:

  • הטבלה שלכם מכילה עד 200 מיליון שורות.

  • אתם מריצים לעיתים קרובות שאילתות באצווה גדולה שכוללות מאות או יותר וקטורים של שאילתות.

בשאילתות באצווה קטנה עם סוג האינדקס TreeAH, יכול להיות ש-VECTOR_SEARCH או AI.SEARCH יחזרו לחיפוש בכוח גס. במקרה כזה, מופיע IndexUnusedReason שמסביר למה לא נעשה שימוש באינדקס הווקטורי.

יצירת אינדקס וקטורי של IVF

כדי ליצור אינדקס וקטורי של IVF, משתמשים בהצהרה הבאה של שפת הגדרת נתונים (DDL) ב-CREATE VECTOR INDEX:

  1. נכנסים לדף BigQuery.

    כניסה ל-BigQuery

  2. בעורך השאילתות, מריצים את הצהרת ה-SQL הבאה:

    כדי ליצור אינדקס וקטורי מסוג IVF:

    CREATE [ OR REPLACE ] VECTOR INDEX [ IF NOT EXISTS ] INDEX_NAME
    ON DATASET_NAME.TABLE_NAME(COLUMN_NAME)
    STORING(STORED_COLUMN_NAME [, ...])
    OPTIONS(index_type = 'IVF',
      distance_type = 'DISTANCE_TYPE',
      ivf_options = '{"num_lists":NUM_LISTS}')

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫INDEX_NAME: השם של אינדקס הווקטור שאתם יוצרים. האינדקס תמיד נוצר באותו פרויקט ובאותה קבוצת נתונים כמו מסד הנתונים הטבלאי, ולכן אין צורך לציין אותם בשם.
    • ‫DATASET_NAME: שם מערך הנתונים שמכיל את הטבלה.
    • ‫TABLE_NAME: שם הטבלה שמכילה את העמודה עם נתוני ההטמעות.
    • ‫COLUMN_NAME: השם של עמודה שמכילה את נתוני ההטמעות. הסוג של העמודה חייב להיות ARRAY<FLOAT64>, או שאם משתמשים ביצירה אוטונומית של הטמעה, הסוג חייב להיות STRUCT<result ARRAY<FLOAT64>, status STRING>.

      בכל המקרים, כל הרכיבים במערך ההטמעה צריכים להיות לא NULL, ולכל הערכים בעמודה צריכים להיות אותם ממדי מערך.

      אם סוג העמודה הוא STRUCT<result ARRAY<FLOAT64>, status STRING>, הערך של STRUCT יכול להיות NULL או שהמערך של result יכול להיות NULL. המערכת מתעלמת מכל השורות שבהן הערך של NULL הוא אחד מהערכים האלה.

    • ‫STORED_COLUMN_NAME: השם של עמודה ברמה העליונה בטבלה, שרוצים לאחסן באינדקס הווקטורי. סוג העמודה לא יכול להיות RANGE. לא נעשה שימוש בעמודות מאוחסנות אם לטבלה יש מדיניות גישה ברמת השורה או אם לעמודה יש תג מדיניות. למידע על איך מפעילים עמודות מאוחסנות, ראו אחסון עמודות וסינון מקדים.

    • ‫DISTANCE_TYPE: מציין את סוג המרחק שמוגדר כברירת מחדל לשימוש כשמבצעים חיפוש וקטורי באמצעות האינדקס הזה. הערכים הנתמכים הם EUCLIDEAN,‏ COSINE ו-DOT_PRODUCT. ‫EUCLIDEAN היא ברירת המחדל.

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

      אם מציינים ערך לארגומנט distance_type של הפונקציה VECTOR_SEARCH או AI.SEARCH, המערכת משתמשת בערך הזה במקום בערך של DISTANCE_TYPE.

    • ‫NUM_LISTS: ערך INT64 שמציין את מספר הרשימות שאליהן מדד ה-IVF מאגד את נתוני הווקטורים ואז מחלק אותם. הערך הזה צריך להיות 5,000 או פחות. במהלך ההוספה לאינדקס, וקטורים מוקצים לרשימה שתואמת למרכז האשכול הקרוב ביותר שלהם. אם לא מציינים את הארגומנט הזה, BigQuery קובע ערך ברירת מחדל על סמך מאפייני הנתונים. ערך ברירת המחדל מתאים לרוב תרחישי השימוש.

      NUM_LISTS שולט ברמת הפירוט של כוונון השאילתות. ערכים גבוהים יותר יוצרים יותר רשימות, כך שאפשר להגדיר את האפשרות fraction_lists_to_search של פונקציית החיפוש לסרוק אחוז קטן יותר של האינדקס. לדוגמה, סריקה של 1% מתוך 100 רשימות לעומת סריקה של 10% מתוך 10 רשימות. האפשרות הזו מאפשרת שליטה מדויקת יותר במהירות החיפוש ובאחזור, אבל היא מעלה מעט את עלות יצירת האינדקס. מגדירים את ערך הארגומנט הזה בהתאם למידת הדיוק שבה רוצים לכוונן את היקף השאילתה.

בדוגמה הבאה נוצר אינדקס וקטורי בעמודה embedding של my_table:

CREATE TABLE my_dataset.my_table(embedding ARRAY<FLOAT64>);

CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
OPTIONS(index_type = 'IVF');

בדוגמה הבאה נוצר אינדקס וקטורי בעמודה embedding של הטבלה my_table, ומוגדרים סוג המרחק שבו יש להשתמש ואפשרויות ה-IVF:

CREATE TABLE my_dataset.my_table(embedding ARRAY<FLOAT64>);

CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
OPTIONS(index_type = 'IVF', distance_type = 'COSINE',
ivf_options = '{"num_lists": 2500}')

בדוגמה הבאה נוצרת טבלה עם יצירת הטמעות אוטונומית ונוצר אינדקס וקטורי בטבלה. העמודה description_embedding נוצרת באופן אוטומטי על סמך העמודה description.

CREATE TABLE mydataset.products (
  description STRING,
  description_embedding STRUCT<result ARRAY<FLOAT64>, status STRING>
    GENERATED ALWAYS AS (
      AI.EMBED(description, connection_id => 'us.example_connection',
        endpoint => 'text-embedding-005'))
    STORED OPTIONS( asynchronous = TRUE ));

CREATE VECTOR INDEX my_index ON my_dataset.my_table(description_embedding)
OPTIONS(index_type = 'IVF');

יצירת אינדקס וקטורי של TreeAH

כדי ליצור אינדקס וקטורי של TreeAH, משתמשים בהצהרה הבאה של CREATE VECTOR INDEX (שפת הגדרת נתונים):

  1. נכנסים לדף BigQuery.

    כניסה ל-BigQuery

  2. בעורך השאילתות, מריצים את הצהרת ה-SQL הבאה:

    CREATE [ OR REPLACE ] VECTOR INDEX [ IF NOT EXISTS ] INDEX_NAME
    ON DATASET_NAME.TABLE_NAME(COLUMN_NAME)
    STORING(STORED_COLUMN_NAME [, ...])
    OPTIONS(index_type = 'TREE_AH',
      distance_type = 'DISTANCE_TYPE',
      tree_ah_options = '{"leaf_node_embedding_count":LEAF_NODE_EMBEDDING_COUNT,
        "normalization_type":"NORMALIZATION_TYPE"}')

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫INDEX_NAME: השם של אינדקס הווקטור שאתם יוצרים. האינדקס תמיד נוצר באותו פרויקט ובאותה קבוצת נתונים כמו מסד הנתונים הטבלאי, ולכן לא צריך לציין אותם בשם.
    • ‫DATASET_NAME: שם מערך הנתונים שמכיל את הטבלה.
    • ‫TABLE_NAME: שם הטבלה שמכילה את העמודה עם נתוני ההטמעות.
    • ‫COLUMN_NAME: שם העמודה שמכילה את נתוני ההטמעות. הסוג של העמודה חייב להיות ARRAY<FLOAT64>, או שאם משתמשים ביצירה אוטונומית של הטמעה, הסוג חייב להיות STRUCT<result ARRAY<FLOAT64>, status STRING>.

      בכל המקרים, כל הרכיבים במערך ההטמעה צריכים להיות לא NULL, ולכל הערכים בעמודה צריכים להיות אותם ממדי מערך. המערך חייב להיות דו-ממדי לפחות.

      אם סוג העמודה הוא STRUCT<result ARRAY<FLOAT64>, status STRING>, אז הערך STRUCT יכול להיות NULL או שהמערך result יכול להיות NULL. המערכת מתעלמת משורות שבהן הערכים האלה הם NULL.

    • ‫STORED_COLUMN_NAME: השם של עמודה ברמה העליונה בטבלה, שרוצים לאחסן באינדקס הווקטורי. סוג העמודה לא יכול להיות RANGE. לא נעשה שימוש בעמודות מאוחסנות אם לטבלה יש מדיניות גישה ברמת השורה או אם לעמודה יש תג מדיניות. למידע על איך מפעילים עמודות מאוחסנות, ראו אחסון עמודות וסינון מקדים.

    • ‫DISTANCE_TYPE: ארגומנט אופציונלי שמציין את סוג המרחק שמוגדר כברירת מחדל לשימוש כשמבצעים חיפוש וקטורי באמצעות האינדקס הזה. הערכים הנתמכים הם EUCLIDEAN,‏ COSINE ו-DOT_PRODUCT. EUCLIDEAN הוא ערך ברירת המחדל.

      יצירת האינדקס עצמו תמיד משתמשת במרחק EUCLIDEAN לאימון, אבל המרחק שמשמש בפונקציית החיפוש יכול להיות שונה.

      אם מציינים ערך לארגומנט distance_type של הפונקציה VECTOR_SEARCH או AI.SEARCH, המערכת משתמשת בערך הזה במקום בערך DISTANCE_TYPE.

    • ‫LEAF_NODE_EMBEDDING_COUNT: ערך INT64 שגדול מ-500 או שווה לו, שמציין את המספר המשוער של וקטורים בכל צומת עלה של העץ שנוצר על ידי אלגוריתם TreeAH. אלגוריתם TreeAH מחלק את מרחב הנתונים כולו למספר רשימות, כאשר כל רשימה מכילה בערך LEAF_NODE_EMBEDDING_COUNT נקודות נתונים. ערך נמוך יותר יוצר יותר רשימות עם פחות נקודות על הגרף, וערך גבוה יותר יוצר פחות רשימות עם יותר נקודות על הגרף. ערך ברירת המחדל הוא 1,000, והוא מתאים לרוב מערכי הנתונים.

    • ‫NORMALIZATION_TYPE: ערך של STRING. הערכים הנתמכים הם NONE או L2. ערך ברירת המחדל הוא NONE. הנורמליזציה מתבצעת לפני כל עיבוד, גם של נתוני מסד הנתונים הטבלאי וגם של נתוני השאילתה, אבל היא לא משנה את עמודת ההטמעה COLUMN_NAME ב-TABLE_NAME. בהתאם למערך הנתונים, למודל ההטמעה ולסוג המרחק שבו משתמשים במהלך החיפוש, נרמול ההטמעות עשוי לשפר את ה-recall.

בדוגמה הבאה נוצר אינדקס וקטורי בעמודה embedding של my_table, ומוגדרים סוג המרחק שבו יש להשתמש ואפשרויות TreeAH:

CREATE TABLE my_dataset.my_table(id INT64, embedding ARRAY<FLOAT64>);

CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
OPTIONS (index_type = 'TREE_AH', distance_type = 'EUCLIDEAN',
tree_ah_options = '{"normalization_type": "L2"}');

כדי לשפר את הביצועים של שאילתות חיפוש היברידי, אתם יכולים להוסיף לאינדקס הווקטורי את פרטי מילות המפתח שנדרשים לעיבוד של חיפוש לקסיקלי. לשם כך, מציינים את lexical_search_columns בהצהרת CREATE VECTOR INDEX. אפשר לעשות את זה לכל סוג קיים של אינדקס וקטורי, כמו IVF או TreeAH. ההצהרה הבאה של CREATE VECTOR INDEX כוללת מידע על מילות מפתח. בדוגמה הזו, האפשרות lexical_search_columns מציגה רשימה של כל העמודות שמשמשות לחלק המילוני של שאילתת חיפוש היברידית. כל עמודה שמוסיפים לרשימה lexical_search_columns צריכה להופיע גם כעמודה מאוחסנת בסעיף STORING:

CREATE [ OR REPLACE ] VECTOR INDEX [ IF NOT EXISTS ] INDEX_NAME
ON DATASET_NAME.TABLE_NAME(COLUMN_NAME)
STORING(STORED_COLUMN_NAME [, ...])
OPTIONS(
  index_type = 'INDEX_TYPE',
  distance_type = 'DISTANCE_TYPE',
  ivf_options = '{"num_lists":NUM_LISTS}'
  lexical_search_columns = [LEXICAL_COLUMN_NAME [, ...]])

בדוגמה הבאה של חיפוש היברידי נוצר אינדקס וקטורי בעמודת ההטמעה ב-my_table. העמודות header, content ו-footer משמשות לחיפוש לקסיקלי בשאילתות חיפוש היברידיות:

CREATE VECTOR INDEX `hybrid_index`
ON `my_dataset.my_table`(`embedding`)
    STORING(`header`, `content`, `footer`)
  OPTIONS (
    index_type = 'IVF',
    distance_type = 'EUCLIDEAN',
    lexical_search_columns = ['header', 'content', 'footer']);

סינון

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

מסננים מקדימים ומסננים מאוחרים

בקריאות VECTOR_SEARCH או AI.SEARCH ב-BigQuery, גם סינון מקדים וגם סינון לאחר השאילתה משמשים לשיפור תוצאות החיפוש, על ידי החלת תנאים שמבוססים על עמודות מטא-נתונים שמשויכות להטמעות הווקטורים. חשוב להבין את ההבדלים ביניהם, את אופן ההטמעה שלהם ואת ההשפעה שלהם כדי לבצע אופטימיזציה של ביצועי השאילתות, העלות והדיוק.

סינון מקדים וסינון משני מוגדרים כך:

  • סינון מקדים: החלת תנאי סינון לפני שחיפוש השכן הקרוב ביותר (ANN) מבצע חישובי מרחק על וקטורים מועמדים. כך מצמצמים את מאגר הווקטורים שנלקחים בחשבון במהלך החיפוש. כתוצאה מכך, סינון מקדים בדרך כלל מקצר את זמני השאילתות ומפחית את עלות החישוב, כי בחיפוש ANN נבדקים פחות מועמדים פוטנציאליים.
  • סינון משני: החלת תנאי הסינון אחרי שחיפוש ה-ANN זיהה את top_k השכנים הקרובים ביותר. הפעולה הזו משפרת את קבוצת התוצאות הסופית על סמך הקריטריונים שצוינו.

המיקום של משפט ה-WHERE קובע אם המסנן פועל כמסנן מקדים או כמסנן לאחר.

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

בדוגמה הבאה אפשר לראות איך יוצרים סינון מקדים:

-- Pre-filter on a stored column. The index speeds up the query.
SELECT *
FROM
  VECTOR_SEARCH(
    (SELECT * FROM my_dataset.my_table WHERE type = 'animal'),
    'embedding',
    TABLE my_dataset.my_testdata);

SELECT *
FROM
  AI.SEARCH(
    (SELECT * FROM my_dataset.my_table WHERE type = 'animal'),
    'content',
    'dog');

-- Filter on a column that isn't stored. The index is used to search the
-- entire table, and then the results are post-filtered. You might see fewer
-- than 5 matches returned for some embeddings.
SELECT query.test_id, base.type, distance
FROM
  VECTOR_SEARCH(
    (SELECT * FROM my_dataset.my_table WHERE id = 123),
    'embedding',
    TABLE my_dataset.my_testdata,
    top_k => 5);

-- Use pre-filters with brute force. The data is filtered and then searched
-- with brute force for exact results.
SELECT query.test_id, base.type, distance
FROM
  VECTOR_SEARCH(
    (SELECT * FROM my_dataset.my_table WHERE id = 123),
    'embedding',
    TABLE my_dataset.my_testdata,
    options => '{"use_brute_force":true}');

כדי ליצור מסנן אחרי, צריך להחיל את פסוקית WHERE של השאילתה מחוץ לפונקציה VECTOR_SEARCH, כדי שהיא תסנן את התוצאות שמוחזרות מהחיפוש.

בדוגמה הבאה אפשר לראות איך יוצרים מסנן פוסטים:

-- Use post-filters. The index is used, but the entire table is searched and
-- the post-filtering might reduce the number of results.
SELECT query.test_id, base.type, distance
FROM
  VECTOR_SEARCH(
    TABLE my_dataset.my_table,
    'embedding',
    TABLE my_dataset.my_testdata,
    top_k => 5)
WHERE base.type = 'animal';

SELECT base.id, distance
FROM
  VECTOR_SEARCH(
    TABLE mydataset.base_table,
    'embedding',
    (SELECT embedding FROM mydataset.query_table),
    top_k => 10
  )
WHERE type = 'document' AND year > 2022

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

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

סינון מקדים עם עמודות מאוחסנות

כדי לשפר עוד יותר את היעילות של אינדקס הווקטורים, אפשר לציין עמודות ממסד הנתונים הטבלאי לאחסון באינדקס הווקטורים. שימוש בעמודות מאוחסנות יכול לשפר את השאילתות שקוראות לפונקציות VECTOR_SEARCH או AI.SEARCH בדרכים הבאות:

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

  • הפונקציות VECTOR_SEARCH ו-AI.SEARCH מחזירות מבנה נתונים שנקרא base שמכיל את כל העמודות מהטבלה הבסיסית. בלי עמודות מאוחסנות, צריך לבצע פעולת צירוף (join) שעלולה להיות יקרה כדי לאחזר את העמודות שמאוחסנות ב-base. אם אתם משתמשים באינדקס IVF והשאילתה שלכם בוחרת רק עמודות מאוחסנות מ-base, ‏ BigQuery מבצע אופטימיזציה של השאילתה כדי לבטל את הצירוף הזה. באינדקסים של TreeAH, הצירוף עם מסד הנתונים הטבלאי לא מוסר. עמודות מאוחסנות באינדקסים של TreeAH משמשות רק למטרות סינון מקדים.

כדי לאחסן עמודות, צריך לציין אותן בסעיף STORING של הצהרת CREATE VECTOR INDEX. אחסון עמודות מגדיל את אינדקס הווקטורים, ולכן מומלץ לאחסן רק את העמודות שמשתמשים בהן או מסננים אותן הכי הרבה.

בדוגמה הבאה נוצר אינדקס וקטורי עם עמודות מאוחסנות, ואז מופעלת שאילתת חיפוש וקטורי שבוחרת רק עמודות מאוחסנות:

-- Create a table that contains an embedding.
CREATE TABLE my_dataset.my_table(embedding ARRAY<FLOAT64>, type STRING, creation_time DATETIME, id INT64);

-- Create a query table that contains an embedding.
CREATE TABLE my_dataset.my_testdata(embedding ARRAY<FLOAT64>, test_id INT64);

-- Create a vector index with stored columns.
CREATE VECTOR INDEX my_index ON my_dataset.my_table(embedding)
STORING (type, creation_time)
OPTIONS (index_type = 'IVF');

-- Select only stored columns from a vector search to avoid an expensive join.
SELECT query, base.type, distance
FROM
  VECTOR_SEARCH(
    TABLE my_dataset.my_table,
    'embedding'
    TABLE my_dataset.my_testdata);

מגבלות על עמודות מאוחסנות

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

סינון מקדים עם מחיצות

אם הטבלה שבה אתם יוצרים את אינדקס הווקטורים מחולקת למחיצות, אתם יכולים לבחור לחלק למחיצות גם את אינדקס הווקטורים. לחלוקה למחיצות של אינדקס וקטורי יש את היתרונות הבאים:

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

אפשר לחלק רק אינדקסים של וקטורים מסוג TreeAH. אי אפשר ליצור אינדקס וקטורי מחולק למחיצות בעמודת הטמעה שנוצרה באופן אוטומטי.

מומלץ לחלק אינדקס וקטורי למחיצות רק אם אתם משתמשים בסינון מקדים כדי להגביל את רוב החיפושים הווקטוריים לכמה מחיצות.

כדי ליצור אינדקס מחולק למחיצות, משתמשים בסעיף PARTITION BY של ההצהרה CREATE VECTOR INDEX. הפסקה PARTITION BY שאתם מציינים בהצהרת CREATE VECTOR INDEX חייבת להיות זהה לפסקה PARTITION BY שצוינה בהצהרת CREATE TABLE של הטבלה שעליה אתם יוצרים את אינדקס הווקטור, כמו שמוצג בדוגמה הבאה:

-- Create a date-partitioned table.
CREATE TABLE my_dataset.my_table(
  embeddings ARRAY,
  id INT64,
  date DATE,
)
PARTITION BY date;

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX my_index ON my_dataset.my_table(embeddings)
PARTITION BY date
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

אם הטבלה משתמשת בחלוקה למחיצות לפי עמודות של טווח מספרים שלמים או יחידות זמן, עמודת החלוקה למחיצות מאוחסנת באינדקס הווקטורי, מה שמגדיל את עלות האחסון. אם עמודה בטבלה משמשת גם בסעיף STORING וגם בסעיף PARTITION BY של הצהרת CREATE VECTOR INDEX, העמודה מאוחסנת רק פעם אחת.

כדי להשתמש במחיצה של אינדקס הווקטור, מסננים את עמודת החלוקה למחיצות בשאילתת המשנה של מסד הנתונים הטבלאי בקריאה VECTOR_SEARCH או AI.SEARCH. בדוגמה הבאה, הטבלה samples.items מחולקת למחיצות לפי העמודה produced_date, ולכן שאילתת המשנה של מסד הנתונים הטבלאי בהצהרה VECTOR_SEARCH מסננת לפי העמודה produced_date:

SELECT query.id, base.id, distance
FROM VECTOR_SEARCH(
  (SELECT * FROM my_dataset.my_table WHERE produced_date = '2025-01-01'),
  'embedding',
  TABLE samples.test,
  distance_type => 'COSINE',
  top_k => 10
);

דוגמאות

יוצרים אינדקס וקטורי מחולק למחיצות בטבלה שמחולקת למחיצות לפי תאריך ושעה:

-- Create a datetime-partitioned table.
CREATE TABLE my_dataset.my_table(
  id INT64,
  produced_date DATETIME,
  embeddings ARRAY
)
PARTITION BY produced_date;

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX index0 ON my_dataset.my_table(embeddings)
PARTITION BY produced_date
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

יוצרים אינדקס וקטורי מחולק למחיצות בטבלה שמחולקת למחיצות לפי חותמת זמן:

-- Create a timestamp-partitioned table.
CREATE TABLE my_dataset.my_table(
  id INT64,
  produced_time TIMESTAMP,
  embeddings ARRAY
)
PARTITION BY TIMESTAMP_TRUNC(produced_time, HOUR);

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX index0 ON my_dataset.my_table(embeddings)
PARTITION BY TIMESTAMP_TRUNC(produced_time, HOUR)
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

יצירת אינדקס וקטורי מחולק למחיצות בטבלה שמחולקת למחיצות לפי טווח של מספרים שלמים:

-- Create an integer range-partitioned table.
CREATE TABLE my_dataset.my_table(
  id INT64,
  embeddings ARRAY
)
PARTITION BY RANGE_BUCKET(id, GENERATE_ARRAY(-100, 100, 20));

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX index0 ON my_dataset.my_table(embeddings)
PARTITION BY RANGE_BUCKET(id, GENERATE_ARRAY(-100, 100, 20))
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

יצירת אינדקס וקטורי מחולק למחיצות בטבלה שמחולקת למחיצות לפי זמן הטמעת הנתונים:

-- Create an ingestion time-partitioned table.
CREATE TABLE my_dataset.my_table(
  id INT64,
  embeddings ARRAY
)
PARTITION BY TIMESTAMP_TRUNC(_PARTITIONTIME, DAY);

-- Create a partitioned vector index on the table.
CREATE VECTOR INDEX index0 ON my_dataset.my_table(embeddings)
PARTITION BY TIMESTAMP_TRUNC(_PARTITIONTIME, DAY)
OPTIONS(index_type='TREE_AH', distance_type='COSINE');

המגבלות של סינון מקדים

מתי הנתונים נוספים לאינדקס

האינדקסים של הווקטורים מנוהלים באופן מלא על ידי BigQuery ומתעדכנים אוטומטית כשיש שינוי בטבלה המאונדקסת.

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

אם יוצרים אינדקס וקטורי בעמודת הטמעה שנוצרה באופן אוטומטי, אימון האינדקס מתחיל ברגע שלפחות 80% מהשורות יצרו הטמעות.

אם יוצרים אינדקס וקטורי בטבלה שקטנה מ-10MB, האינדקס הווקטורי לא מאוכלס. באופן דומה, אם מוחקים נתונים מטבלה שנוסף לה אינדקס וגודל הטבלה קטן מ-10MB, האינדקס הווקטורי מושבת באופן זמני. במקרה כזה, שאילתות חיפוש וקטורי לא משתמשות באינדקס, והקוד indexUnusedReasons בקטע vectorSearchStatistics של משאב Job הוא BASE_TABLE_TOO_SMALL. בלי האינדקס, פונקציית החיפוש חוזרת באופן אוטומטי לשימוש בכוח גס כדי למצוא את השכנים הקרובים ביותר של ההטמעות.

אם מוחקים את העמודה עם האינדקס בטבלה, או משנים את השם של הטבלה עצמה, אינדקס הווקטורים נמחק אוטומטית.

מעקב אחרי הסטטוס של אינדקסים של וקטורים

אפשר לעקוב אחרי התקינות של האינדקסים של הווקטורים באמצעות שאילתות של INFORMATION_SCHEMA תצוגות. התצוגות הבאות מכילות מטא-נתונים על אינדקסים של וקטורים:

  • בתצוגה INFORMATION_SCHEMA.VECTOR_INDEXES יש מידע על אינדקסים של וקטורים במערך נתונים.

    אחרי שההצהרה CREATE VECTOR INDEX מסתיימת, צריך לאכלס את האינדקס כדי שתוכלו להשתמש בו. אפשר להשתמש בעמודות last_refresh_time ו-coverage_percentage כדי לוודא שאינדקס וקטורי מוכן. אם אינדקס הווקטורים לא מוכן, עדיין אפשר להשתמש בפונקציות VECTOR_SEARCH ו-AI.SEARCH בטבלה, אבל יכול להיות שהן יפעלו לאט יותר בלי האינדקס.

  • בתצוגה INFORMATION_SCHEMA.VECTOR_INDEX_COLUMNS יש מידע על העמודות שנוספו לאינדקס וקטורי בכל הטבלאות בקבוצת הנתונים.

  • בתצוגה INFORMATION_SCHEMA.VECTOR_INDEX_OPTIONS יש מידע על האפשרויות שמשמשות את האינדקסים של הווקטורים במערך נתונים.

דוגמאות לאינדקסים של וקטורים

בדוגמה הבאה מוצגים כל האינדקסים הפעילים של וקטורים בטבלאות במערך הנתונים my_dataset, שנמצא בפרויקט my_project. הוא כולל את השמות שלהם, את הצהרות ה-DDL ששימשו ליצירה שלהם ואת אחוז הכיסוי שלהם. אם גודל מסד נתונים טבלאי עם אינדקס קטן מ-10MB, האינדקס שלו לא יאוכלס, ובמקרה כזה הערך של coverage_percentage יהיה 0.

SELECT table_name, index_name, ddl, coverage_percentage
FROM my_project.my_dataset.INFORMATION_SCHEMA.VECTOR_INDEXES
WHERE index_status = 'ACTIVE';

התוצאה אמורה להיראות כך:

+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+
| table_name | index_name | ddl                                                                                             | coverage_percentage |
+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+
| table1     | indexa     | CREATE VECTOR INDEX `indexa` ON `my_project.my_dataset.table1`(embeddings)                      | 100                 |
|            |            | OPTIONS (distance_type = 'EUCLIDEAN', index_type = 'IVF', ivf_options = '{"num_lists": 100}')   |                     |
+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+
| table2     | indexb     | CREATE VECTOR INDEX `indexb` ON `my_project.my_dataset.table2`(vectors)                         | 42                  |
|            |            | OPTIONS (distance_type = 'COSINE', index_type = 'IVF', ivf_options = '{"num_lists": 500}')      |                     |
+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+
| table3     | indexc     | CREATE VECTOR INDEX `indexc` ON `my_project.my_dataset.table3`(vectors)                         | 98                  |
|            |            | OPTIONS (distance_type = 'DOT_PRODUCT', index_type = 'TREE_AH',                                 |                     |
|            |            |          tree_ah_options = '{"leaf_node_embedding_count": 1000, "normalization_type": "NONE"}') |                     |
+------------+------------+-------------------------------------------------------------------------------------------------+---------------------+

דוגמאות לעמודות של אינדקס וקטורי

השאילתה הבאה מחלצת מידע על עמודות שיש להן אינדקסים וקטוריים:

SELECT table_name, index_name, index_column_name, index_field_path
FROM my_project.dataset.INFORMATION_SCHEMA.VECTOR_INDEX_COLUMNS;

התוצאה אמורה להיראות כך:

+------------+------------+-------------------+------------------+
| table_name | index_name | index_column_name | index_field_path |
+------------+------------+-------------------+------------------+
| table1     | indexa     | embeddings        | embeddings       |
| table2     | indexb     | vectors           | vectors          |
| table3     | indexc     | vectors           | vectors          |
+------------+------------+-------------------+------------------+

דוגמאות לאפשרויות של אינדקס וקטורי

השאילתה הבאה מחלצת מידע על אפשרויות של אינדקס וקטורי:

SELECT table_name, index_name, option_name, option_type, option_value
FROM my_project.dataset.INFORMATION_SCHEMA.VECTOR_INDEX_OPTIONS;

התוצאה אמורה להיראות כך:

+------------+------------+------------------+------------------+-------------------------------------------------------------------+
| table_name | index_name | option_name      | option_type      | option_value                                                      |
+------------+------------+------------------+------------------+-------------------------------------------------------------------+
| table1     | indexa     | index_type       | STRING           | IVF                                                               |
| table1     | indexa     | distance_type    | STRING           | EUCLIDEAN                                                         |
| table1     | indexa     | ivf_options      | STRING           | {"num_lists": 100}                                                |
| table2     | indexb     | index_type       | STRING           | IVF                                                               |
| table2     | indexb     | distance_type    | STRING           | COSINE                                                            |
| table2     | indexb     | ivf_options      | STRING           | {"num_lists": 500}                                                |
| table3     | indexc     | index_type       | STRING           | TREE_AH                                                           |
| table3     | indexc     | distance_type    | STRING           | DOT_PRODUCT                                                       |
| table3     | indexc     | tree_ah_options  | STRING           | {"leaf_node_embedding_count": 1000, "normalization_type": "NONE"} |
+------------+------------+------------------+------------------+-------------------------------------------------------------------+

אימות השימוש באינדקס וקטורי

המידע על השימוש באינדקס וקטורי זמין במטא-נתונים של המשימה שהריצה את שאילתת החיפוש הווקטורי. אפשר לראות את המטא-נתונים של העבודות באמצעות Google Cloud המסוף, כלי שורת הפקודה של BigQuery, BigQuery API או ספריות הלקוח.

כשמשתמשים במסוף Google Cloud , אפשר למצוא מידע על השימוש באינדקס וקטורי בשדות Vector Index Usage Mode ו- Vector Index Unused Reasons.

כשמשתמשים בכלי bq או ב-BigQuery API, אפשר למצוא את נתוני השימוש באינדקס וקטורי בקטע VectorSearchStatistics של משאב Job.

מצב השימוש באינדקס מציין אם נעשה שימוש באינדקס וקטורי, באמצעות אחד מהערכים הבאים:

  • ‫UNUSED: לא נעשה שימוש באינדקס וקטורי.
  • ‫PARTIALLY_USED: חלק מפונקציות החיפוש בשאילתה השתמשו באינדקסים וקטוריים וחלק לא.
  • ‫FULLY_USED: כל פונקציית חיפוש בשאילתה השתמשה באינדקס וקטורי.

כשהערך של מצב השימוש באינדקס הוא UNUSED או PARTIALLY_USED, הסיבות לאי-שימוש באינדקס מציינות למה לא נעשה שימוש באינדקסים של וקטורים בשאילתה.

לדוגמה, התוצאות הבאות שהוחזרו על ידי bq show --format=prettyjson -j my_job_id מראות שהאינדקס לא היה בשימוש כי האפשרות use_brute_force צוינה בפונקציה VECTOR_SEARCH:

"vectorSearchStatistics": {
  "indexUnusedReasons": [
    {
      "baseTable": {
        "datasetId": "my_dataset",
        "projectId": "my_project",
        "tableId": "my_table"
      },
      "code": "INDEX_SUPPRESSED_BY_FUNCTION_OPTION",
      "message": "No vector index was used for the base table `my_project:my_dataset.my_table` because use_brute_force option has been specified."
    }
  ],
  "indexUsageMode": "UNUSED"
}

בעיה ידועה: השימוש באינדקס הווקטורי עשוי להיות לא מדויק כשהשאילתה פועלת, מבוטלת או נכשלת.

אפשרויות לניהול אינדקסים

כדי ליצור אינדקסים ולתחזק אותם ב-BigQuery, יש שתי אפשרויות:

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

שימוש במשבצות משותפות

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

אם תוסיפו נתונים לטבלה, והגודל הכולל של הטבלאות שנוספו לאינדקס יחרוג מהמגבלה של הארגון, BigQuery ישהה את ניהול האינדקס של הטבלה הזו. במקרה כזה, בשדה index_status בתצוגה INFORMATION_SCHEMA.VECTOR_INDEXES מוצג הערך PENDING DISABLEMENT והאינדקס מועבר לתור למחיקה. בזמן שהאינדקס ממתין להשבתה, הוא עדיין משמש בשאילתות ותחויבו על אחסון האינדקס. אחרי שהאינדקס נמחק, בשדה index_status האינדקס מוצג כ-TEMPORARILY DISABLED. במצב הזה, שאילתות לא משתמשות באינדקס, ואין חיוב על אחסון האינדקס. במקרה הזה, הקוד IndexUnusedReason הוא BASE_TABLE_TOO_LARGE.

אם תמחקו נתונים מהטבלה והגודל הכולל של הטבלאות שנוספו לאינדקס יהיה קטן מהמגבלה לארגון, ניהול האינדקסים יתחדש. השדה index_status בתצוגה INFORMATION_SCHEMA.VECTOR_INDEXES הוא ACTIVE, השאילתות יכולות להשתמש באינדקס ואתם מחויבים על אחסון האינדקס.

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

‫BigQuery לא מתחייב לגבי הקיבולת הזמינה של המאגר המשותף או קצב העברת הנתונים של הוספה לאינדקס. באפליקציות בסביבת הייצור, יכול להיות שתרצו להשתמש במשבצות ייעודיות לעיבוד האינדקס.

שימוש בהזמנה משלכם

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

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

כדי ליצור אינדקס לטבלאות בפרויקט עם הזמנה ייעודית, צריך ליצור הזמנה באזור שבו הטבלאות נמצאות. לאחר מכן, מקצים את הפרויקט ל-reservation עם job_type שמוגדר ל-BACKGROUND, שמשתף משאבים בין משימות אופטימיזציה ברקע:

SQL

משתמשים בהצהרת ה-DDL‏ CREATE ASSIGNMENT.

  1. נכנסים לדף BigQuery במסוף Google Cloud .

    כניסה ל-BigQuery

  2. בעורך השאילתות, מזינים את ההצהרה הבאה:

    CREATE ASSIGNMENT
      `ADMIN_PROJECT_ID.region-LOCATION.RESERVATION_NAME.ASSIGNMENT_ID`
    OPTIONS (
      assignee = 'projects/PROJECT_ID',
      job_type = 'BACKGROUND');

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫ADMIN_PROJECT_ID: מזהה הפרויקט של פרויקט הניהול שבבעלותו משאב ההזמנה
    • ‫LOCATION: המיקום של ההזמנה
    • ‫RESERVATION_NAME: השם של ההזמנה
    • ‫ASSIGNMENT_ID: מזהה המטלה

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

    • ‫PROJECT_ID: מזהה הפרויקט שמכיל את הטבלאות לאינדוקס. הפרויקט הזה מוקצה להזמנה.

  3. לוחצים על הפעלה.

מידע נוסף על הרצת שאילתות זמין במאמר הרצת שאילתה אינטראקטיבית.

BQ

משתמשים בפקודה bq mk:

bq mk \
    --project_id=ADMIN_PROJECT_ID \
    --location=LOCATION \
    --reservation_assignment \
    --reservation_id=RESERVATION_NAME \
    --assignee_id=PROJECT_ID \
    --job_type=BACKGROUND \
    --assignee_type=PROJECT

מחליפים את מה שכתוב בשדות הבאים:

  • ‫ADMIN_PROJECT_ID: מזהה הפרויקט של פרויקט הניהול שבבעלותו משאב המקום השמור
  • ‫LOCATION: המיקום של ההזמנה
  • ‫RESERVATION_NAME: השם של ההזמנה
  • ‫PROJECT_ID: מזהה הפרויקט שרוצים להקצות לשמירת המקום הזו

הצגת משימות ההוספה לאינדקס

משימת אינדוקס חדשה נוצרת בכל פעם שנוצר או מתעדכן אינדקס בטבלה יחידה. כדי לראות מידע על המשימה, שולחים שאילתה לתצוגות של INFORMATION_SCHEMA.JOBS*. כדי לסנן לפי משימות הוספה לאינדקס, מגדירים את job_type IS NULL AND SEARCH(job_id, '`search_index`') בסעיף WHERE של השאילתה. בדוגמה הבאה מוצגת רשימה של חמש משימות ההוספה לאינדקס האחרונות בפרויקט my_project:

SELECT *
FROM
 region-us.INFORMATION_SCHEMA.JOBS
WHERE
  project_id  = 'my_project'
  AND job_type IS NULL
  AND SEARCH(job_id, '`search_index`')
ORDER BY
 creation_time DESC
LIMIT 5;

בחירת גודל ההזמנה

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

  • אתם יוצרים אינדקס בטבלה.
  • הנתונים משתנים בטבלה שנוסף לה אינדקס.
  • הסכימה של טבלה משתנה, וזה משפיע על העמודות שמתווספות לאינדקס.
  • הנתונים והמטא-נתונים של האינדקס עוברים אופטימיזציה או מתעדכנים מדי פעם.

מספר המשבצות שצריך למשימת ניהול אינדקס בטבלה תלוי בגורמים הבאים:

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

הדרך הכי טובה להעריך את מספר המשבצות שצריך כדי להריץ ביעילות את משימות ניהול האינדקסים היא לעקוב אחרי ניצול המשבצות ולשנות את גודל ההזמנה בהתאם. השאילתה הבאה מפיקה את השימוש היומי ב-slots למשימות של ניהול אינדקסים. רק 30 הימים האחרונים נכללים באזור us-west1:

SELECT
  TIMESTAMP_TRUNC(job.creation_time, DAY) AS usage_date,
  -- Aggregate total_slots_ms used for index-management jobs in a day and divide
  -- by the number of milliseconds in a day. This value is most accurate for
  -- days with consistent slot usage.
  SAFE_DIVIDE(SUM(job.total_slot_ms), (1000 * 60 * 60 * 24)) AS average_daily_slot_usage
FROM
  `region-us-west1`.INFORMATION_SCHEMA.JOBS job
WHERE
  project_id = 'my_project'
  AND job_type IS NULL
  AND SEARCH(job_id, '`search_index`')
GROUP BY
  usage_date
ORDER BY
  usage_date DESC
limit 30;

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

בנייה מחדש של אינדקס וקטורי

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

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

SELECT last_model_build_time
FROM DATASET_NAME.INFORMATION_SCHEMA.VECTOR_INDEXES
WHERE table_name = TABLE_NAME;

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

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

כדי לבנות מחדש אינדקס וקטורי:

  1. עוברים לדף BigQuery.

    כניסה ל-BigQuery

  2. בעורך השאילתות, מריצים את הצהרת ה-SQL הבאה כדי לבדוק את הסטייה בנתונים של הטבלה שנוספה לאינדקס:

    SELECT * FROM VECTOR_INDEX.STATISTICS(TABLE DATASET_NAME.TABLE_NAME);

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫DATASET_NAME: השם של קבוצת הנתונים שמכילה את הטבלה עם האינדקס.
    • ‫TABLE_NAME: שם הטבלה שמכילה את אינדקס הווקטורים.

    הפונקציה מחזירה ערך FLOAT64 בטווח [0,1). ערך נמוך יותר מצביע על פחות סטייה. בדרך כלל, ערך של 0.3 או יותר נחשב לשינוי משמעותי מספיק כדי להצביע על כך שבנייה מחדש של אינדקס וקטורי עשויה להועיל.

  3. אם הפונקציה VECTOR_INDEX.STATISTICS מציינת ששינוי הנתונים בטבלה משמעותי, מריצים את הצהרת ה-SQL הבאה כדי לבנות מחדש את אינדקס הווקטורים:

    ALTER VECTOR INDEX IF EXISTS INDEX_NAME
    ON DATASET_NAME.TABLE_NAME
    REBUILD;

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫INDEX_NAME: השם של אינדקס הווקטורים שאתם בונים מחדש.
    • ‫DATASET_NAME: השם של קבוצת הנתונים שמכילה את הטבלה עם האינדקס.
    • ‫TABLE_NAME: שם הטבלה שמכילה את אינדקס הווקטורים.

מעקב אחר בנייה מחדש

כדי לעקוב אחרי הבנייה מחדש של האינדקס, מריצים שאילתה על התצוגה INFORMATION_SCHEMA.VECTOR_INDEXES. לדוגמה, השאילתה הבאה מציגה את סטטוס הבנייה מחדש של כל האינדקסים בטבלאות במערך הנתונים my_dataset בפרויקט my_project. אם לא מתבצעת בנייה מחדש של אינדקס, הערך של last_index_alteration_info הוא NULL.

SELECT
  table_name,
  index_name,
  last_index_alteration_info.status AS status,
  last_index_alteration_info.new_coverage_percentage AS coverage
FROM my_project.my_dataset.INFORMATION_SCHEMA.VECTOR_INDEXES

התוצאה אמורה להיראות כך:

+------------+------------+-------------+----------+
| table_name | index_name | status      | coverage |
+------------+------------+-------------+----------+
| table1     | index_a    | IN_PROGRESS | 50       |
| table2     | index_b    | null        | null     |
+------------+------------+-------------+----------+

ביטול של בנייה מחדש

כדי לבטל בנייה מחדש של אינדקס וקטורי, משתמשים בפרוצדורת המערכת BQ.CANCEL_INDEX_ALTERATION. יכול להיות שפעולת שינוי האינדקס תושלם לפני שבקשת הביטול תעובד. כדי לוודא שהפעולה בוטלה בהצלחה, שולחים שאילתה אל התצוגה INFORMATION_SCHEMA.VECTOR_INDEXES. אם הביטול הצליח, השדה last_model_build_time לא מתעדכן והשדה last_index_alteration_info לא מופיע באינדקס הזה.

מחיקת אינדקס וקטורי

אם כבר לא צריך אינדקס וקטורי או שרוצים לשנות את העמודה שמקבלת אינדקס בטבלה, אפשר למחוק את האינדקס בטבלה באמצעות הצהרת ה-DDL‏ DROP VECTOR INDEX:

  1. נכנסים לדף BigQuery.

    כניסה ל-BigQuery

  2. בעורך השאילתות, מריצים את הצהרת ה-SQL הבאה:

    DROP VECTOR INDEX INDEX_NAME ON DATASET_NAME.TABLE_NAME;

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫INDEX_NAME: השם של אינדקס הווקטורים שמוחקים.
    • ‫DATASET_NAME: שם מערך הנתונים שמכיל את הטבלה שנוסף לה אינדקס.
    • ‫TABLE_NAME: שם הטבלה שמכילה את אינדקס הווקטורים.

אם טבלה שנוסף לה אינדקס נמחקת, האינדקס שלה נמחק באופן אוטומטי.

כדי להפעיל אפליקציות אונליין עם זמן טעינה קצר במיוחד, השתמשו בשילוב של BigQuery עם חיפוש וקטורי ב-Gemini Enterprise Agent Platform כדי לייבא את ההטמעות של BigQuery לחיפוש וקטורי ולפרוס נקודות קצה עם זמן טעינה קצר. מידע נוסף זמין במאמר ייבוא נתוני אינדקס מ-BigQuery.

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