文件與索引

Search API 為內含結構化資料的文件提供索引模型。您可以搜尋索引,並整理及顯示搜尋結果。這個 API 可支援對字串欄位進行全文比對。文件與索引皆儲存在單獨的永久存放區中,且存放區已針對搜尋作業進行最佳化。Search API 可將任意數量的文件編入索引。對於需要擷取龐大結果集的應用程式而言,App Engine Datastore 可能更適合。

總覽

Search API 有四大基礎概念:文件、索引、查詢與結果。

文件

文件是具有唯一識別碼的物件,也具有包含使用者資料的欄位清單。每個欄位都有名稱與類型。欄位分為多種類型,以欄位所包含的值種類來識別其類型:

  • Atom 欄位 - 不可分割的字元字串。
  • 文字欄位 - 可逐字搜尋的純文字字串。
  • HTML 欄位 - 包含 HTML 標記代碼的字串,只有標記代碼之外的文字可以搜尋。
  • 數字欄位 - 浮點數。
  • 日期欄位 - 日期物件。
  • 地理點欄位 - 具有緯度與經度座標的資料物件。

文件的大小上限為 1 MB。

索引

索引儲存的文件可用於擷取。您可以按照文件 ID 來擷取單一文件、擷取具有連續 ID 的一系列文件,或是擷取索引中的所有文件。您也可以將特定欄位及其值指定為查詢字串,透過搜尋索引來擷取符合這些條件的文件。您可以將文件群組放進獨立的索引中,藉此管理文件群組。

索引中的文件數,或您能夠使用的索引數,兩者皆無限制。根據預設,單一索引中所有文件的總大小限制在 10 GB 以內,但您可從 Google Cloud Platform 主控台的 App Engine「Search」(搜尋) 頁面提交要求,將此限制提升到 200 GB。

查詢

如要搜尋索引,您可以建構內含查詢字串,並可能含有其他某些選項的查詢。查詢字串可以指定一或多個文件欄位的值條件。搜尋索引時,您只會取得索引中欄位符合查詢條件的文件。

最簡單的查詢 (有時候也稱為「全域搜尋」) 是僅包含欄位值的字串。以下搜尋使用的字串會搜尋內含「rose」與「water」字詞的文件:

以下搜尋會搜尋日期欄位中包含「1776 年 7 月 4 日」這個日期,或文字欄位中包含「1776-07-04」字串的文件:

查詢字串也可以更具體一點,其中可包含一或多個條件,每個條件都有一個欄位名稱,以及對於欄位值的限制。條件的確切格式取決於欄位的類型。例如,假設有一個名為「Product」的文字欄位和一個名為「Price」的數字欄位,以下是對這兩個條件執行查詢的字串:

Java 8

// search for documents with pianos that cost less than $5000
index.search("product = piano AND price < 5000");

Java 7

// search for documents with pianos that cost less than $5000
index.search("product = piano AND price < 5000");

「查詢選項」顧名思義為非必要選項。這些選項提供下列各種功能:

  • 控制在搜尋結果中傳回的文件數量。
  • 指定要在結果中包含哪些文件欄位。預設值是包含原始文件中的所有欄位。您可以指定結果只包含一個欄位子集 (原始文件不受影響)。
  • 將結果排序。
  • 使用 FieldExpressions 在文件中建立「運算欄位」,並使用程式碼片段建立刪節文字欄位。
  • 僅傳回每項查詢的部分相符文件 (使用偏移與游標),藉此支援搜尋結果分頁功能。

搜尋結果

search() 的呼叫只能傳回限定數量的相符文件。 搜尋所找到的文件數量,可能會比單一呼叫能傳回的數量來得多。每個搜尋呼叫都會傳回 Results 類別的執行個體,其中包含找到與傳回文件數量相關的資訊,以及傳回文件的清單。您可以使用游標偏移重複執行相同的搜尋,以擷取完整的相符文件集。

其他訓練材料

除了本說明文件以外,您還可以在 Google 開發人員學院閱讀有關 Search API 的兩部分訓練課程。 (儘管該課程使用的是 Python API,您仍可能會發現對於「搜尋」概念的額外討論很有幫助。)

文件與欄位

Document 類別代表文件。每個文件都具有「文件 ID」以及「欄位」清單。

