Microsoft Azure

Azure Search によるデータ探索の強化

Bruno Terkaly

コード サンプルのダウンロード

Bruno Terkaly データ探索、リアルタイム分析、機械学習の分野では、多数の独創的な方法が使われています。企業は、さまざまなオープン ソースのソフトウェア パッケージを中心に興味深いアーキテクチャを構築しています。大規模アーキテクチャの一部として構築された Azure Search は、検索のエキスパートでなくても、Web サイトや Web アプリに応用できる新しく強力な検索機能です。

Azure Search は完全に管理されるクラウドベースのサービスで、シンプルな REST API を使用し、先行入力候補、近似一致に基づく候補結果、多面的なナビゲーション、ニーズに基づく容量調整機能などを提供します。さらに、重みによる全文検索、ランクによる全文検索、およびフィールド属性の組み合わせによって定義されるスキーマに基づく独自の検索動作による全文検索も可能です。データには即座にインデックスが設定され、検索の遅延が最小限に抑えられます。

効率のよい検索のニーズは、大量データの保存と共にますます拡大しています。Facebook だけでも、ユーザーによって毎月数千億分もの時間が検索に費やされています。Wikipedia を効率よく検索するには、1,700 万件もの項目にインデックスを付けなければなりません。Twitter のユーザーは 6 億人にも上り、1 日に 5 万件以上のツイートが行われています。この規模のデータを全文検索するには、何らかの独創的なエンジニアリングが必要です。このような情報すべてにインデックスを作成して管理するのは、安心のためではありません。

自社の事業に検索が不可欠だと考える企業の多くは、32 コア、448GB RAM、6.5 TB のソリッド ステート ドライブ (SSD) を搭載した Azure G-Series Virtual Machine を使い始めています。カスタム アセンブリと C コードを記述して、データ キャッシュと命令キャッシュ両方に対してキャッシュの一貫性を最適化しているエンジニアもいます。このようなキャッシュを利用すれば、メモリ要求が満たされるまで CPU が待機する時間と転送する必要があるデータの全体量が削減されます。G-Series のように巨大なマルチコア コンピューターの課題の 1 つは、メモリ バスが競合するおそれが高くなることです。そのため L2 および L3 キャッシュを活用してパフォーマンスを向上させることが非常に重要になります。大量のデータが急に全文検索エンジンに流れ込むと致命的となるため、こうした対処がすべて重要になります。

全文検索の適切な実行

Azure Search には多くのメリットがあります。Azure Serch を利用すれば、検索インデックスの設定や管理を独自に行う複雑さが緩和されます。完全に管理されるサービスなので、インデックスの破損、サービスの可用性、スケール変換、サービスの更新などの煩わしい作業に対処する必要がなくなります。大きなメリットの 1 つに、リッチできめ細かいランク付けモデルがサポートされる点があります。このモデルにより、検索結果をビジネスの目標に結び付けることができるようになります。また、多言語検索、スペルミスの訂正、先行入力候補の表示などの機能もあります。検索結果に満足できなければ、Azure Search から近似一致に基づくクエリが提案されます。bit.ly/1wYb8L8 (英語) では、新しい Azure Search インスタンスをプロビジョニングするための簡単なチュートリアルを確認できます。

このチュートリアルでは、Azure Search の概要とポータルで行う必要のある手順が解説されています。Azure の管理ポータル (portal.azure.com) で Azure Search のプロビジョニングを行います。プロビジョニングには、ポータル自体によって提供される URL と API キーの 2 つが必要です。URL はAzure Search サービスを実行中のクラウドのエンドポイント、つまりクライアント アプリが応答する先を示します。API キーがあればサービスすべてにアクセスできるようになるため、慎重に保護する必要があります。逆に、API キーを使って認証を受けていないクライアントは Azure Search サービスにアクセスすることはできません。

インデックスの数、インデックスごとの最大フィールド数、ドキュメントの最大数などには、注意すべき制限と制約があります。重要な制限の 1 つとして、クエリに関連付けるクォータや最大制限がありません。1 秒あたりのクエリ数 (QPS) は、利用可能な帯域幅と、システム リソースの競合状態によって異なります。

無償の Azure Search サービスでは、共有サービスによってサポートされる Azure のコンピューティング リソースとストレージ サービスが、複数のサブスクライバによって共有されます。そのため、ソリューションの QPS は同時に実行されるワークロードの数によって大きく変わります。専用 (STANDARD SKU) サービスの場合、リソースはすべて顧客専用に確保され、共有されません。

