Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O Azure AI Search dá suporte a dois métodos básicos para importar dados em um índice de pesquisa: enviar seus dados programaticamente para o índice ou extrair os dados apontando um indexador para uma fonte de dados com suporte.
Este tutorial explica como indexar dados com eficiência usando o modelo de push por meio de solicitações de envio em lote e usando uma estratégia de repetição de retirada exponencial. Você pode baixar e executar o aplicativo de exemplo. Este tutorial também explica os principais aspectos do aplicativo e quais fatores considerar ao indexar dados.
Neste tutorial, você usa o C# e a biblioteca Azure.Search.Documents do SDK do Azure para .NET para:
- Crie um índice
- Testa vários tamanhos de lote para determinar o tamanho mais eficiente
- Indexar lotes de maneira assíncrona
- Usar vários threads para aumentar as velocidades de indexação
- Usar uma estratégia de repetição de retirada exponencial para repetir documentos com falha
Pré-requisitos
- Uma conta do Azure com uma assinatura ativa. Crie uma conta gratuitamente.
- Visual Studio.
Baixar arquivos
O código-fonte para este tutorial está na pasta optimize-data-indexing/v11 no repositório do GitHub Azure-Samples/azure-search-dotnet-scale.
Principais considerações
Os fatores a seguir afetam as velocidades de indexação. Para obter mais informações, consulte Índice de grandes conjuntos de dados.
- Tipo de preço e número de partições/réplicas: adicionar partições ou atualizar sua camada aumenta as velocidades de indexação.
- Complexidade do esquema de indexação: adicionar campos e propriedades de campo reduz as velocidades de indexação. É mais rápido indexar os índices menores.
- Tamanho do lote: o tamanho do lote ideal varia de acordo com o esquema do índice e o conjunto de dados.
- Número de threads/trabalhadores: um único thread não aproveitará totalmente as velocidades de indexação.
- Estratégia de repetição: uma estratégia de repetição com recuo exponencial é uma prática recomendada para uma indexação ideal.
- Velocidades de transferência de dados de rede: as velocidades de transferência de dados podem ser um fator limitante. Indexe os dados de dentro de seu ambiente do Azure para aumentar as velocidades de transferência de dados.
Criar um serviço de pesquisa
Este tutorial requer um serviço do Azure AI Search, que você pode criar no portal do Azure. Você também pode encontrar um serviço existente em sua assinatura atual. Para testar com precisão e otimizar as velocidades de indexação, recomendamos usar a mesma camada de preços que você planeja usar em produção.
Obter uma chave de administrador e um URL para a Pesquisa de IA do Azure
Este tutorial usa a autenticação baseada em chave. Copie uma chave de API de administrador para colar no appsettings.json arquivo.
Entre no portal do Azure e selecione seu serviço de pesquisa.
No painel esquerdo, selecione Visão geral e copie o ponto de extremidade. Ele deve estar nesse formato:
https://my-service.search.windows.netNo painel esquerdo, selecioneChaves de > e copie uma chave de administrador para obter direitos completos no serviço. Há duas chaves de administração intercambiáveis, fornecidas para a continuidade dos negócios, caso seja necessário sobrepor uma. Você pode usar uma chave em solicitações para adicionar, modificar ou excluir objetos.
Configure seu ambiente
Abra o arquivo
OptimizeDataIndexing.slnno Visual Studio.No Gerenciador de Soluções, edite o
appsettings.jsonarquivo com as informações de conexão coletadas na etapa anterior.{ "SearchServiceUri": "https://{service-name}.search.windows.net", "SearchServiceAdminApiKey": "", "SearchIndexName": "optimize-indexing" }
Explorar o código
Depois de atualizar appsettings.json, o programa de OptimizeDataIndexing.sln exemplo deve estar pronto para compilar e executar.
Esse código é derivado da seção C# do Início Rápido: pesquisa de texto completo, que fornece informações detalhadas sobre os conceitos básicos de trabalhar com o SDK do .NET.
Este aplicativo de console C#/.NET simples realiza as seguintes tarefas:
- Cria um índice baseado na estrutura de dados da classe C#
Hotel(que também faz referência à classeAddress) - Testa vários tamanhos de lote para determinar o tamanho mais eficiente
- Indexa dados de modo assíncrono
- Usando vários threads para aumentar as velocidades de indexação
- Usar uma estratégia de repetição de retirada exponencial para repetir itens com falha
Antes de executar o programa, reserve um minuto para estudar o código e as definições de índice para este exemplo. O código relevante está em vários arquivos:
-
Hotel.cseAddress.cscontêm o esquema que define o índice -
DataGenerator.cscontém uma classe simples para facilitar a criação de grandes quantidades de dados de hotéis -
ExponentialBackoff.cscontém código para otimizar o processo de indexação, conforme descrito neste artigo -
Program.cscontém funções que criam e excluem o índice do Azure AI Search, indexa lotes de dados e testa tamanhos de lote diferentes
Criar o índice
Este programa de exemplo usa o SDK do Azure para .NET para definir e criar um índice da Pesquisa de IA do Azure. Ele aproveita a classe FieldBuilder para gerar uma estrutura de índice de uma classe de modelo de dados C#.
O modelo de dados é definido pela classe Hotel, que também contém referências à classe Address.
FieldBuilder faz drill down por meio de várias definições de classe para gerar uma estrutura de dados complexa para o índice. As marcas de metadados são usadas para definir os atributos de cada campo, como se ele é pesquisável ou classificável.
Os snippets a seguir do Hotel.cs arquivo especificam um único campo e uma referência a outra classe de modelo de dados.
. . .
[SearchableField(IsSortable = true)]
public string HotelName { get; set; }
. . .
public Address Address { get; set; }
. . .
Program.cs No arquivo, o índice é definido com um nome e uma coleção de campos gerada pelo FieldBuilder.Build(typeof(Hotel)) método e, em seguida, criado da seguinte maneira:
private static async Task CreateIndexAsync(string indexName, SearchIndexClient indexClient)
{
// Create a new search index structure that matches the properties of the Hotel class.
// The Address class is referenced from the Hotel class. The FieldBuilder
// will enumerate these to create a complex data structure for the index.
FieldBuilder builder = new FieldBuilder();
var definition = new SearchIndex(indexName, builder.Build(typeof(Hotel)));
await indexClient.CreateIndexAsync(definition);
}
Gerar dados
Uma classe simples é implementada no DataGenerator.cs arquivo para gerar dados para teste. A finalidade dessa classe é facilitar a geração de um grande número de documentos com uma ID exclusiva para indexação.
Para obter uma lista de 100.000 hotéis com IDs exclusivas, execute o seguinte código:
long numDocuments = 100000;
DataGenerator dg = new DataGenerator();
List<Hotel> hotels = dg.GetHotels(numDocuments, "large");
Há dois tamanhos de hotéis disponíveis para teste neste exemplo: pequenos e grandes.
O esquema do índice afeta as velocidades de indexação. Depois de concluir este tutorial, considere converter essa classe para gerar dados que melhor correspondam ao esquema de índice pretendido.
Testar tamanhos de lote
Para carregar documentos únicos ou múltiplos em um índice, o Azure AI Search dá suporte às seguintes APIs:
A indexação de documentos em lotes aprimora significativamente o desempenho da indexação. Esses lotes podem ter até 1.000 documentos ou até cerca de 16 MB por lote.
Determinar o tamanho de lote ideal para seus dados é um componente-chave de otimização de velocidades de indexação. Os dois fatores principais que influenciam o tamanho de lote ideal são:
- O esquema do índice
- O tamanho dos seus dados
Como o tamanho ideal do lote depende do índice e dos dados, a melhor abordagem é testar diferentes tamanhos de lote para determinar o que resulta nas velocidades de indexação mais rápidas para seu cenário.
A função a seguir demonstra uma abordagem simples para testar tamanhos de lote.
public static async Task TestBatchSizesAsync(SearchClient searchClient, int min = 100, int max = 1000, int step = 100, int numTries = 3)
{
DataGenerator dg = new DataGenerator();
Console.WriteLine("Batch Size \t Size in MB \t MB / Doc \t Time (ms) \t MB / Second");
for (int numDocs = min; numDocs <= max; numDocs += step)
{
List<TimeSpan> durations = new List<TimeSpan>();
double sizeInMb = 0.0;
for (int x = 0; x < numTries; x++)
{
List<Hotel> hotels = dg.GetHotels(numDocs, "large");
DateTime startTime = DateTime.Now;
await UploadDocumentsAsync(searchClient, hotels).ConfigureAwait(false);
DateTime endTime = DateTime.Now;
durations.Add(endTime - startTime);
sizeInMb = EstimateObjectSize(hotels);
}
var avgDuration = durations.Average(timeSpan => timeSpan.TotalMilliseconds);
var avgDurationInSeconds = avgDuration / 1000;
var mbPerSecond = sizeInMb / avgDurationInSeconds;
Console.WriteLine("{0} \t\t {1} \t\t {2} \t\t {3} \t {4}", numDocs, Math.Round(sizeInMb, 3), Math.Round(sizeInMb / numDocs, 3), Math.Round(avgDuration, 3), Math.Round(mbPerSecond, 3));
// Pausing 2 seconds to let the search service catch its breath
Thread.Sleep(2000);
}
Console.WriteLine();
}
Como nem todos os documentos têm o mesmo tamanho (embora estejam neste exemplo), estimamos o tamanho dos dados que estamos enviando para o serviço de pesquisa. Você pode fazer isso usando a função a seguir que primeiro converte o objeto em JSON e, em seguida, determina seu tamanho em bytes. Essa técnica nos permite determinar quais tamanhos de lote são mais eficientes em termos de velocidades de indexação de MB/s.
// Returns size of object in MB
public static double EstimateObjectSize(object data)
{
// converting object to byte[] to determine the size of the data
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
byte[] Array;
// converting data to json for more accurate sizing
var json = JsonSerializer.Serialize(data);
bf.Serialize(ms, json);
Array = ms.ToArray();
// converting from bytes to megabytes
double sizeInMb = (double)Array.Length / 1000000;
return sizeInMb;
}
A função requer um SearchClient, mais o número de tentativas que você deseja testar para cada tamanho de lote. Como pode haver variabilidade nos tempos de indexação para cada lote, tente cada lote três vezes, por padrão, para tornar os resultados estatisticamente mais significativos.
await TestBatchSizesAsync(searchClient, numTries: 3);
Ao executar a função, você deverá ver uma saída no console semelhante ao exemplo a seguir:
Identifique qual tamanho de lote é mais eficiente e use esse tamanho de lote na próxima etapa deste tutorial. Você poderá ver um limite em MB/s em diferentes tamanhos de lote.
Indexar os dados
Agora que você identificou o tamanho do lote que pretende usar, a próxima etapa é começar a indexar os dados. Para indexar dados com eficiência, este exemplo:
- Usa vários threads/trabalhos
- Implementa uma estratégia de repetição de retirada exponencial
Descomente as linhas 41 a 49 e, em seguida, execute novamente o programa. Nessa execução, o exemplo gera e envia lotes de documentos, até 100.000 se você executar o código sem alterar os parâmetros.
Usar vários threads/trabalhos
Para aproveitar as velocidades de indexação do Azure AI Search, use vários threads para enviar solicitações de indexação em lote simultaneamente para o serviço.
Várias das principais considerações podem afetar o número ideal de threads. Você pode modificar este exemplo e testar com contagens de threads diferentes para determinar a contagem de threads ideal para seu cenário. No entanto, desde que haja vários threads em execução simultânea, você poderá aproveitar a maioria dos ganhos de eficiência.
Conforme você aumenta as solicitações que chegam ao serviço de pesquisa, pode encontrar códigos de status HTTP indicando que a solicitação não foi totalmente concluída com sucesso. Durante a indexação, dois códigos de status HTTP comuns são:
- 503 Serviço Não Disponível: esse erro significa que o sistema está sob carga pesada e sua solicitação não pode ser processada no momento.
- 207 Multi-Status: esse erro significa que alguns documentos foram bem-sucedidos, mas pelo menos um deles falhou.
Implementar uma estratégia de repetição de retirada exponencial
Se ocorrer uma falha, você deverá repetir as solicitações usando uma estratégia de repetição de retirada exponencial.
O SDK do .NET da Pesquisa de IA do Azure repete automaticamente o erro 503 e outras solicitações com falha, mas você deve implementar sua própria lógica para tentar novamente o 207. Ferramentas de software livre, como o Polly , podem ser úteis em uma estratégia de repetição.
Neste exemplo, implementamos nossa própria estratégia de repetição de retirada exponencial. Começamos definindo algumas variáveis, incluindo a maxRetryAttempts e a delay inicial para uma solicitação com falha.
// Create batch of documents for indexing
var batch = IndexDocumentsBatch.Upload(hotels);
// Create an object to hold the result
IndexDocumentsResult result = null;
// Define parameters for exponential backoff
int attempts = 0;
TimeSpan delay = delay = TimeSpan.FromSeconds(2);
int maxRetryAttempts = 5;
Os resultados da operação de indexação são armazenados na variável IndexDocumentResult result. Essa variável permite verificar se os documentos no lote falharam, conforme mostrado no exemplo a seguir. Se houver uma falha parcial, um novo lote será criado com base nas IDs dos documentos falhos.
As exceções RequestFailedException também devem ser detectadas, pois indicam que a solicitação falhou completamente e deve ser repetida.
// Implement exponential backoff
do
{
try
{
attempts++;
result = await searchClient.IndexDocumentsAsync(batch).ConfigureAwait(false);
var failedDocuments = result.Results.Where(r => r.Succeeded != true).ToList();
// handle partial failure
if (failedDocuments.Count > 0)
{
if (attempts == maxRetryAttempts)
{
Console.WriteLine("[MAX RETRIES HIT] - Giving up on the batch starting at {0}", id);
break;
}
else
{
Console.WriteLine("[Batch starting at doc {0} had partial failure]", id);
Console.WriteLine("[Retrying {0} failed documents] \n", failedDocuments.Count);
// creating a batch of failed documents to retry
var failedDocumentKeys = failedDocuments.Select(doc => doc.Key).ToList();
hotels = hotels.Where(h => failedDocumentKeys.Contains(h.HotelId)).ToList();
batch = IndexDocumentsBatch.Upload(hotels);
Task.Delay(delay).Wait();
delay = delay * 2;
continue;
}
}
return result;
}
catch (RequestFailedException ex)
{
Console.WriteLine("[Batch starting at doc {0} failed]", id);
Console.WriteLine("[Retrying entire batch] \n");
if (attempts == maxRetryAttempts)
{
Console.WriteLine("[MAX RETRIES HIT] - Giving up on the batch starting at {0}", id);
break;
}
Task.Delay(delay).Wait();
delay = delay * 2;
}
} while (true);
Daqui em diante, encapsule o código de retirada exponencial em uma função para que ele possa ser chamado facilmente.
Outra função é então criada para gerenciar os threads ativos. Para simplificar, essa função não está incluída aqui, mas pode ser encontrada em ExponentialBackoff.cs. Você pode chamar a função usando o comando a seguir, onde hotels estão os dados que desejamos carregar, 1000 é o tamanho do lote e 8 é o número de threads simultâneos.
await ExponentialBackoff.IndexData(indexClient, hotels, 1000, 8);
Ao executar a função, você deverá ver uma saída semelhante ao exemplo a seguir:
Quando um lote de documentos falha, um erro é impresso indicando a falha e que o lote está sendo repetido.
[Batch starting at doc 6000 had partial failure]
[Retrying 560 failed documents]
Depois que a função terminar de ser executada, você poderá verificar se todos os documentos foram adicionados ao índice.
Explorar o índice
Depois que o programa terminar de ser executado, você poderá explorar o índice de pesquisa preenchido programaticamente ou usando o Gerenciador de Pesquisa no portal do Azure.
Programaticamente
Há duas opções principais para verificar o número de documentos em um índice: a API de Contagem de Documentos e a API Obter Estatísticas de Índice. Ambos os caminhos exigem tempo para serem processados, portanto, não se assuste se o número de documentos retornados for inicialmente menor do que você espera.
Contar documentos
A operação Count Documents recupera uma contagem do número de documentos em um índice de pesquisa.
long indexDocCount = await searchClient.GetDocumentCountAsync();
Obter estatísticas de índice
A operação Obter Estatísticas do Índice retorna uma contagem de documentos para o índice atual, além do uso do armazenamento. As estatísticas de índice levam mais tempo para serem atualizadas do que a contagem de documentos.
var indexStats = await indexClient.GetIndexStatisticsAsync(indexName);
portal do Azure
No portal do Azure, no painel esquerdo, localize o índice optimize-indexing na lista Índices.
A Contagem de Documentos e o Tamanho do Armazenamento são baseados na API Obter Estatísticas de Índice e podem levar vários minutos para serem atualizados.
Redefina e reinicie
Nos primeiros estágios experimentais de desenvolvimento, a abordagem mais prática para iterações de design é excluir os objetos da IA do Azure Search e permitir que seu código os reconstrua. Os nomes dos recursos são únicos. Excluir um objeto permite que você recriá-la usando o mesmo nome.
O código de exemplo deste tutorial verifica se há índices existentes e os exclui, de modo que você possa executar novamente o código.
Você também pode usar o portal do Azure para excluir índices.
Limpar os recursos
Quando você está trabalhando em sua própria assinatura, no final de um projeto, é recomendável remover os recursos que já não são necessários. Recursos deixados em execução podem custar dinheiro. É possível excluir os recursos individualmente ou excluir o grupo de recursos para excluir todo o conjunto de recursos.
Você pode encontrar e gerenciar recursos no portal do Azure usando o link Todos os recursos ouGrupos de recursos no painel de navegação à esquerda.
Próxima etapa
Para saber mais sobre a indexação de dados de grandes quantidades, experimente o seguinte tutorial: