API Search per servizi in bundle legacy

L'API Search fornisce un modello per indicizzare i documenti che contengono dati strutturati. Puoi cercare in un indice, organizzare e presentare i risultati di ricerca. L'API supporta la corrispondenza completa del testo nei campi stringa. I documenti e gli indici vengono salvati in un archivio permanente separato, ottimizzato per le operazioni di ricerca. L'API Search può indicizzare un numero qualsiasi di documenti. App Engine Datastore può essere più appropriato per le applicazioni che devono recuperare set di risultati molto grandi. Per visualizzare i contenuti del pacchetto search, consulta il riferimento al pacchetto search.

Panoramica

L'API Search si basa su quattro concetti principali: documenti, indici, query e risultati.

Documenti

Un documento è un oggetto con un ID univoco e un elenco di campi contenenti dati utente. Ogni campo ha un nome e un tipo. Esistono diversi tipi di campi, identificati in base ai tipi di valori che contengono:

  • Atom Field: una stringa di caratteri indivisibili.
  • Campo di testo: una stringa di testo normale in cui è possibile eseguire ricerche parola per parola.
  • Campo HTML: una stringa che contiene tag di markup HTML, in cui è possibile cercare solo il testo al di fuori di questi tag.
  • Campo numerico: un numero con rappresentazione in virgola mobile.
  • Campo temporale: un valore time.Time memorizzato con una precisione di millisecondi.
  • Campo Geopoint: un oggetto dati con coordinate di latitudine e longitudine.

La dimensione massima di un documento è 1 MB.

Indici

Un indice archivia i documenti per recuperarli. Puoi recuperare un singolo documento in base al suo ID, a un intervallo di documenti con ID consecutivi o a tutti i documenti in un indice. Puoi anche cercare in un indice per recuperare i documenti che soddisfano determinati criteri sui campi e sui relativi valori, specificati come stringa di query. Puoi gestire gruppi di documenti inserendoli in indici separati.

Non c'è limite al numero di documenti in un indice o al numero di indici che puoi utilizzare. La dimensione totale di tutti i documenti in un singolo indice è limitata a 10 GB per impostazione predefinita. Gli utenti con il ruolo Amministratore App Engine possono inviare una richiesta dalla pagina Ricerca di App Engine della console Google Cloud per aumentare le dimensioni fino a 200 GB.

Query

Per cercare in un indice, crea una query che abbia una stringa di query e possibilmente alcune opzioni aggiuntive. Una stringa di query specifica le condizioni per i valori di uno o più campi documento. Quando cerchi in un indice, vengono visualizzati solo i documenti al suo interno con campi che soddisfano la query.

La query più semplice, a volte chiamata "ricerca globale", è una stringa che contiene solo valori dei campi. Questa ricerca utilizza una stringa che cerca documenti che contengono le parole "rosa" e "acqua":

index.Search(ctx, "rose water", nil)

Questa cerca i documenti con campi di data che contengono la data 4 luglio 1776 o campi di testo che includono la stringa "1776-07-04":

index.Search(ctx, "1776-07-04", nil)

Una stringa di query può anche essere più specifica. Può contenere uno o più termini, ciascuno dei quali assegna un nome a un campo e un vincolo sul valore del campo. La forma esatta di un termine dipende dal tipo di campo. Ad esempio, supponendo che esista un campo di testo denominato "Prodotto" e un campo numerico denominato "Prezzo", ecco una stringa di query con due termini:

// search for documents with pianos that cost less than $5000
index.Search(ctx, "Product = piano AND Price < 5000", nil)

Le opzioni di query, come suggerisce il nome, non sono obbligatorie. Attivano una serie di funzionalità:

  • Controlla quanti documenti vengono restituiti nei risultati di ricerca.
  • Specifica quali campi del documento includere nei risultati. Per impostazione predefinita vengono inclusi tutti i campi del documento originale. Puoi specificare che i risultati includono solo un sottoinsieme di campi (il documento originale non è interessato).
  • Ordina i risultati.
  • Crea "campi calcolati" per i documenti utilizzando FieldExpressions e campi di testo ridotto utilizzando gli snippet.
  • Supporta la navigazione nelle pagine dei risultati di ricerca restituendo solo una parte dei documenti corrispondenti per ogni query (utilizzando offset e cursori)

Ti consigliamo di registrare le stringhe di query nell'applicazione se vuoi conservare un registro delle query eseguite.

Risultati di ricerca

Una chiamata Search restituisce un valore Iterator che può essere utilizzato per restituire l'insieme completo di documenti corrispondenti.

Materiale di formazione aggiuntivo

Oltre a questa documentazione, puoi leggere il corso di formazione in due parti sulla Search API nella Google Developer's Academy. Anche se la classe utilizza l'API Python, potresti trovare utile l'ulteriore discussione dei concetti di ricerca.

Documenti e campi

I documenti sono rappresentati da struct Go, comprendenti un elenco di campi. I documenti possono anche essere rappresentati da qualsiasi tipo che implementa l'interfaccia di FieldLoadSaver.

Identificatore documento

Ogni documento in un indice deve avere un identificatore di documento univoco o docID. L'identificatore può essere utilizzato per recuperare un documento da un indice senza eseguire una ricerca. Per impostazione predefinita, l'API Search genera automaticamente un docID quando viene creato un documento. Puoi anche specificare il docID quando crei un documento. Un elemento docID deve contenere solo caratteri ASCII stampabili visibili (codici ASCII da 33 a 126 inclusi) e non superare i 500 caratteri. Un identificatore documento non può iniziare con un punto esclamativo ('!') e non può iniziare e terminare con due trattini bassi ("__").

Sebbene sia pratico creare identificatori di documenti univoci leggibili e significativi, non puoi includere docID in una ricerca. Considera questo scenario: hai un indice con documenti che rappresentano parti e che utilizza il numero di serie della parte come docID. Sarà molto efficiente recuperare il documento per ogni singola parte, ma sarà impossibile cercare un intervallo di numeri di serie insieme ad altri valori di campo, ad esempio la data di acquisto. Memorizzare il numero di serie in un campo atomico risolve il problema.

Campi documento

Un documento contiene campi che hanno un nome, un tipo e un singolo valore di quel tipo. Due o più campi possono avere lo stesso nome, ma tipi diversi. Ad esempio, puoi definire due campi con il nome "age": uno con un tipo di testo (il valore "ventidue") e l'altro con un tipo di numero (valore 22).

Nomi dei campi

I nomi dei campi sono sensibili alle maiuscole e possono contenere solo caratteri ASCII. Devono iniziare con una lettera e possono contenere lettere, numeri o trattini bassi. Il nome di un campo non può superare i 500 caratteri.

Campi a più valori

Un campo può contenere un solo valore, che deve corrispondere al tipo di campo. I nomi dei campi non devono essere univoci. Un documento può avere più campi con lo stesso nome e lo stesso tipo, in modo da rappresentare un campo con più valori. Tuttavia, non è possibile ripetere i campi di data e numeri con lo stesso nome. Un documento può anche contenere più campi con lo stesso nome e tipi di campo diversi.

Tipi di campo

Esistono tre tipi di campi in cui vengono memorizzate stringhe di caratteri; li definiamo collettivamente campi di stringhe:

  • Campo di testo: una stringa di lunghezza massima 1024**2 caratteri.
  • Campo HTML: una stringa in formato HTML con lunghezza massima 1024**2 caratteri.
  • Campo Atom: una stringa con lunghezza massima di 500 caratteri.

Esistono inoltre tre tipi di campi in cui vengono archiviati i dati non testuali:

  • Campo numerico: un valore in virgola mobile a precisione doppia compreso tra -2.147.483.647 e 2.147.483.647.
  • Campo temporale: un valore time.Time memorizzato con una precisione di millisecondi.
  • Campo Geopoint: un punto sulla Terra descritto dalle coordinate di latitudine e longitudine.

I tipi di campo stringa sono il tipo integrato string di Go e i tipi HTML e Atom del pacchetto search. I campi numerici sono rappresentati con il tipo float64 integrato di Go, i campi temporali utilizzano il tipo time.Time e i campi di geolocalizzazione utilizzano il tipo GeoPoint del pacchetto appengine.

Trattamento speciale dei campi stringa e temporali

Quando un documento contenente campi relativi a ora, testo o HTML viene aggiunto a un indice, ha luogo una gestione speciale. È utile capire cosa sta succedendo "indietro" per utilizzare l'API Search in modo efficace.

Campi stringa di tokenizzazione

Quando un campo di testo o HTML viene indicizzato, i relativi contenuti vengono tokenizzati. La stringa viene suddivisa in token ogni volta che compaiono spazi vuoti o caratteri speciali (segni di punteggiatura, cancelletto, barra rovesciata e così via). L'indice includerà una voce per ogni token. Ciò ti consente di cercare parole chiave e frasi che comprendono solo una parte del valore di un campo. Ad esempio, la ricerca di "scuro" troverà un documento con un campo di testo contenente la stringa "è stata una notte buia e tempestosa", mentre la ricerca "tempo" troverà un documento con un campo di testo contenente la stringa "questo è un sistema in tempo reale".

Nei campi HTML, il testo all'interno dei tag di markup non è tokenizzato, pertanto un documento con un campo HTML contenente it was a <strong>dark</strong> night corrisponderà a una ricerca per "night", ma non per "strong". Se vuoi cercare il testo del markup, archivialo in un campo di testo.

I campi Atom non sono tokenizzati. Un documento con un campo Atom contenente il valore "maltempo" corrisponderà solo alla ricerca dell'intera stringa "maltempo". e non corrisponde alla sola ricerca di "pessimo" o "meteo".