URL と API キーを取得したら、サービスを使う準備は完了です。Fiddler は独自の HTTP リクエストを組み立てることができるため、Fiddler を使用するのが最も簡単な方法です (Fiddler の無償コピーは bit.ly/1jKA1UJ (英語) から入手できます)。Azure Search はシンプルな HTTP を使用するため、Fiddler を使ってデータを挿入およびクエリするのは簡単です (図 1 参照)。コラムの最後で、Azure Search と対話するために Node.js を使用する方法も紹介します。

Fiddler を使用した Azure Search への挿入の実行
図 1 Fiddler を使用した Azure Search への挿入の実行

この図には、興味を深い点が 4 つあります。

  • HTTP 動詞
  • URL
  • 要求ヘッダー
  • 要求フッター

HTTP 動詞 (PUT、POST、GET、または DELETE) は、スキーマ定義操作、データ挿入など、異なる操作にマップされます。たとえば、PUT はスキーマ定義、POST はデータ挿入にマップされます。URL は Azure のポータルから入手でき、クエリ パラメーターによって変わる可能性があります。API キーは、要求ヘッダーで送信されます。要求本文は常に、挿入されるスキーマまたはデータの JSON 表現です。

Azure Search は大規模アーキテクチャにおいて重要な役割を果たします。図 2 は Azure Search を利用するアーキテクチャの例を示しています。必要不可欠な認証レイヤーにはいくつか選択肢があります。まずはここから見ていきましょう。たとえば、OAuth2 を使用する Azure Active Directory Graph API を、Node.js と組み合わせて使用できます。Node.js に関しては、Azure Search の URL エンドポイントへのプロキシとして使用でき、サービスにある程度の構造とコントロールを提供します。

大規模アーキテクチャにおける Azure Search
図 2 大規模アーキテクチャにおける Azure Search

Azure Search が優れている点の 1 つは、写真、画像、動画を除く、構造化されているほぼすべてのデータにインデックスを付け、検索できることです。図 2 には、Azure Search に使用可能なデータ ソースの例をいくつか示しています。リレーショナル データベースは全文検索の実行にはあまり適さないため、多くの場合、最初にリレーショナル データを取得し、キー データを Azure Search にエクスポートします。これにより、リレーショナル データベースで全文検索を実行する負荷が取り除かれるため、これもメリットの 1 つです。

だからといって、Azure Search が万能というわけではありません。データをマイニングする場合は、クラスタリングなどの機械学習を適用するために、依然として Hadoop や HDInsight のようなマップリデュース テクノロジを使用して、テキスト ドキュメントをトピック別に関連ドキュメントにグループ化する必要があります。

ツイートを分析し、そのツイートが他のカテゴリに属する可能性を評価するとします。たとえば、Rants という感情的な非難を表すカテゴリと、Raves という好意的な意見を表すカテゴリがあるとします。このような洞察を行う場合、線形分類器アルゴリズムが使われることがよくあります。Azure Search では、このアルゴリズムを使えません。ただし、Azure Search でドキュメントにインデックスを付ける方法を考える場合は、データを分類および分析する方法を基にします。

Node.js

今度は、Node.js フロント エンドの作成について考えます。このフロント エンドは、Azure Search 前面のプロキシ レイヤーとして使用します。

  • ステップ 1: 前述のチュートリアルに従って、ポータルで Azure Search のプロビジョニングを行います。URL と API キーが必要です。
  • ステップ 2: 開発用コンピューターに Node.js ランタイムをダウンロードし、ローカルにインストールします。Node.js は nodejs.org\download で見つかります。今回の例では c:\program files\nodejs フォルダーにインストールしました。作業を進める前に、Node.js で基本的な "hello world" を実行することをお勧めします。
  • ステップ 3: Node Package Manager (NPM) を正しくインストールし、構成します。NPM によって、NPM レジストリで利用可能な Node.js アプリ (JavaScript) をインストールできるようになります。
  • ステップ 4: elasticsearch パッケージをインストールします。これにより、Azure Search と通信するコード作成が簡単になります。

ステップ 4 まで完了したら、コマンド プロンプトに戻り、コードの作成を開始するディレクトリに移動します。NPM でエラーが発生する場合は、一部の環境変数を検証する必要があります。

C:\node>set nodejs=C:\Program Files\nodejs\
C:\node>set node_path=C:\Program Files\nodejs\node_modules\*
C:\node>set npm=C:\Program Files\nodejs\

Node.js ソリューションの作成

これで、Node.js コードを開発し、ローカル システム上でコードを実行し、Azure Search と通信する準備が整いました。Node.js によって Azure Search へのデータの挿入とクエリが容易になります。terkaly.search.windows.net という Azure Search URL があるとします。この URL は Azure の管理ポータルから入手したものです。API キーも必要です。今回の例では B7D12B8CA3D018EC09C754F95CA552D2 とします。

