さて、今日はAzure Search
のIndexer
を作ります。
Indexerとは
Indexer
はAzure 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の生成に関しては前回の投稿を参照ください。
データソースは下記のラインで生成しています。
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 簡単に言うと softDeleteColumnName
と softDeleteMarkerValue
の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