文件 ID

索引中的每個文件都必須具有專屬的文件 ID,或是 doc_id。 從索引中擷取文件時可以使用 ID,而不用執行搜尋。根據預設,Search API 會在文件建立時自動產生 doc_id。您也可以在建立文件時自行指定 doc_iddoc_id 只能包含可見、可列印的 ASCII 字元 (從 33 到 126 (含首尾) 的 ASCII 碼),且不得超過 500 個字元。文件 ID 不得以驚嘆號 (「!」) 開頭,且不得以雙底線 (「__」) 開頭或結尾。

儘管建立容易理解且具有意義的專屬文件 ID 很方便,但您仍不可在搜尋中加入 doc_id。請考量以下情況:您有一個索引內含代表零件的文件,並使用零件的序號做為 doc_id。針對任何單一零件擷取文件會很有效率,但您無法連同其他欄位值 (例如購買日期) 一併搜尋某個範圍內的序號。只要將序號儲存到 Atom 欄位中即可解決這個問題。

文件欄位

文件包含的欄位具有「名稱」、「類型」以及該類型的單一「值」。兩個以上的欄位名稱可以相同,但類型不能相同。例如,您可以使用「年齡」名稱定義兩個欄位:其中一個是文字類型 (值為「二十二」),而另一個是數字類型 (值為「22」)。

欄位名稱

欄位名稱會區分大小寫,並且只能包含 ASCII 字元。名稱必須以字母為開頭,可包含字母、數字或底線。欄位名稱長度不得超過 500 個字元。

多值欄位

欄位只能包含一個值,且必須符合欄位的類型。欄位名稱可以重複。文件可以有相同名稱及相同類型的多個欄位,這是表示多值欄位的一種方式。(但是,相同名稱的日期與數字欄位不得重複。)文件也可以包含相同名稱及「不同」欄位類型的多個欄位。

欄位類型

總共有三種欄位可用於儲存 java.lang.String 字元字串;我們將這些欄位統稱為「字串欄位」

  • 文字欄位:長度上限為 1024**2 個字元的字串。
  • HTML 欄位:長度上限為 1024**2 個字元的 HTML 格式字串。
  • Atom 欄位:長度上限為 500 個字元的字串。

此外,還有三種欄位類型可用於儲存非文字資料:

  • 數字欄位:介於 -2,147,483,647 及 2,147,483,647 之間的雙精度浮點值。
  • 日期欄位:java.util.Date
  • 地理點欄位:地球上由緯度與經度座標表示的點。

使用 Field.FieldType 列舉 TEXTHTMLATOMNUMBERDATEGEO_POINT 指定欄位類型。

字串與日期欄位的特殊處理方式

將含有日期、文字或 HTML 欄位的文件加入索引時,會出現某些特殊處理方式。為了能夠有效使用 Search API,事先瞭解「運作原理」會相當有幫助。

代碼化字串欄位

將 HTML 或文字欄位編入索引時,就會「代碼化」這些欄位的內容。字串會在出現空格或特殊字元 (標點符號、井字號、反斜線等) 的位置分割為代碼。索引將包含每個代碼的項目。如此一來,您可以搜尋僅包含欄位值一部分的關鍵字與詞組。舉例來說,在搜尋「dark」時,會將文件與包含「it was a dark and stormy night」字串的文字欄位進行比對;在搜尋「time」時,則會將文件與包含「this is a real-time system」字串的文字欄位進行比對。

在 HTML 欄位中,標記代碼內的文字並不會代碼化,因此 HTML 欄位中包含 it was a <strong>dark</strong> night 的文件將會符合「night」的搜尋,但不符合「strong」的搜尋。如果您想要搜尋標記文字,請將標記文字儲存在文字欄位中。

Atom 欄位不會代碼化。具有 Atom 欄位且欄位值為「bad weather」的文件,將僅符合對「bad weather」完整字串進行的搜尋,而不符合單獨對「bad」或「weather」進行的搜尋。