Node.js アプリをローカルのコンピューターで開発する方法は 1 つだけではありません。Visual Studio のデバッガーを利用する場合は、Node.js Tools for Visual Studio プラグイン (nodejstools.codeplex.com、英語) を使用します。コマンド ラインを利用する場合は、Nodejs.org を確認します。Node.js のインストール後は、NPM を統合することが重要です。これにより、NPM レジストリで利用可能な Node.js アプリ (JavaScript) をインストールできるようになります。ここで使用するコア パッケージを request と呼びます。

図 3 のコードはわかりやすいコードです。このコードで行っていることは、bit.ly/1Ilh6vB (英語) のチュートリアルで説明されていることとほぼ同じです。唯一違う点は、コードと Node.js を request パッケージを使用して実装している点です。今回のコードは、インデックスの作成、データの挿入、クエリの実行など、一般的なユース ケースのいくつかに対応しています。以下のコードにはスキーマを定義し、データを挿入し、データをクエリするコールバックが多数あります。

図 3 インデックスの作成、データの挿入、およびデータのクエリの方法を示す Node.js コード

var request = require('request');
//////////////////////////////////////////////////
// OPTIONS FOR HTTP PUT
// Purpose:    Used to create an index called hotels
//////////////////////////////////////////////////
var optionsPUT = {
  url: 'https://terkaly.search.windows.net/indexes/hotels?api-version=2014-07-31-Preview',
  method: 'PUT',
  json: true,
  headers: {
    'api-key': 'B7D12B8CA3D018EC09C754F95CA552D2',
    'Content-Type': 'application/json'
  },
  body: {
    "name": "hotels",
    "fields": [
      { "name": "hotelId", "type": "Edm.String", "key": true, "searchable": false },
      { "name": "baseRate", "type": "Edm.Double" },
      { "name": "description", "type": "Edm.String", "filterable": false, 
        "sortable": false,
        "facetable": false, "suggestions": true },
      { "name": "hotelName", "type": "Edm.String", "suggestions": true },
      { "name": "category", "type": "Edm.String" },
      { "name": "tags", "type": "Collection(Edm.String)" },
      { "name": "parkingIncluded", "type": "Edm.Boolean" },
      { "name": "smokingAllowed", "type": "Edm.Boolean" },
      { "name": "lastRenovationDate", "type": "Edm.DateTimeOffset" },
      { "name": "rating", "type": "Edm.Int32" },
      { "name": "location", "type": "Edm.GeographyPoint" }
    ]
  }
};
//////////////////////////////////////////////////
// OPTIONS FOR HTTP POST
// Purpose: Used to insert data  
//////////////////////////////////////////////////
var optionsPOST = {
  url: 'https://terkaly.search.windows.net/indexes/hotels/docs/
    index?api-version=2014-07-31-Preview',
  method: 'POST',
  json: true,
  headers: {
    'api-key': 'B7D12B8CA3D018EC09C754F95CA552D2',
    'Content-Type': 'application/json'
  },
  body: {
    "value": [
    {
      "@search.action": "upload",
      "hotelId": "1",
      "baseRate": 199.0,
      "description": "Best hotel in town",
      "hotelName": "Fancy Stay",
      "category": "Luxury",
      "tags": ["pool", "view", "wifi", "concierge"],
      "parkingIncluded": false,
      "smokingAllowed": false,
      "lastRenovationDate": "2010-06-27T00:00:00Z",
      "rating": 5,
      "location": { "type": "Point", "coordinates": [-122.131577, 47.678581] }
    },
    {
      "@search.action": "upload",
      "hotelId": "2",
      "baseRate": 79.99,
      "description": "Cheapest hotel in town",
      "hotelName": "Roach Motel",
      "category": "Budget",
      "tags": ["motel", "budget"],
      "parkingIncluded": true,
      "smokingAllowed": true,
      "lastRenovationDate": "1982-04-28T00:00:00Z",
      "rating": 1,
      "location": { "type": "Point", "coordinates": [-122.131577, 49.678581] }
    },
    {
      "@search.action": "upload",
      "hotelId": "3",
      "baseRate": 279.99,
      "description": "Surprisingly expensive",
      "hotelName": "Dew Drop Inn",
      "category": "Bed and Breakfast",
      "tags": ["charming", "quaint"],
      "parkingIncluded": true,
      "smokingAllowed": false,
      "lastRenovationDate": null,
      "rating": 4,
      "location": { "type": "Point", "coordinates": [-122.33207, 47.60621] }
    },
    {
      "@search.action": "upload",
      "hotelId": "4",
      "baseRate": 220.00,
      "description": "This could be the one",
      "hotelName": "A Hotel for Everyone",
      "category": "Basic hotel",
      "tags": ["pool", "wifi"],
      "parkingIncluded": true,
      "smokingAllowed": false,
      "lastRenovationDate": null,
      "rating": 4,
      "location": { "type": "Point", "coordinates": [-122.12151, 47.67399] }
    }
    ]
  }
};
//////////////////////////////////////////////////
// OPTIONS FOR HTTP GET
// Purpose:    Used to do a perform a query
//////////////////////////////////////////////////
var optionsGET = {
  url: 'https://terkaly.search.windows.net/indexes/hotels/
    docs?search=motel&facet=category&facet=rating,
    values:1|2|3|4|5&api-version=2014-07-31-Preview',
  method: 'GET',
  json: true,
  headers: {
    'api-key': 'B7D12B8CA3D018EC09C754F95CA552D2',
    'Content-Type': 'application/json'
  },
  body: {
  }
};
request(optionsPUT, callbackPUT);
//////////////////////////////////////////////////
// Purpose:    Used to create an index
// Http Verb:  PUT
// End Result: Defines an index using the fields
// that make up the index definition.
//////////////////////////////////////////////////
function callbackPUT(error, response, body) {
  if (!error) {
    try {
      if (response.statusCode === 204) {
          console.log('***success in callbackPUT***');
          request(optionsPOST, callbackPOST);
      }
    } catch (error2) {
      console.log('***Error encountered***');
      console.log(error2);
    }
  } else {
    console.log('error');
    console.log(error);
  }
}
//////////////////////////////////////////////////
// Purpose:    Used to insert data
// End Result: Inserts a document
//////////////////////////////////////////////////
function callbackPOST(error, response, body) {
  if (!error) {
    try {
      var result = response.request.response.statusCode;
      if (result === 200) {
          console.log('***success in callbackPOST***');
          console.log("The statusCode = " + result);
        // Perform a query
        request(optionsGET, callbackGET);
      }
    } catch (error2) {
      console.log('***Error encountered***');
      console.log(error2);
    }
  } else {
    console.log('error');
    console.log(error);
  }
}
//////////////////////////////////////////////////////////////
// Purpose:    Used to retrieve information
// Http Verb:  GET
// End Result: Query searches on the term "motel" and retrieves
// facet categories for ratings.
//////////////////////////////////////////////////////////////
function callbackGET(error, response, body) {
  if (!error) {
    try {
      var result = response.request.response.statusCode;
      if (result === 200) {
          result = body.value[0];
          console.log('description = ' + result.description);
          console.log('hotel name = ' + result.hotelName);
          console.log('hotel rate = ' + result.baseRate);
      }
      console.log('***success***');
    } catch (error2) {
      console.log('***Error encountered***');
      console.log(error2);
    }
  } else {
    console.log('error');
    console.log(error);
  }
}

