LoginSignup
1
1

More than 3 years have passed since last update.

[Azure Search] Indexerを作る

Posted at

さて、今日はAzure SearchIndexerを作ります。 

Indexerとは

IndexerAzure Searchで作成したIndexにデータをデータソースから流し込み機能です。一度だけ流し込むパターンもあれば、定期的(スケジュール)に流し込むこともできます。

対応データソース

対応しているデータソースは下記です

  • Azure Blob Storage
  • Azure Data Lake Storage Gen2 (in preview)
  • Azure Table Storage
  • Azure Cosmos DB
  • Azure SQL Database
  • SQL Server on Azure Virtual Machines
  • SQL Managed instances on Azure

サンプルコード

public static async Task Main(string[] args)
{
  IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
  IConfigurationRoot configuration = builder.Build();

  if (configuration["SearchServiceName"] == "Put your search service name here")
  {
    Console.Error.WriteLine("Specify SearchServiceName in appsettings.json");
    Environment.Exit(-1);
  }

  if (configuration["SearchServiceAdminApiKey"] == "Put your primary or secondary API key here")
  {
    Console.Error.WriteLine("Specify SearchServiceAdminApiKey in appsettings.json");
    Environment.Exit(-1);
  }

  if (configuration["AzureSQLConnectionString"] == "Put your Azure SQL database connection string here")
  {
    Console.Error.WriteLine("Specify AzureSQLConnectionString in appsettings.json");
    Environment.Exit(-1);
  }

  SearchServiceClient searchService = new
    SearchServiceClient(searchServiceName: configuration["SearchServiceName"],
    credentials: new 
    SearchCredentials(configuration["SearchServiceAdminApiKey"]));

  Console.WriteLine("Creating index...");
  Index index = new Index(
    name: "hotels",
    fields: FieldBuilder.BuildForType<Hotel>());

  // If we have run the sample before, this index will be populated
  // We can clear the index by deleting it if it exists and creating
  // it again
  bool exists = await searchService.Indexes.ExistsAsync(index.Name);
  if (exists)
  {
    await searchService.Indexes.DeleteAsync(index.Name);
  }

  await searchService.Indexes.CreateAsync(index);

  Console.WriteLine("Creating data source...");

  // The sample data set has a table name of "hotels"
  // The sample data set table has a "soft delete" column named IsDeleted
  // When this column is set to true and the indexer sees it, it will remove the
  // corresponding document from the search service
  // See this link for more information
  // https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.search.models.softdeletecolumndeletiondetectionpolicy
  // The sample data set uses SQL integrated change tracking for change detection
  // This means that when the indexer runs, it will be able to detect which data has
  // changed since the last run using built in change tracking
  // See this link for more information
  // https://docs.microsoft.com/en-us/sql/relational-databases/track-changes/about-change-tracking-sql-server
  DataSource dataSource = DataSource.AzureSql(
    name: "azure-sql",
    sqlConnectionString: configuration["AzureSQLConnectionString"],
    tableOrViewName: "hotels",
    deletionDetectionPolicy: new SoftDeleteColumnDeletionDetectionPolicy(
    softDeleteColumnName: "IsDeleted",
    softDeleteMarkerValue: "true"));
    dataSource.DataChangeDetectionPolicy = new SqlIntegratedChangeTrackingPolicy();
    // The data source does not need to be deleted if it was already created,
    // but the connection string may need to be updated if it was changed
    await searchService.DataSources.CreateOrUpdateAsync(dataSource);

    Console.WriteLine("Creating Azure SQL indexer...");
    Indexer indexer = new Indexer(
    name: "azure-sql-indexer",
    dataSourceName: dataSource.Name,
    targetIndexName: index.Name,
    schedule: new IndexingSchedule(TimeSpan.FromDays(1)));
    // Indexers contain metadata about how much they have already indexed
    // If we already ran the sample, the indexer will remember that it already
    // indexed the sample data and not run again
    // To avoid this, reset the indexer if it exists
    exists = await searchService.Indexers.ExistsAsync(indexer.Name);
    if (exists)
    {
      await searchService.Indexers.ResetAsync(indexer.Name);
    }

    await searchService.Indexers.CreateOrUpdateAsync(indexer);

    // We created the indexer with a schedule, but we also
    // want to run it immediately
    Console.WriteLine("Running Azure SQL indexer...");

    try
    {
      await searchService.Indexers.RunAsync(indexer.Name);
    }
    catch (CloudException e) when (e.Response.StatusCode == (HttpStatusCode)429)
    {
      Console.WriteLine("Failed to run indexer: {0}", e.Response.Content);
    }

    Console.WriteLine("Press any key to continue...");
    Console.ReadKey();
    Environment.Exit(0);
}

データソースの生成

サンプルコードではIndexの生成から始まっていますが、Indexの生成に関しては前回の投稿を参照ください。

:point_right_tone1:[Azure Search] で高速テキスト検索

データソースは下記のラインで生成しています。

DataSource dataSource = DataSource.AzureSql(
  name: "azure-sql",
  sqlConnectionString: configuration["AzureSQLConnectionString"],
  tableOrViewName: "hotels",
  deletionDetectionPolicy: new SoftDeleteColumnDeletionDetectionPolicy(
  softDeleteColumnName: "IsDeleted",
  softDeleteMarkerValue: "true"));
  dataSource.DataChangeDetectionPolicy = new 
    SqlIntegratedChangeTrackingPolicy();
    // The data source does not need to be deleted if it was already created,
    // but the connection string may need to be updated if it was changed

  await searchService.DataSources.CreateOrUpdateAsync(dataSource);