Regole di tokenizzazione
  • Il trattino basso (_) e il carattere e commerciale (&) non separano le parole in token.

  • Questi spazi vuoti suddividono sempre le parole in token: spazio, ritorno a capo, avanzamento riga, tabulazione orizzontale, tabulazione verticale, feed modulo e NULL.

  • Questi caratteri vengono trattati come punteggiatura e suddivideranno le parole in token:

    !"%()
    *,-|/
    []]^`
    :=>?@
    {}~$
  • I caratteri nella seguente tabella di solito suddividono le parole in token, ma possono essere gestiti in modo diverso a seconda del contesto in cui appaiono:

    Basato su caratteri Regola
    < In un campo HTML, il simbolo "minore di" indica l'inizio di un tag HTML che viene ignorato.
    + Una stringa di uno o più segni "più" viene trattata come parte della parola se si trova alla fine della parola (C++).
    # Il segno "hash" viene trattato come parte della parola se è preceduto da a, b, c, d, e, f, g, j o x (a# - g# sono note musicali; j# e x# sono un linguaggio di programmazione, c# è entrambi). Se un termine è preceduto da "#" (#google), viene trattato come un hashtag e l'hash diventa parte della parola.
    ' L'apostrofo è una lettera se precede la lettera "s" seguita da un'interruzione di parola, come in "cappello di John".
    . Se tra le cifre c'è un punto decimale, è parte di un numero (ovvero il separatore decimale). Può anche far parte di una parola se utilizzata in un acronimo (A.B.C).
    - Il trattino fa parte di una parola se utilizzato in un acronimo (I-B-M).
  • Tutti gli altri caratteri a 7 bit diversi dalle lettere e dai numeri ("A-Z", "a-z", "0-9") vengono gestiti come punteggiatura e suddividono le parole in token.

  • Tutti gli altri vengono analizzati come caratteri UTF-8.

Acronimi

La tokenizzazione utilizza regole speciali per il riconoscimento degli acronimi (stringhe come "I.B.M.", "a-b-c" o "C I A"). Un acronimo è una stringa composta da singoli caratteri alfabetici, con lo stesso separatore tra tutti e due. I separatori validi sono il punto, il trattino o qualsiasi numero di spazi. Il carattere separatore viene rimosso dalla stringa quando viene tokenizzato un acronimo. Quindi le stringhe di esempio menzionate sopra diventano i token "ibm", "abc" e "cia". Il testo originale rimane nel campo del documento.

Quando tratti gli acronimi, tieni presente che:

  • Un acronimo non può contenere più di 21 lettere. Una stringa di un acronimo valida con più di 21 lettere verrà suddivisa in una serie di acronimi di massimo 21 lettere.
  • Se le lettere di un acronimo sono separate da spazi, tutte le lettere devono avere lo stesso carattere. Gli acronimi creati con punto e trattino possono utilizzare lettere maiuscole e minuscole miste.
  • Quando cerchi un acronimo, puoi inserire la forma canonica dell'acronimo (la stringa senza separatori) o l'acronimo preceduto dal trattino o dal punto (ma non entrambi) tra le lettere. Il testo "I.B.M" può essere recuperato con uno qualsiasi dei termini di ricerca: "I-B-M", "I.B.M" o "IBM".

Precisione del campo temporale

Quando crei un campo temporale in un documento, ne imposti il valore su time.Time. Ai fini dell'indicizzazione e della ricerca del campo temporale, qualsiasi componente orario viene ignorato e la data viene convertita nel numero di giorni a partire dall'1/01/1970 UTC. Ciò significa che, anche se un campo temporale può contenere un valore temporale preciso, una query sulla data può specificare solo un valore del campo temporale nel formato yyyy-mm-dd. Ciò significa anche che l'ordine dei campi temporali con la stessa data non è ben definito. Mentre il tipo time.Time rappresenta il tempo con una precisione nei nanosecondi, l'API Search li archivia con una precisione di soli millisecondi.

Altre proprietà del documento

Il ranking di un documento è un numero intero positivo che determina l'ordine predefinito dei documenti restituiti da una ricerca. Per impostazione predefinita, il ranking è impostato al momento della creazione del documento sul numero di secondi dal 1° gennaio 2011. Puoi impostare esplicitamente il ranking quando crei un documento. È una cattiva idea assegnare lo stesso ranking a molti documenti e non dovresti mai assegnare lo stesso ranking a più di 10.000 documenti. Se specifichi le opzioni di ordinamento, puoi utilizzare il ranking come chiave di ordinamento. Tieni presente che quando il ranking viene utilizzato in un'espressione di ordinamento o un'espressione di campo, viene fatto riferimento come _rank. Consulta il riferimento DocumentMetadata per ulteriori informazioni sull'impostazione del ranking.

La proprietà Language dello struct Field specifica la lingua in cui viene codificato il campo.

Collegamento da un documento ad altre risorse

Puoi utilizzare l'elemento docID di un documento e altri campi come link ad altre risorse nell'applicazione. Ad esempio, se utilizzi Blobstore, puoi associare il documento a un BLOB specifico impostando docID o il valore di un campo Atom sulla BlobKey dei dati.

Creazione di un documento

Il seguente esempio di codice mostra come creare un oggetto documento. Il tipo User specifica la struttura del documento e viene creato un valore User nel modo consueto.

import (
	"fmt"
	"net/http"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/search"
)

type User struct {
	Name      string
	Comment   search.HTML
	Visits    float64
	LastVisit time.Time
	Birthday  time.Time
}

func putHandler(w http.ResponseWriter, r *http.Request) {
	id := "PA6-5000"
	user := &User{
		Name:      "Joe Jackson",
		Comment:   "this is <em>marked up</em> text",
		Visits:    7,
		LastVisit: time.Now(),
		Birthday:  time.Date(1960, time.June, 19, 0, 0, 0, 0, nil),
	}
	// ...

Utilizzo di un indice

Inserimento di documenti in un indice

Quando inserisci un documento in un indice, il documento viene copiato in uno spazio di archiviazione permanente e ciascuno dei suoi campi viene indicizzato in base al nome, al tipo e a docID.

Il seguente esempio di codice mostra come accedere a un indice e inserirvi un documento.

// ...
ctx := appengine.NewContext(r)
index, err := search.Open("users")
if err != nil {
	http.Error(w, err.Error(), http.StatusInternalServerError)
	return
}
_, err = index.Put(ctx, id, user)
if err != nil {
	http.Error(w, err.Error(), http.StatusInternalServerError)
	return
}
fmt.Fprint(w, "OK")

Quando inserisci un documento in un indice e l'indice contiene già un documento con lo stesso docID, il nuovo documento sostituisce quello precedente. Non viene fornito nessun avviso. Puoi chiamare Index.Get prima di creare o aggiungere un documento a un indice per verificare se esiste già una docID specifica.

Il metodo Put restituisce un docID. Se non hai specificato tu docID, puoi esaminare il risultato per scoprire l'elemento docID che è stato generato:

id, err = index.Put(ctx, "", user)
if err != nil {
	http.Error(w, err.Error(), http.StatusInternalServerError)
	return
}
fmt.Fprint(w, id)

Tieni presente che la creazione di un'istanza di tipo Index non garantisce l'effettiva esistenza di un indice permanente. Un indice permanente viene creato la prima volta che vi aggiungi un documento con il metodo put.

Aggiornamento dei documenti

Un documento non può essere modificato dopo averlo aggiunto a un indice. Non puoi aggiungere o rimuovere campi, né modificare il valore di un campo. Tuttavia, puoi sostituire il documento con un nuovo documento che abbia lo stesso docID.

Recupero di documenti per docID

Utilizza il metodo Index.Get per recuperare un documento da un indice in base al suo docID:

func getHandler(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	index, err := search.Open("users")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	id := "PA6-5000"
	var user User
	if err := index.Get(ctx, id, &user); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fmt.Fprint(w, "Retrieved document: ", user)
}

Cercare documenti in base ai contenuti

Per recuperare i documenti da un indice, crea una stringa di query e chiama Index.Search. Search restituisce un iteratore che restituisce i documenti corrispondenti in ordine decrescente.

func searchHandler(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	index, err := search.Open("myIndex")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	for t := index.Search(ctx, "Product: piano AND Price < 5000", nil); ; {
		var doc Doc
		id, err := t.Next(&doc)
		if err == search.Done {
			break
		}
		if err != nil {
			fmt.Fprintf(w, "Search error: %v\n", err)
			break
		}
		fmt.Fprintf(w, "%s -> %#v\n", id, doc)
	}
}

Eliminazione di un indice

Ogni indice è costituito dai relativi documenti indicizzati e da uno schema dell'indice. Per eliminare un indice, elimina tutti i documenti al suo interno, quindi elimina lo schema dell'indice.

Puoi eliminare i documenti in un indice specificando il docID del documento che vuoi eliminare con il metodo Index.Delete.

func deleteHandler(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)

	index, err := search.Open("users")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	id := "PA6-5000"
	err = index.Delete(ctx, id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	fmt.Fprint(w, "Deleted document: ", id)
}

Coerenza finale

Quando inserisci, aggiorni o elimini un documento in un indice, la modifica si propaga in più data center. In genere questo avviene rapidamente, ma il tempo necessario può variare. L'API Search garantisce l'eventuale coerenza. Ciò significa che, in alcuni casi, una ricerca o il recupero di uno o più documenti potrebbero restituire risultati che non riflettono le modifiche più recenti.

Schemi di indice

Ogni indice ha uno schema che mostra tutti i nomi e i tipi di campo riportati nei documenti che contiene. Non puoi definire uno schema autonomamente. Gli schemi sono gestiti in modo dinamico e vengono aggiornati non appena i documenti vengono aggiunti a un indice. Uno schema semplice potrebbe avere il seguente aspetto, in formato JSON:

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

Ogni chiave del dizionario è il nome di un campo documento. Il valore della chiave è un elenco dei tipi di campo utilizzati con il nome di quel campo. Se hai utilizzato lo stesso nome con diversi tipi di campo, lo schema elencherà più di un tipo per un nome di campo, come riportato di seguito:

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

Una volta visualizzato in uno schema, un campo non può più essere rimosso. Non è possibile eliminare un campo, anche se l'indice non contiene più documenti con quel particolare nome di campo.

Uno schema non definisce una "classe" nel senso della programmazione di oggetti. Per quanto riguarda l'API Search, ogni documento è univoco e gli indici possono contenere diversi tipi di documenti. Se vuoi trattare le raccolte di oggetti con lo stesso elenco di campi come istanze di una classe, questa è un'astrazione che devi applicare al tuo codice. Ad esempio, puoi assicurarti che tutti i documenti con lo stesso insieme di campi vengano mantenuti nel proprio indice. Lo schema dell'indice potrebbe essere visto come la definizione della classe e ogni documento nell'indice sarebbe un'istanza della classe.

Visualizzazione degli indici nella console Google Cloud

Nella console Google Cloud, puoi visualizzare informazioni sugli indici delle tue applicazioni e sui documenti che contengono. Se fai clic sul nome di un indice, vengono visualizzati i documenti contenuti nell'indice. Vedrai tutti i campi dello schema definiti per l'indice; per ogni documento con un campo di quel nome, vedrai il valore del campo. Puoi anche eseguire query sui dati di indice direttamente dalla console.

Quote dell'API Search

L'API Search offre diverse quote gratuite:

Risorsa o chiamata API Quota gratuita
Capacità di archiviazione totale (documenti e indici) 0,25 GB
Query 1000 query al giorno
Aggiunta di documenti agli indici 0,01 GB al giorno

L'API Search impone questi limiti per garantire l'affidabilità del servizio. Queste norme si applicano sia alle app gratuite che a quelle a pagamento:

Risorsa Quota di sicurezza
Utilizzo massimo delle query 100 minuti aggregati di tempo di esecuzione delle query al minuto
Numero massimo di documenti aggiunti o eliminati 15.000 al minuto
Dimensioni massime per indice (numero illimitato di indici consentiti) 10 GB

L'utilizzo dell'API viene conteggiato in modi diversi a seconda del tipo di chiamata:

  • Index.Search: ogni chiamata API viene conteggiata come una query; il tempo di esecuzione è equivalente alla latenza della chiamata.
  • Index.Put: quando aggiungi documenti all'indicizzazione, le dimensioni di ciascun documento e il numero di documenti vengono conteggiati ai fini del calcolo della quota di indicizzazione.
  • Tutte le altre chiamate all'API Search vengono conteggiate in base al numero di operazioni che comportano:
    • Index.Get: 1 operazione conteggiata per ogni documento effettivamente restituito o 1 operazione se non viene restituito nulla.
    • Index.Delete: 1 operazione conteggiata per ogni documento nella richiesta o 1 operazione se la richiesta è vuota.

La quota per la velocità effettiva delle query è imposta in modo che un singolo utente non possa monopolizzare il servizio di ricerca. Poiché le query possono essere eseguite contemporaneamente, ogni applicazione può eseguire query che consumano fino a 100 minuti di tempo di esecuzione per un minuto di tempo. Se esegui molte query brevi, probabilmente non raggiungerai questo limite. Una volta superata la quota, le query successive non andranno a buon fine fino alla sezione temporale successiva, quando la quota verrà ripristinata. La quota non è imposta in modo rigoroso in sezioni di un minuto; viene utilizzata una variante dell'algoritmo del bucket che presenta perdite per controllare la larghezza di banda di ricerca in incrementi di cinque secondi.

Per ulteriori informazioni sulle quote, consulta la pagina Quote. Quando un'app cerca di superare questi importi, viene restituito un errore di quota insufficiente.

Tieni presente che, sebbene questi limiti vengano applicati al minuto, la console mostra i totali giornalieri per ciascuno. I clienti con assistenza Silver, Gold o Platinum possono richiedere limiti di velocità effettiva più elevati contattando il proprio rappresentante dell'assistenza.

Prezzi dell'API Search

Per l'utilizzo che supera le quote gratuite vengono applicati i seguenti costi:

Risorsa Costo
Capacità di archiviazione totale (documenti e indici) 0,18 $ per GB al mese
Query 0,50 $ per 10.000 query
Indicizzazione dei documenti disponibili per la ricerca 2,00 $ per GB

Ulteriori informazioni sui prezzi sono disponibili nella pagina Prezzi.