コールバック チェーンもわかりやすいものです。簡単な GET から始まり、PUT、POST へ移動後、2 つめの GET (クエリ付き) に移動します。これは、Azure Search で使用するコード操作の例を表しています。最初に、後で追加するドキュメント用のスキーマを作成します。HTTP 動詞の PUT を使用してスキーマを定義します。次に POST を使ってデータを挿入します。最後に GET を使用してデータをクエリします。

まとめ

今回の目的は、新興の分野、特にソーシャル ネットワーク分析の分野で起こっている興味深い要素をいくつか紹介することでした。Azure Search は、Web サイトと Web アプリを統合するために、洗練され、かつ強力な検索機能が必要になる大規模ソリューションの一部として使用できます。Azure Search のきめ細かいランク付けモデルを使えば、検索結果を、信頼性の高い処理能力やストレージにはもちろん、ビジネス目標にも結び付けることができます。


Bruno Terkaly は、業界をリードするアプリケーションやサービスを複数のデバイス向けに開発できるようにすることを目標にするマイクロソフトのプリンシパル ソフトウェア エンジニアです。テクノロジが実現可能かどうかという視点を越え、米国で最高のクラウド商談やモバイル商談を進めることを担当しています。ISV が評価、開発、配置を行う際に、アーキテクチャに関するガイダンスを提供したり、技術的な細かいサポート作業を行うことによって、パートナーがアプリケーションを市場に投入できるようにサポートしています。また、フィードバックを提供したり、ロードマップに影響を与えて、クラウドやモバイルのエンジニアリング グループと密接に連携することも行っています。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Liam Cavanagh、Simon Gurevich、Govind Kanshi、Raj Krishnan、Venugopal Latchupatula、Eugene Shvets に心より感謝いたします。