DataSourceクラスの詳細はこちらを参照してください。
https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.search.models.datasource?view=azure-dotnet

今回のサンプルではAzureSqlを使っています。AzureSqlメソッドの詳細はこちらです。
https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.search.models.datasource.azuresql?view=azure-dotnet

まずはデータソースの名前を指定します。

name: "azure-sql",

次にSQLへの接続文字列を指定

sqlConnectionString: configuration["AzureSQLConnectionString"],

次にTable or View の名前を指定します

tableOrViewName: "hotels",

次に削除されたデータ(Searchから削除をするべきデータ)を指定します。

deletionDetectionPolicy: new SoftDeleteColumnDeletionDetectionPolicy(
softDeleteColumnName: "IsDeleted",
softDeleteMarkerValue: "true"));

SoftDeleteColumnDeletionDetectionPolicyメセッドの詳細は下記です。
https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.search.models.softdeletecolumndeletiondetectionpolicy?view=azure-dotnet 簡単に言うと softDeleteColumnNamesoftDeleteMarkerValueの2フィールドです。

softDeleteColumnNameでフィールド名を指定し
softDeleteMarkerValueで削除を示す値を指定します。

次はデータが変更されたことをトラッキングする手法を指定します。

dataSource.DataChangeDetectionPolicy = new 
    SqlIntegratedChangeTrackingPolicy();

DataChangeDetectionPolicyメソッドの詳細はこちらを参照ください。
https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.search.models.datachangedetectionpolicy?view=azure-dotnet

ここは、コンストラクターの初期化以外に指定するものがなさそうです。

最後にデータソースを生成します。

await searchService.DataSources.CreateOrUpdateAsync(dataSource);

Indexerの生成

次は先ほど作成したデータソースをベースにIndexerを生成します。

Indexer indexer = new Indexer(
  name: "azure-sql-indexer",
  dataSourceName: dataSource.Name,
  targetIndexName: index.Name,
  schedule: new IndexingSchedule(TimeSpan.FromDays(1)));

  // Indexers contain metadata about how much they have already indexed
  // If we already ran the sample, the indexer will remember that it already
  // indexed the sample data and not run again
  // To avoid this, reset the indexer if it exists
  exists = await searchService.Indexers.ExistsAsync(indexer.Name);
  if (exists)
  {
    await searchService.Indexers.ResetAsync(indexer.Name);
  }

  await searchService.Indexers.CreateOrUpdateAsync(indexer);

こちらがIndexerを生成するファンクションです。

Indexer indexer = new Indexer(
  name: "azure-sql-indexer",
  dataSourceName: dataSource.Name,
  targetIndexName: index.Name,
  schedule: new IndexingSchedule(TimeSpan.FromDays(1)));

ここでIndexerの名前を指定します。

name: "azure-sql-indexer",

先ほど生成したデータソースを指定します

dataSourceName: dataSource.Name,

ターゲットのIndex名を指定します

targetIndexName: index.Name,

ここでIndexerが始動するタイミングを指定します。

schedule: new IndexingSchedule(TimeSpan.FromDays(1)))

IndexingScheduleの詳細は下記を参照ください
https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.search.models.indexingschedule?view=azure-dotnet

下記が基本構成です。

IndexingSchedule(TimeSpan, Nullable<DateTimeOffset>)

TimeSpan (Interval) と Nullable<DateTimeOffset> (StartTime)を指定します。StartTimeはオプションのようです。指定しない場合はこのメソッドを読んだ時から(以降)該当するタイミングで実行されます。

TimeSpan に関しては下記を参照ください。
https://docs.microsoft.com/en-us/dotnet/api/system.timespan?view=netframework-4.8

このメソッドが呼ばれた同じ時間(タイミング)で毎日Indexerが稼働するようになっています。

実行

サンプルは下記のようにIndexerが存在する場合はリセットしていますが、本来必要ありません。サンプルとしてテストする際にすでにIndexerが実行されていると前回からデータソースに更新がないためIndexerの実行がないのでリセットしています。

exists = await searchService.Indexers.ExistsAsync(indexer.Name);
if (exists)
{
  await searchService.Indexers.ResetAsync(indexer.Name);
}

下記で実行します。

await searchService.Indexers.CreateOrUpdateAsync(indexer);

上記でスケジュールはセットされましたが、次のスケジュールまで実行されないため、ここでスケジュールと関係なく実行するように指示します。

// We created the indexer with a schedule, but we also
// want to run it immediately
Console.WriteLine("Running Azure SQL indexer...");

try
{
  await searchService.Indexers.RunAsync(indexer.Name);
}

catch (CloudException e) when (e.Response.StatusCode == (HttpStatusCode)429)
{
  Console.WriteLine("Failed to run indexer: {0}", e.Response.Content);
}

Console.WriteLine("Press any key to continue...");
Console.ReadKey();
Environment.Exit(0);

最後に

Azure Searchはウェブサービスを作る際にとても便利なサービスです。よく使う場合はマイライブラリー化して使うことをお勧めします。

参照

Indexer Overview
https://docs.microsoft.com/en-us/azure/search/search-indexer-overview

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1