Na API Search, há um modelo para a indexação de documentos contendo dados estruturados. É possível pesquisar um índice, organizar e apresentar os resultados da pesquisa. A API é compatível com correspondência de texto completa em campos de string. Documentos e índices são salvos em um armazenamento permanente separado e otimizado para operações de pesquisa. Qualquer número de documentos pode ser indexado na API Search. O App Engine Datastore pode ser mais apropriado para aplicativos que precisam recuperar grupos de resultados muito grandes.
Visão geral
A API Search é baseada em quatro conceitos principais: documentos, índices, consultas e resultados.
Documentos
Um documento é um objeto com um código exclusivo e uma lista de campos contendo dados do usuário. Cada campo tem um nome e um tipo. Existem vários tipos de campos, identificados pelos tipos de valores que contêm:
- Campo atômico: uma string de caracteres indivisível.
- Campo de texto: uma string de texto simples que pode ser pesquisada por palavra.
- Campo HTML: uma string de caracteres que contém tags de marcação HTML. Somente o texto fora das tags de marcação pode ser pesquisado.
- Campo numérico: um número de ponto flutuante.
- Campo de data: um objeto de data.
- Campo de Geopoint: um objeto de dados com coordenadas de latitude e longitude.
O tamanho máximo de um documento é 1 MB.
Índices
Um índice armazena documentos para recuperação. É possível recuperar um documento pelo código, uma série de documentos com códigos consecutivos ou todos os documentos em um índice. Também é possível pesquisar um índice para recuperar documentos que atendam a determinados critérios nos campos e valores correspondentes, especificados como uma string de consulta. Também é possível gerenciar grupos de documentos colocando-os em índices separados.
Não há limite para a quantidade de documentos em um índice ou o número de índices em uso. O tamanho total de todos os documentos em um único índice é limitado a 10 GB por padrão. Aqueles com o papel Administrador do App Engine podem enviar uma solicitação pela página do App Engine Search no console do Google Cloud para aumentar o tamanho para até 200 GB.
Consultas
A fim de pesquisar um índice, você constrói uma consulta contendo uma string de consulta e talvez opções adicionais. Na string de consulta, são especificadas as condições para os valores de um ou mais campos do documento. Ao pesquisar um índice, você recupera apenas os documentos com os campos que atendem à consulta.
A consulta mais simples, às vezes chamada de "pesquisa global", é uma string que contém apenas valores de campo. Nessa pesquisa, uma string é usada para localizar documentos que contenham as palavras "rose" e "water":
A string a seguir é usada para pesquisar documentos com campos de data que contenham "4 de julho de 1776" ou campos de texto que incluam a string "1776-07-04":
Uma string de consulta também pode ser mais específica. Ela pode conter um ou mais termos, cada um nomeando um campo e uma restrição sobre o valor dele. A forma exata de um termo depende do tipo de campo. Por exemplo, supondo que exista um campo de texto chamado "Produto" e um campo numérico chamado "Preço", aqui está uma string de consulta com dois termos:
As opções de consulta, como o nome indica, não são necessárias. Com elas, é possível usar diversos recursos:
- Controlar quantos documentos são retornados nos resultados da pesquisa.
- Especificar quais campos de documento incluir nos resultados. O padrão é incluir todos os campos do documento original. É possível definir que os resultados incluam apenas um subgrupo de campos, sem que o documento original seja afetado.
- Classificar os resultados.
- Criar "campos calculados" para documentos usando
FieldExpressions
e campos de texto abreviados usando snippets. - Dar suporte à paginação por meio dos resultados da pesquisa ao retornar apenas uma parte dos documentos correspondentes em cada consulta, usando deslocamentos e cursores.
Recomendamos que você registre strings de consulta no aplicativo se quiser manter um registro das consultas que foram executadas.
Resultados da pesquisa
Uma chamada parasearch()
só pode retornar um número limitado de documentos correspondentes.
A pesquisa pode encontrar mais documentos do que podem ser retornados em uma única chamada. Cada
chamada de pesquisa retorna uma instância da classe
Results
,
que contém informações sobre quantos documentos foram encontrados e quantos
foram retornados, além da lista de documentos retornados. Repita
a mesma pesquisa usando
cursores
ou deslocamentos
para recuperar o conjunto completo de documentos correspondentes.
Material de treinamento adicional
Além desta documentação, leia a aula de treinamento em duas partes sobre a API Search na Google Developer's Academy. A API Python é usada na aula, mas talvez você considere útil discutir sobre os conceitos de pesquisa.
Documentos e campos
A classe Document representa documentos. Cada documento tem um identificador e uma lista de campos.Identificador do documento
Todo documento em um índice precisa ter um identificador exclusivo ou doc_id
.
O identificador pode ser usado para recuperar um documento de um índice sem realizar uma pesquisa. Por padrão, a API Search gera automaticamente um doc_id
quando um documento é criado. É possível definir o doc_id
ao criar um documento. O doc_id
pode conter apenas caracteres ASCII visíveis e imprimíveis (códigos ASCII 33 a 126 inclusos) e não pode ter mais de 500 caracteres. O identificador de documento não pode começar com um ponto de exclamação ('!'), e não pode começar e terminar com sublinhados duplos ("__").
É conveniente criar identificadores de documentos exclusivos, legíveis e significativos, mas não é possível incluir o doc_id
em uma pesquisa. Considere esta situação: você tem um índice com documentos que representam peças, usando o número de série da peça como doc_id
. Será muito eficiente recuperar o documento de uma peça única, mas será impossível pesquisar por uma faixa de vários números de série junto com outros valores de campo, como data de compra. Armazenar o número de série em um campo atômico resolve o problema.
Campos do documento
Um documento contém campos com um nome, um tipo e um único valor desse tipo. Dois ou mais campos podem ter o mesmo nome, mas tipos diferentes. Por exemplo, é possível definir dois campos com o nome "idade": um com tipo texto (o valor "vinte e dois") e outro com o tipo número (valor 22).
Nomes de campos
Os nomes dos campos diferenciam maiúsculas de minúsculas e só podem conter caracteres ASCII. Eles precisam começar com uma letra e podem conter letras, números ou sublinhados. O nome do campo não pode ter mais de 500 caracteres.
Campos com vários valores
Um campo pode conter apenas um valor, que precisa corresponder ao tipo do campo. Os nomes dos campos não precisam ser exclusivos. Um documento pode ter vários campos com o mesmo nome e o mesmo tipo. Essa é uma maneira de representar um campo com vários valores. No entanto, os campos de data e número com o mesmo nome não podem ser repetidos. Um documento também pode conter vários campos com o mesmo nome e diferentes tipos.
Tipos de campo
Existem três tipos de campos para armazenar java.lang.String
strings de caracteres. Coletivamente, nos referimos a eles como campos de string:
- Campo de texto: uma string com comprimento máximo de 1024**2 caracteres.
- Campo HTML: uma string formatada em HTML com comprimento máximo de 1024**2 caracteres.
- Campo atômico: uma string com comprimento máximo de 500 caracteres.
Existem também três tipos de campos para armazenar dados não-textuais:
- Campo numérico: um ponto flutuante de dupla precisão entre -2.147.483.647 e 2.147.483.647.
- Campo de data: um
java.util.Date
- Campo de Geopoint: um ponto na Terra descrito em coordenadas de latitude e longitude.
Os tipos de campo são especificados usando as enumerações Field.FieldType
TEXT
, HTML
, ATOM
, NUMBER
, DATE
e GEO_POINT
.
Tratamento especial de campos de string e data
Quando um documento com campos de data, texto ou HTML é adicionado a um índice, ocorre um tratamento especial. É útil entender o que está acontecendo nos bastidores para usar a API Search de forma eficaz.
Tokenização de campos de string
Quando um campo de HTML ou texto é indexado, o conteúdo é tokenizado. A string é dividida em tokens onde houver espaços em branco ou caracteres especiais, como sinais de pontuação, barra invertida etc. O índice inclui uma entrada para cada token. Isso permite pesquisar palavras-chave e frases que compõem apenas parte do valor de um campo. Por exemplo, em uma pesquisa por "noite", um documento com um campo de texto contendo a string "era uma noite escura e tormentosa" será retornado. Em uma pesquisa por "tempo", um documento com um campo de texto contendo a string "isto é um sistema em tempo real" será retornado.
Em campos HTML, o texto dentro das tags de marcação não é tokenizado. Portanto, um documento com um campo HTML que contém it was a <strong>dark</strong> night
será retornado para "night", mas não para "strong". Para pesquisar um texto de marcação, armazene-o em um campo de texto.
Os campos atômicos não são tokenizados. Um documento com um campo atômico que contenha o valor "tempo ruim" só será retornado em uma pesquisa para toda a string "tempo ruim". Ele não será retornado em uma pesquisa pelos termos "tempo" ou "ruim" separados.
Regras de tokenização
As palavras não são divididas em tokens pelos caracteres e comercial (&) e sublinhado (_).
As palavras são sempre divididas em tokens pelos seguintes caracteres de espaço em branco: espaço, retorno de carro, avanço de linha, tabulação horizontal, tabulação vertical, avanço de formulário e NULL.
As palavras são divididas em tokens pelos caracteres a seguir, que são tratados como sinais de pontuação:
! " % ( ) * , - | / [ ] ] ^ ` : = > ? @ { } ~ $ Geralmente, as palavras são divididas em tokens pelos caracteres a seguir, mas eles podem ser manipulados de forma diferente dependendo do contexto em que aparecem:
Caractere Regra <
Em um campo HTML, o sinal "menor que" indica o início de uma tag HTML, que é ignorada. +
Uma string de um ou mais sinais de adição é tratada como parte da palavra se aparecer no final dela (C++). #
O sinal de jogo da velha é tratado como parte da palavra se for precedido de a, b, c, d, e, f, g, j ou x (a# - g# são notas musicais, j# e x# são linguagens de programação e c# é as duas coisas). Se um termo é precedido por '#' (#google), ele é tratado como uma hashtag, e o sinal de jogo da velha se torna parte da palavra. '
O apóstrofo é uma letra se preceder a letra "s" seguida de uma quebra de palavra, como em "John's hat". .
Se um ponto decimal aparecer entre dígitos, ele faz parte de um número, ou seja, é o separador de decimais. Ele também pode ser parte de uma palavra se usado em uma sigla (A.B.C). -
O traço é parte de uma palavra se usado em uma sigla (I-B-M). As palavras são divididas em tokens pelos outros caracteres de 7 bits que não sejam letras e dígitos ("A-Z", "a-z", "0-9"). Eles são tratados como sinais de pontuação.
Todo o resto é analisado como um caractere UTF-8.
Siglas
A tokenização tem regras especiais para reconhecer as siglas. Por exemplo, strings como "I.B.M.", "a-b-c" ou "C.I.A". Uma sigla é uma string de caracteres alfabéticos individuais com o mesmo caractere separador entre todos eles. Os separadores válidos são o ponto, o traço ou qualquer número de espaços. O caractere separador é removido da string quando a sigla é tokenizada. Assim, as strings de exemplo mencionadas acima tornam-se os tokens "ibm", "abc" e "cia". O texto original permanece no campo do documento.
Ao lidar com siglas, observe que:
- Uma sigla não pode conter mais de 21 letras. Uma string de sigla válida com mais de 21 letras será dividida em uma série de siglas, cada uma com até 21 letras.
- Se as letras de uma sigla forem separadas por espaços, todas precisarão ter a mesma capitalização. As siglas formadas com pontos e traços podem usar letras maiúsculas e minúsculas.
- Ao pesquisar uma sigla, insira a forma canônica dela (a string sem separadores) ou a sigla pontuada com o traço ou o ponto (mas não ambos) entre as letras. Portanto, o texto "IBM" pode ser recuperado com qualquer um dos termos de pesquisa: "I-B-M", "I.B.M" ou "IBM".
Precisão do campo de data
Ao criar um campo de data em um documento, você define seu valor como java.util.Date
.
Para fins de indexação e pesquisa do
campo de data, qualquer componente de horário é
ignorado e a data é convertida no número de dias desde 1/1/1970 UTC. Isso
significa que, mesmo que um campo de data
possa conter um valor de hora preciso, uma consulta de data só pode especificar um
valor de campo de data no formulário
yyyy-mm-dd
. Também significa que a ordem de classificação dos campos de data com a mesma data não está bem definida.
Outras propriedades do documento
A classificação de um documento é um número inteiro positivo que determina a ordem padrão dos documentos retornados de uma pesquisa. Por padrão, ela é definida no momento em que o documento é criado, pelo número de segundos desde 1º de janeiro de 2011. É possível definir a classificação explicitamente ao criar um documento. Não é uma boa ideia atribuir a mesma classificação a muitos documentos. Nunca defina a mesma classificação para mais de 10.000 documentos.
Ao especificar opções de classificação, é possível usar a classificação como uma chave. Quando a classificação é usada em uma expressão de classificação ou de campo, ela é mencionada como _rank
.
A propriedade "locale" especifica a linguagem na qual os campos são programados.
Consulte a página de referência da classe
Document
para mais detalhes sobre esses atributos.
Como vincular de um documento a outros recursos
É possível usar o doc_id
e outros campos de um documento como links para outros recursos do aplicativo. Por exemplo, se você usa o Blobstore, associe o documento a um blob específico. Basta configurar doc_id
ou o valor de um campo atômico como BlobKey dos dados.
Criar documentos
Para criar um documento, solicite um novo builder usando o
método Document.newBuilder()
. Com acesso a um builder, o aplicativo especifica um identificador de documentos opcional e adiciona campos.
Campos, assim como documentos, são criados usando-se um builder. O método Field.newBuilder()
retorna um builder de campo para que você especifique o nome e o valor de um campo. O tipo de campo é especificado automaticamente escolhendo um método específico de configuração. Por exemplo, para indicar que um campo armazena texto simples, chame setText()
.
O código a seguir cria um documento com campos que representam uma saudação do livro de visitas.
Para acessar os campos no documento, use getOnlyField()
:
Como trabalhar com um índice
Como colocar documentos em um índice
Ao colocar um documento em um índice, ele é copiado para o armazenamento permanente, e cada um dos campos é indexado de acordo com nome, tipo e doc_id
.
Veja a seguir um exemplo de código para acessar um índice e colocar um documento nele. Essas etapas são:
- Crie um
IndexSpec
- Crie um
SearchService
- Chame
SearchService.getIndex()
para criar uma instância de índice. - Chame
Index.put()
para adicionar o documento ao índice.
put()
. Fazer isso em lote é mais eficiente do que adicionar documentos um de cada vez.
Quando você coloca um documento em um índice e este já contém um documento com o mesmo doc_id
, o novo documento substitui o anterior. Nenhum aviso é exibido. Chame
Index.get(id)
antes de criar ou adicionar um documento a um índice para verificar se determinado
doc_id
já existe.
Observe que criar uma instância da classe Index
não garante que um índice permanente realmente exista. O índice permanente é criado na primeira vez em que um documento é adicionado a ele usando o método put
.
Se você quiser verificar se um índice realmente existe antes de começar a usá-lo, execute o método SearchService.getIndexes()
.
Atualizar documentos
Um documento não pode ser alterado depois de adicionado a um índice. Não é possível adicionar ou remover campos nem alterar o valor de um campo. No entanto, é possível substituir o documento por outro que tenha o mesmo doc_id
.
Como recuperar documentos por doc_id
Há duas maneiras de recuperar documentos de um índice usando identificadores de documentos:- Use
Index.get()
para buscar um único documento pelodoc_id
. - Use
Index.getRange()
para recuperar um grupo de documentos consecutivos classificados pordoc_id
.
Cada chamada é demonstrada no exemplo abaixo.
Como pesquisar documentos pelo conteúdo
Para recuperar documentos de um índice, crie uma string de consulta e chame o Index.search()
.
É possível transmitir a string de consulta diretamente como argumento ou incluí-la em um objeto Query transmitido como argumento.
Por padrão, search()
retorna documentos
correspondentes classificados em ordem decrescente. Para controlar quantos documentos são retornados, como eles são classificados ou adicionar campos calculados aos resultados, você precisa usar um objeto Query
, que contém uma string de consulta e também pode especificar outras opções de pesquisa e classificação.
Como excluir um índice
Cada índice consiste em documentos indexados e um esquema de índice. Para excluir um índice, exclua todos os documentos inclusos nele e também o esquema dele.
Você pode excluir documentos em um índice especificando o doc_id
de um ou mais documentos que queira excluir para o método delete()
.
Faça a exclusão em lotes para melhorar a eficiência. É possível transmitir até 200 IDs de documento por vez ao método delete()
.
delete()
. A exclusão em lote é mais eficiente do que o processamento individual.
Consistência eventual
Quando você manipula um documento em um índice usando put, update ou delete, a alteração é propagada por vários data centers. Normalmente esse processo é rápido, mas o tempo pode variar. Uma consistência eventual (link em inglês) é garantida pela API Search. Isso significa que, em alguns casos, uma pesquisa ou a recuperação de documentos pode retornar resultados que não refletem as alterações mais recentes.
Como determinar o tamanho de um índice
Um índice armazena documentos para recuperação. É possível recuperar um documento pelo código, uma série de documentos com códigos consecutivos ou todos os documentos em um índice. Também é possível pesquisar um índice para recuperar documentos que atendam a determinados critérios nos campos e valores correspondentes, especificados como uma string de consulta. Também é possível gerenciar grupos de documentos colocando-os em índices separados. Não há limite para a quantidade de documentos em um índice nem para o número de índices em uso. O
tamanho total de todos os documentos em um único índice é limitado a 10 GB por padrão,
mas pode ser aumentado para até 200 GB enviando uma solicitação da
página App Engine Search
do console do Google Cloud. O método Index.getStorageLimit()
retorna o tamanho máximo permitido de um índice.
Index.getStorageUsage()
é uma estimativa da quantidade de espaço de armazenamento usado por um índice. Esse número é uma estimativa porque o sistema de monitoramento de índice não é executado continuamente. O uso real é computado periodicamente. O storage_usage
é ajustado entre
pontos de amostragem contabilizando-se as adições de documentos, mas não as exclusões.
Esquemas de índice
Todos os índices têm esquemas que mostram os nomes e os tipos de campos que aparecem nos documentos que contêm. Não é possível definir um esquema. Os esquemas são mantidos dinamicamente e são atualizados conforme os documentos são adicionados ao índice. Um esquema simples pode ter a seguinte aparência em um formulário estilo JSON:
{'comment': ['TEXT'], 'date': ['DATE'], 'author': ['TEXT'], 'count': ['NUMBER']}
Cada chave do dicionário é o nome de um campo de documento. O valor da chave é uma lista dos tipos de campo usados com esse nome. Se você usar o mesmo nome de campo com tipos diferentes, o esquema listará mais de um tipo para cada nome, como no exemplo a seguir:
{'ambiguous-integer': ['TEXT', 'NUMBER', 'ATOM']}
Quando um campo aparece em um esquema, ele nunca mais pode ser removido. Não há como excluir um campo, mesmo que o índice não contenha mais nenhum documento com esse nome de campo específico.
É possível ver os esquemas dos índices assim: Observe que uma chamada paraGetIndexes()
não pode retornar mais de 1.000 índices. Para recuperar mais índices, chame o método repetidamente, usando setStartIndexName()
com GetIndexesRequest.Builder
.
No esquema, não há a definição de uma "classe" no sentido da programação de objeto. No que diz respeito à API Search, cada documento é único, e os índices podem conter diferentes tipos de documentos. Se você quiser tratar coleções de objetos com a mesma lista de campos como instâncias de uma classe, isso é uma abstração que precisará ser aplicada no seu código. Por exemplo, é possível garantir que todos os documentos com o mesmo conjunto de campos sejam mantidos no próprio índice. O esquema de índice pode ser visto como a definição da classe, e cada documento no índice é uma instância dessa classe.
Como visualizar índices no console do Google Cloud
No console do Google Cloud, é possível ver informações sobre os índices do aplicativo e os documentos que eles contêm. Clique em um nome de índice para exibir os documentos contidos nele. Você verá todos os campos de esquema definidos para o índice e, para cada documento que contém um campo com aquele nome, você verá o valor desse campo. Também é possível emitir consultas aos dados do índice diretamente do console.
Cotas da API Search
A API Search tem várias cotas gratuitas:
Recurso ou chamada de API | Cota gratuita |
---|---|
Armazenamento total (documentos e índices) | 0,25 GB |
Consultas | 1.000 consultas por dia |
Adição de documentos a índices | 0,01 GB por dia |
Esses limites são impostos pela API para garantir a confiabilidade do serviço. Eles são válidos para aplicativos gratuitos e pagos:
Recurso | Cota de segurança |
---|---|
Uso máximo da consulta | 100 minutos agregados de tempo de execução da consulta por minuto |
Máximo de documentos adicionados ou excluídos | 15.000 por minuto |
Tamanho máximo por índice (número ilimitado de índices permitido) | 10 GB |
O uso da API é contado de diferentes maneiras, dependendo do tipo de chamada:
Index.search()
: cada chamada de API conta como uma consulta. O tempo de execução é equivalente à latência da chamada.Index.put()
: quando você adiciona documentos a índices, o tamanho de cada documento e o número de documentos contam para a cota de indexação.- Todas as outras chamadas da API Search são contadas com base no número de operações que envolvem:
SearchService.getIndexes()
: uma operação é contada para cada índice realmente retornado ou uma operação se nada for retornado.Index.get()
eIndex.getRange()
: uma operação é contada para cada documento realmente retornado ou uma operação se nada for retornado.Index.delete()
: uma operação é contada para cada documento da solicitação, ou uma operação se a solicitação estiver vazia.
A cota sobre a capacidade da consulta é imposta para que um único usuário não monopolize o serviço de pesquisa. Como as consultas podem ser executadas simultaneamente, cada aplicativo pode executar consultas que consumam até 100 minutos de tempo de execução por minuto no relógio. Se executar várias consultas curtas, você provavelmente não atingirá esse limite. porém, se exceder a cota, as consultas subsequentes falharão até a próxima fração de tempo, quando a cota é restaurada. A cota não é imposta rigorosamente em frações de um minuto. Uma variação do algoritmo leaky bucket é usada para controlar a largura de banda da pesquisa em incrementos de cinco segundos.
Veja mais informações na página Cotas. Se o app tentar exceder esses valores, é exibido um erro de cotas insuficientes.
Esses limites são aplicados por minuto, mas o console exibe os totais diários para cada um. Os clientes com suporte Silver, Gold ou Platinum podem solicitar limites de capacidade maiores. Basta entrar em contato com o representante do suporte.
Preços da API Search
As seguintes cobranças são aplicadas ao uso além das cotas gratuitas:
Recurso | Custo |
---|---|
Armazenamento total (documentos e índices) | US$ 0,18 por GB/mês |
Consultas | US$ 0,50 por 10.000 consultas |
Indexação de documentos pesquisáveis | US$ 2,00 por GB |
Para mais informações, acesse a página de Preços.