代碼化規則
  • 底線 (_) 與連接符號 (&) 字元不會將字詞分割成代碼。

  • 以下這些空白字元一律會將字詞分割成代碼:空格、回車字元、換行字元、水平定位點、垂直定位點、換頁字元與 NULL。

  • 這些字元會被視為標點符號,並且會將字詞分割為代碼:

    !"%()
    *,-|/
    []]^`
    :=>?@
    {}~$
  • 下表中的字元「通常」會將字詞分割成代碼,但根據這些字元出現時所在的前後文,各字元的處理方式可能不同:

    字元 規則
    < 在 HTML 欄位中,「小於」符號表示忽略 HTML 標記的開頭。
    + 如果一或多個「加號」的字串出現在字詞的結尾,則會將這些字串當成字詞的一部分來處理 (C++)。
    # 如果「井字號」前面加上 a、b、c、d、e、f、g、j 或 x (a# - g# 是音樂記號;j# 與 x# 是程式設計語言,c# 既是音樂記號也是程式設計語言),則會將井字號當成字詞的一部分來處理。如果某一字詞「前面加上」了「#」(#google),則會將其當成主題標記,井字號會變成該字詞的一部分。
    ' 如果單引號位於字母「s」之前,而「s」後面跟著空格,例如「John's hat」,就會將單引號當成字母處理。
    . 如果小數點出現在數字之間,則屬於數字的一部分 (即小數點)。如果在縮寫 (A.B.C) 中使用,則也可以是字詞的一部分。
    - 如果在縮寫字中使用破折號 (I-B-M),則破折號也是字詞的一部分。
  • 除了字母與數字 (「A-Z」、「a-z」、「0-9」) 以外的其他所有 7 位元字元都會當成標點符號處理,並將字詞分割成代碼。

  • 其他任何字元都會當成 UTF-8 字元剖析。

縮寫字

代碼化作業使用特殊規則來辨識縮寫字 (如「I.B.M.」、「a-b-c」或「C I A」等字串)。縮寫字是由單一字母字元組成的字串,每個字元之間都加上相同的分隔字元。有效的分隔字元有句號、破折號或任意數量的空格。當縮寫字進行代碼化時,會移除字串內的分隔字元,因此上述範例字串會變成「ibm」、「abc」和「cia」,原始文字則會保留在文件欄位中。

處理縮寫字時,請注意以下幾點:

  • 縮寫字不得包含超過 21 個字母。包含超過 21 個字母的有效縮寫字字串將會分割成一系列縮寫字,每一系列縮寫字內含不超過 21 個字母。
  • 如果縮寫字中的字母由空格分隔,所有字母的大小寫都必須相同。由句號與破折號構成的縮寫字可以使用大小寫混合的字母。
  • 搜尋縮寫字時,可以輸入標準格式的縮寫字 (不含分隔符號的字串),或在字母之間用破折號或縮寫點 (但並非同時使用兩者) 分隔的縮寫字。因此,搜尋「I-B-M」、「I.B.M」或「IBM」字詞時,都可能擷取出「I.B.M」這樣的文字。

日期欄位準確率

當您在文件中建立日期欄位時,可將欄位值設定為 java.util.Date。為了將日期欄位編入索引並搜尋日期欄位,系統會忽略任何時間元件,並將日期轉換成從世界標準時間 1970/1/1 開始的天數。這就表示,即使日期欄位可以包含精確的時間值,日期查詢還是只能以 yyyy-mm-dd 格式指定日期欄位值。這也表示,具有相同日期的日期欄位,其排序順序並未明確定義。

其他文件屬性

文件的「排名」是一個正整數,可確定搜尋傳回的預設文件順序。根據預設,排名會在建立文件時設定為從 2011 年 1 月 1 日開始的秒數。您可以在建立文件時明確設定排名。為多份文件指派相同的排名並不明智,您絕對不可為 10,000 份以上的文件指派相同的排名。如果您指定排序選項,則可使用排名做為排序鍵值。請注意,在排序運算式欄位運算式中使用排名時,會以 _rank 表示。

語言屬性會指定欄位的語言編碼。

如要進一步瞭解這些屬性,請參閱 Document 類別參考資料頁面。

從文件連結至其他資源

您可以使用文件的 doc_id 及其他欄位做為連結,連至應用程式中的其他資源。舉例來說,如果您使用 Blobstore,則可將 doc_id 或 Atom 欄位的值設定為資料的 BlobKey,以建立文件與特定 Blob 之間的關聯。

建立文件

如要建立文件,請使用 Document.newBuilder() 方法要求新製作工具。應用程式能夠存取製作工具時,便可指定選用文件 ID 及新增欄位。

像文件之類的欄位,是使用製作工具建立的。Field.newBuilder() 方法會傳回欄位製作工具,讓您指定欄位的名稱與值。欄位類型會透過選擇一組專屬方法來自動指定。例如,如要指示讓欄位保存純文字,請呼叫 setText()。以下程式碼建構的文件中含有表示留言板問候的欄位。

Java 8

User currentUser = UserServiceFactory.getUserService().getCurrentUser();
String userEmail = currentUser == null ? "" : currentUser.getEmail();
String userDomain = currentUser == null ? "" : currentUser.getAuthDomain();
String myDocId = "PA6-5000";
Document doc =
    Document.newBuilder()
        // Setting the document identifer is optional.
        // If omitted, the search service will create an identifier.
        .setId(myDocId)
        .addField(Field.newBuilder().setName("content").setText("the rain in spain"))
        .addField(Field.newBuilder().setName("email").setText(userEmail))
        .addField(Field.newBuilder().setName("domain").setAtom(userDomain))
        .addField(Field.newBuilder().setName("published").setDate(new Date()))
        .build();

Java 7

User currentUser = UserServiceFactory.getUserService().getCurrentUser();
String userEmail = currentUser == null ? "" : currentUser.getEmail();
String userDomain = currentUser == null ? "" : currentUser.getAuthDomain();
String myDocId = "PA6-5000";
Document doc = Document.newBuilder()
    // Setting the document identifer is optional.
    // If omitted, the search service will create an identifier.
    .setId(myDocId)
    .addField(Field.newBuilder().setName("content").setText("the rain in spain"))
    .addField(Field.newBuilder().setName("email").setText(userEmail))
    .addField(Field.newBuilder().setName("domain").setAtom(userDomain))
    .addField(Field.newBuilder().setName("published").setDate(new Date()))
    .build();

如要存取文件中的欄位,請使用 getOnlyField()

Java 8

String coverLetter = document.getOnlyField("coverLetter").getText();
String resume = document.getOnlyField("resume").getHTML();
String fullName = document.getOnlyField("fullName").getAtom();
Date submissionDate = document.getOnlyField("submissionDate").getDate();

Java 7

String coverLetter = document.getOnlyField("coverLetter").getText();
String resume = document.getOnlyField("resume").getHTML();
String fullName = document.getOnlyField("fullName").getAtom();
Date submissionDate = document.getOnlyField("submissionDate").getDate();

使用索引

將文件放入索引

將文件放入索引時,會將文件複製到永久儲存空間,文件的每個欄位都會根據文件名稱、類型和 doc_id 編入索引。

以下程式碼範例顯示如何存取索引,以及如何將文件放入索引。步驟如下:

Java 8

public static void indexADocument(String indexName, Document document)
    throws InterruptedException {
  IndexSpec indexSpec = IndexSpec.newBuilder().setName(indexName).build();
  Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);

  final int maxRetry = 3;
  int attempts = 0;
  int delay = 2;
  while (true) {
    try {
      index.put(document);
    } catch (PutException e) {
      if (StatusCode.TRANSIENT_ERROR.equals(e.getOperationResult().getCode())
          && ++attempts < maxRetry) { // retrying
        Thread.sleep(delay * 1000);
        delay *= 2; // easy exponential backoff
        continue;
      } else {
        throw e; // otherwise throw
      }
    }
    break;
  }
}

Java 7

public static void indexADocument(String indexName, Document document)
    throws InterruptedException {
  IndexSpec indexSpec = IndexSpec.newBuilder().setName(indexName).build();
  Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);

  final int maxRetry = 3;
  int attempts = 0;
  int delay = 2;
  while (true) {
    try {
      index.put(document);
    } catch (PutException e) {
      if (StatusCode.TRANSIENT_ERROR.equals(e.getOperationResult().getCode())
          && ++attempts < maxRetry) { // retrying
        Thread.sleep(delay * 1000);
        delay *= 2; // easy exponential backoff
        continue;
      } else {
        throw e; // otherwise throw
      }
    }
    break;
  }
}
您一次最多可傳送 200 個文件給 put() 方法。批次放置會比逐次新增單個文件來得有效率。

將文件放入索引,且索引已包含相同 doc_id 的文件時,新文件會取代舊文件。系統不會提出任何警告。在建立或新增文件到索引之前,您可以呼叫 Index.get(id) 來確認特定 doc_id 是否已存在。

請注意,建立 Index 類別的執行個體並不保證永久索引確實存在。在您第一次使用 put 方法將文件加入永久索引時,就會建立永久索引。如要在開始使用索引之前,先行檢查索引是否確實存在,請使用 SearchService.getIndexes() 方法。

更新文件

當您將文件加入索引之後,就無法變更文件。您無法新增或移除欄位,也無法變更欄位的值。但是,您可以用 doc_id 相同的新文件取代舊文件。

依照 doc_id 擷取文件

您可以透過下列兩種方式,使用文件 ID 從索引中擷取文件:

下方範例說明每個呼叫的使用方式。

Java 8

IndexSpec indexSpec = IndexSpec.newBuilder().setName(INDEX).build();
Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);

// Fetch a single document by its  doc_id
Document doc = index.get("AZ125");

// Fetch a range of documents by their doc_ids
GetResponse<Document> docs =
    index.getRange(GetRequest.newBuilder().setStartId("AZ125").setLimit(100).build());

Java 7

IndexSpec indexSpec = IndexSpec.newBuilder().setName(INDEX).build();
Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);

// Fetch a single document by its  doc_id
Document doc = index.get("AZ125");

// Fetch a range of documents by their doc_ids
GetResponse<Document> docs = index.getRange(
    GetRequest.newBuilder().setStartId("AZ125").setLimit(100).build());

依照文件內容搜尋文件

如要從索引中擷取文件,您可以建構查詢字串並呼叫 Index.search()。查詢字串能以引數直接傳送,您也可以在以引數傳送的 Query 物件中包含查詢字串。根據預設,search() 會以遞減排名的排序傳回相符文件。如要控制傳回的文件數量、文件的排序方式,或是將運算欄位新增到結果中,則需使用 Query 物件,此物件會包含查詢字串,而且也能指定其他搜尋和排序選項。

Java 8

final int maxRetry = 3;
int attempts = 0;
int delay = 2;
while (true) {
  try {
    String queryString = "product = piano AND price < 5000";
    Results<ScoredDocument> results = getIndex().search(queryString);

    // Iterate over the documents in the results
    for (ScoredDocument document : results) {
      // handle results
      out.print("maker: " + document.getOnlyField("maker").getText());
      out.println(", price: " + document.getOnlyField("price").getNumber());
    }
  } catch (SearchException e) {
    if (StatusCode.TRANSIENT_ERROR.equals(e.getOperationResult().getCode())
        && ++attempts < maxRetry) {
      // retry
      try {
        Thread.sleep(delay * 1000);
      } catch (InterruptedException e1) {
        // ignore
      }
      delay *= 2; // easy exponential backoff
      continue;
    } else {
      throw e;
    }
  }
  break;
}

Java 7

final int maxRetry = 3;
int attempts = 0;
int delay = 2;
while (true) {
  try {
    String queryString = "product = piano AND price < 5000";
    Results<ScoredDocument> results = getIndex().search(queryString);

    // Iterate over the documents in the results
    for (ScoredDocument document : results) {
      // handle results
      out.print("maker: " + document.getOnlyField("maker").getText());
      out.println(", price: " + document.getOnlyField("price").getNumber());
    }
  } catch (SearchException e) {
    if (StatusCode.TRANSIENT_ERROR.equals(e.getOperationResult().getCode())
        && ++attempts < maxRetry) {
      // retry
      try {
        Thread.sleep(delay * 1000);
      } catch (InterruptedException e1) {
        // ignore
      }
      delay *= 2; // easy exponential backoff
      continue;
    } else {
      throw e;
    }
  }
  break;
}

刪除索引

每個索引都包含索引文件和索引結構定義。如要刪除索引,請先刪除索引中的所有文件,然後再刪除索引結構定義。

您可以將想要刪除的一或多個文件的 doc_id 指定給 delete() 方法,藉此從索引中刪除文件。為提高效率,您應該整批刪除文件。您一次最多可傳送 200 個文件 ID 給 delete() 方法。

Java 8

try {
// looping because getRange by default returns up to 100 documents at a time
while (true) {
List<String> docIds = new ArrayList<>();
// Return a set of doc_ids.
GetRequest request = GetRequest.newBuilder().setReturningIdsOnly(true).build();
GetResponse<Document> response = getIndex().getRange(request);
if (response.getResults().isEmpty()) {
  break;
}
for (Document doc : response) {
  docIds.add(doc.getId());
}
getIndex().delete(docIds);
}
// Delete the index schema
getIndex().deleteSchema();
} catch (RuntimeException e) {
LOG.log(Level.SEVERE, "Failed to delete index", e);
}

Java 7

try {
// looping because getRange by default returns up to 100 documents at a time
while (true) {
List<String> docIds = new ArrayList<>();
// Return a set of doc_ids.
GetRequest request = GetRequest.newBuilder().setReturningIdsOnly(true).build();
GetResponse<Document> response = getIndex().getRange(request);
if (response.getResults().isEmpty()) {
  break;
}
for (Document doc : response) {
  docIds.add(doc.getId());
}
getIndex().delete(docIds);
}
// Delete the index schema
getIndex().deleteSchema();
} catch (RuntimeException e) {
LOG.log(Level.SEVERE, "Failed to delete index", e);
}
您一次最多可傳送 200 個文件給 delete() 方法。整批刪除會比逐個刪除文件來得有效率。

最終一致性

在索引中放置、更新或刪除文件時,變更會傳播至多個資料中心。這通常很快就會發生,但花費的時間各異。Search API 可以保證最終一致性。這表示在某些情況下,搜尋或擷取一或多份文件可能會傳回不會反映最近變更的結果。

判斷索引的大小

索引儲存的文件可用於擷取。您可以按照文件 ID 來擷取單一文件、擷取具有連續 ID 的一系列文件,或是擷取索引中的所有文件。您也可以將特定欄位及其值指定為查詢字串,透過搜尋索引來擷取符合這些條件的文件。您可以將文件群組放進獨立的索引中,藉此管理文件群組。索引中的文件數,或您能夠使用的索引數,兩者皆無限制。根據預設,單一索引中所有文件的總大小限制在 10 GB 以內,但您可從 Google Cloud Platform 主控台的 App Engine「Search」(搜尋) 頁面提交要求,將此限制提升到 200 GB。Index.getStorageLimit() 方法會傳回索引的允許大小上限。

Index.getStorageUsage() 方法是索引所用儲存空間的預估值。此數字為預估值,因為索引監控系統不會持續執行,系統會定期計算實際的使用量。storage_usage 會根據文件新增 (而非刪除) 所佔的比例,在取樣點之間進行調整。

索引結構定義

每個索引都有一個結構定義,其中顯示該索引所含文件中出現的所有欄位名稱和欄位類型。您無法自行定義結構定義。結構定義會以動態方式維護,並於文件新增至索引時更新。一個簡單的結構定義可能如下所示,採用類似 JSON 的格式:

{'comment': ['TEXT'], 'date': ['DATE'], 'author': ['TEXT'], 'count': ['NUMBER']}

字典中的每個鍵值都是文件欄位的名稱。鍵值是與該欄位名稱搭配使用的欄位類型清單。如果您將相同的欄位名稱用於不同的欄位類型,結構定義將會為欄位名稱列出一個以上的欄位類型,如下所示:

{'ambiguous-integer': ['TEXT', 'NUMBER', 'ATOM']}

欄位出現在結構定義中之後,就再也無法移除。即使索引不再包含具有特定欄位名稱的任何文件,依然無法刪除該欄位。

您可以透過如下方式查看索引的結構定義:

Java 8

GetResponse<Index> response =
    SearchServiceFactory.getSearchService()
        .getIndexes(GetIndexesRequest.newBuilder().setSchemaFetched(true).build());

// List out elements of each Schema
for (Index index : response) {
  Schema schema = index.getSchema();
  for (String fieldName : schema.getFieldNames()) {
    List<FieldType> typesForField = schema.getFieldTypes(fieldName);
    // Just printing out the field names and types
    for (FieldType type : typesForField) {
      out.println(index.getName() + ":" + fieldName + ":" + type.name());
    }
  }
}

Java 7

GetResponse<Index> response = SearchServiceFactory.getSearchService().getIndexes(
    GetIndexesRequest.newBuilder().setSchemaFetched(true).build());

// List out elements of each Schema
for (Index index : response) {
  Schema schema = index.getSchema();
  for (String fieldName : schema.getFieldNames()) {
    List<FieldType> typesForField = schema.getFieldTypes(fieldName);
    // Just printing out the field names and types
    for (FieldType type : typesForField) {
      out.println(index.getName() + ":" + fieldName + ":" + type.name());
    }
  }
}
請注意,對 GetIndexes() 的呼叫無法傳回超過 1000 個索引。如要擷取更多索引,請使用 setStartIndexName()GetIndexesRequest.Builder 重複呼叫方法。

結構定義不會以物件程式設計的方式定義「類別」。只要與 Search API 有關,每份文件就不會重複,且索引可以包含不同種類的文件。如果您要將欄位清單相同的物件集合當成類別的執行個體處理,那就必須在您的程式碼中強制執行這樣的概念。例如,您可以確保具有同一組欄位的所有文件都保存在各自的索引中。索引結構定義可以視為類別定義,而索引中的每份文件都會是類別的執行個體。

在 Google Cloud Platform 主控台中查看索引

在 GCP 主控台中,您可以查看應用程式索引及該索引所含文件的相關資訊。按一下索引名稱,即會顯示索引包含的文件。您可以看到該索引的所有已定義的結構定義欄位;如果文件中有一個欄位使用該名稱,您就會看見欄位的值。您也可以直接從主控台對索引資料發出查詢。

Search API 配額

Search API 提供多項免費配額:

資源或 API 呼叫 免費配額
總儲存空間 (文件與索引) 0.25 GB
查詢 每日 1000 次查詢
新增文件至索引 每日 0.01 GB

為確保服務可靠性,Search API 有下列限制。這些限制同時適用於免費與付費應用程式:

資源 安全配額
配額使用量上限 每分鐘的匯總查詢執行時間上限為 100 分鐘
新增或刪除的文件數量上限 每分鐘 15,000 個
每個索引的大小上限 (允許的索引數不限) 10 GB

根據呼叫類型而定,API 使用量會以不同的方式計算:

  • Index.search():每次 API 呼叫都會計為單次查詢;執行時間等於呼叫的延遲時間。
  • Index.put():將文件新增至索引時,每份文件的大小與文件數量都會計入索引配額。
  • 其他所有 Search API 呼叫都會根據呼叫涉及的作業數量計算:
    • SearchService.getIndexes():實際傳回的每個索引會計為一次作業;如果沒有傳回索引,也會計為一次作業。
    • Index.get()Index.getRange():實際傳回的每份文件會計為一次作業;如果沒有傳回文件,也會計為一次作業。
    • Index.delete():要求中的每份文件會計為一次作業;如果要求為空,也會計為一次作業。

系統設下了查詢總處理量的配額,如此一來,單一使用者就無法獨佔搜尋服務。由於查詢可以同時執行,因此在實際的每一分鐘裡,每個應用程式所能執行的查詢最多只能耗用 100 分鐘的執行時間。如果您正在執行許多短查詢,可能不會達到這個限制。當您超出配額時,在到達下一個時間配量而使您的配額復原之前,後續查詢將會失敗。配額不會在一分鐘的配量中嚴格執行;系統會使用經過變化的漏桶演算法,控制以五秒鐘為單位遞增的搜尋頻寬。

如要進一步瞭解配額,請參閱配額頁面。如果應用程式嘗試超出這些額度,系統會傳回配額不足的錯誤。

請注意,雖然上述限制均以分鐘為計算單位,不過主控台中顯示的是各項限制的每日總額。客戶如有選用白銀級、爍金級或白金級支援服務,可與支援代表聯絡,申請提高總處理量限制。

Search API 計價方式

如果您啟用應用程式的計費功能,則必須支付超出免費配額以外的額外用量費用。以下是適用於付費應用程式的費用:

資源 費用
總儲存空間 (文件與索引) 每月每 GB $0.18 美元
查詢 每 10 萬筆查詢 $ 0.50 美元
將可供搜尋的文件編入索引 每 GB $2.00 美元

如要進一步瞭解定價資訊,請參閱定價頁面。

本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Java 適用的 App Engine 標準環境