2019 年 4 月
Volume 34 Number 4
[.NET]
独自のエンタープライズ検索の実装
検索はできて当たり前だと思っていませんか。検索は、次の旅行の宿泊先を見つけたり、仕事に必要な情報を探したりなど、日々のあらゆる作業で使用されます。検索が適切に実装されていれば、コストを節約できるだけでなく、利益を出すことも可能です。アプリがどんなに優れていても、検索に問題があれば、満足のいくユーザー エクスペリエンスを提供できません。
問題は、検索が重要であるにもかかわらず、見つからないか機能しない場合にしか意識されず、最も誤解されているの IT 機能の 1 つだということです。しかし、検索は必ずしも実装の難しい正体不明の機能ではありません。この記事では、C# でエンタープライズ検索 API を開発する方法について説明します。
検索を理解するには、検索エンジンが必要です。検索エンジンには、オープン ソースから商用までさまざまなバリエーションがあり、その多くが優れた情報検索ライブラリである Lucene を内部で利用しています。この中には Azure Search、ElasticSearch、Solr も含まれます。今回は Solr を使用します。その理由は、長い間利用され、十分なドキュメントがあり、コミュニティが活発で、著名なユーザーが多いからです。私自身も、小規模なサイトから大企業向けまで、多くのアプリケーションで検索を実装するために Solr を使用しました。
Solr の入手
検索エンジンのインストールというと、複雑な作業のように聞こえますが、実際、サポートする必要がある 1 秒あたりのクエリ数 (QPS) が非常に大きい本番環境の Solr インスタンスをセットアップする作業は、確かに複雑です (QPS は、検索のワークロードを参照する際に使われる一般的なメトリックです)。
このような作業の手順については、Solr のドキュメントの「Installing Solr (Solr のインストール)」(lucene.apache.org/solr)、および『Apache Solr Reference Guide (Apache Solr リファレンス ガイド)』の「The Well-Configured Solr Instance (適切に構成された Solr インスタンス)」(bit.ly/2IK7mqY) で詳しく説明されています。ここでは、開発用の Solr インスタンスがあれば十分なので、bit.ly/2tEXqoo にアクセスしてバイナリ リリースを入手します (solr-7.7.0.zip が良いでしょう)。
.zip ファイルをダウンロードして解凍し、コマンド ラインを開いて、ファイルを解凍したフォルダーのルートに移動します。あとは、次のコマンドを発行します。
> bin\solr.cmd start
これだけです。http://localhost:8983 にアクセスすると、図 1 のような Solr Admin UI が表示されます。
図 1. 検索エンジンの起動
データの入手
次に、データが必要ですね。面白そうなデータを含むデータセットはいろいろありますが、何千人もの開発者がファイルへの書き込み方法がわからなかったり、Vim を終了できなかったり、特定の問題を解決する C# スニペットが必要だったりして、毎日のように StackOverflow を利用しています。さいわい、この StackOverflow のデータを XML ダンプとして入手することができます (bit.ly/1GsHll6)。この中には、約 1,000 万件の質問と回答、タグ、バッジ、匿名化されたユーザー情報などが含まれています。
さらに、StackExchange サイトの数千件の質問を含む小規模なデータセットを同じ形式で入手することもできます。まずはこの小規模なデータセットでテストして、その後より多くのデータを処理できるようにインフラストラクチャを強化することができます。
ここでは、最初に約 25,000 件の投稿を含む datascience.stackexchange.com のデータを使用します。ファイル名は datascience.stackexchange.com.7z です。これをダウンロードして、Posts.xml を抽出します。
重要な概念
検索エンジンとデータの準備ができたので、ここで重要な概念をいくつか確認しておきましょう。リレーショナル データベースを使い慣れているユーザーにはおなじみのはずです。
インデックスは、検索エンジンが検索のために収集したすべてのデータを格納する場所です。大まかに言うと、Solr はいわゆる転置インデックスにデータを格納します。転置インデックスでは、単語 (またはトークン) が特定のドキュメントを指しています。特定の単語を検索すると、それがクエリになります。インデックスで単語が見つかる (ヒットまたは一致する) と、その単語がどのドキュメントのどの場所に含まれているかがわかります。
スキーマは、データの構造を指定するしくみです。各レコードはドキュメントと呼ばれ、データベースの列を定義するのと同じように、検索エンジンではドキュメントのフィールドを指定します。
スキーマを作成するには、schema.xml という名前の XML ファイルを編集して、フィールドをその型とともに定義します。ただし、不明なフィールドを追加したときに新しいフィールドが自動的に作成される動的なフィールドを使用することもできます。これは、データ内に存在するすべてのフィールドを完全には把握していない場合に便利です。
既にお気づきかもしれませんが、非正規化され、ドキュメントのコレクション間で必ずしも整合しないフィールドを含むドキュメントを格納できるという点で、Solr は NoSQL ドキュメント ストアとよく似ています。
スキーマレス モードを使用してインデックス内のデータをモデル化する方法もあります。この場合は、インデックスを作成するフィールドを Solr で明示的に指定しません。代わりに、インデックスにデータを追加すると、追加されたデータの型に応じて Solr がスキーマを作成します。スキーマレス モードを使用するには、フィールドを手動で編集できないマネージド スキーマを使用する必要があります。これは、データ探索の段階や概念実証の場合に特に便利です。
ここでは、手動で編集したスキーマを使用します。スキーマを細かく制御できるため、本番環境ではこの方法をお勧めします。
インデックス作成は、インデックスにデータを追加するプロセスです。インデックスの作成中、アプリケーションはさまざまなソースからデータを読み取り、取り込み用のデータを準備します。このデータは、ドキュメントを追加するために使用される単語で、フィードという用語で呼ばれることもあります。
ドキュメントのインデックス作成が完了すると、個々の検索に対して最も関連性の高いドキュメントを最上位で返す検索が可能になります。
関連性ランキングは、最も適切な検索結果を最上位で返すしくみです。これは説明しやすい概念です。理想的な状況としては、ユーザーがクエリを実行すると、検索エンジンがユーザーの心理を「読み取り」、ユーザーが探していたドキュメントを正確に返します。
適合率と再現率: 適合率は、関連性のあるデータが返された結果の何割を占めるか (結果の品質) を表します。再現率は、関連性のあるデータ全体の何割が返された結果に含まれているか (結果の分量または完全性) を表します。
アナライザー、トークナイザー、フィルター: データのインデックス作成や検索を行うと、アナライザーがテキストを検査し、トークン ストリームを生成します。続いてフィルターによる変換が適用され、インデックス処理されたデータとクエリが照会されます。検索エンジン テクノロジの詳細を知るには、こうした概念を理解する必要があります。さいわい、アプリケーションの作成を始めるには、概要を理解しておくだけで十分です。
データについて
インデックスをモデル化するには、データを理解する必要があります。そのため、Posts.xml を開いて分析します。
データは次のようになっています。posts ノードには、多くの row 子ノードが含まれています。1 つの子ノードが 1 つのレコードに対応し、フィールドは子ノード内の属性としてエクスポートされています。このデータは検索エンジンに取り込める十分にクリーンな状態であり、特に問題ありません。
<posts>
<row Id=”5” PostTypeId=”1” CreationDate=”2014-05-13T23:58:30.457”
Score=”9” ViewCount=”448” Body=”<Contains the body of the question or answer>”
OwnerUserId=”5” LastActivityDate=”2014-05-14T00:36:31.077”
Title=”How can I do simple machine learning without hard-coding behavior?”
Tags=”<machine-learning,artificial-intelligence>;” AnswerCount=”4”
CommentCount=”5” FavoriteCount=”1”
ClosedDate=”2014-05-14T14:40:25.950” />
...
</posts>
さまざまな型のフィールドが含まれていることが一目でわかります。一意識別子、日付、数値フィールド、さまざまな長さのテキスト フィールド、メタデータ フィールドなどがあります。わかりやすくするため、長いテキスト フィールドである Body は編集でカットしていますが、それ以外はすべて元の状態のままです。
従来型スキーマを使用する Solr の構成
インデックスのデータ構造を指定するには、いくつかの方法があります。既定では、Solr はマネージド スキーマ (つまり、スキーマレス モード) を使用します。しかし、ここでは手動でスキーマを作成したいので (これを従来型スキーマと呼びます)、構成をある程度変更する必要があります。まず、インデックス構成を保存するフォルダーを作成し、msdnarticledemo という名前を付けます。このフォルダーは <solr>\server\solr\ にあります (<solr> は Solr を解凍したフォルダーです)。
次に、このフォルダーのルートに core.properties という名前のテキスト ファイルを作成し、このファイルに name=msdnarticledemo という行だけを追加します。このファイルは、Lucene インデックスの実行インスタンスである Solr コアを作成するために使用されます。ワード コレクションという呼び方もありますが、これは文脈によって意味が異なる場合があります。ここでは、「コア」をインデックスと同じ意味で使用します。
次に、クリーンなサンプル インデックスの内容をベースとして使用するためにコピーする必要があります。Solr のサンプル インデックスは <solr>\server\solr\configsets\_default にあります。この conf フォルダーを msdnarticledemo にコピーします。
次の手順が非常に重要です。従来型スキーマを使用するように (つまり、スキーマを手動で編集できるように) Solr を設定します。そのためには、solrconfig.xml を開き、次の行を追加します。
<schemaFactory class=”ClassicIndexSchemaFactory”/>
さらに、このファイルを開いた状態で 2 つのノードをコメントアウトします。updateRequestProcessorChain with:
name=”add-unknown-fields-to-the-schema”
and updateProcessor with:
name=”add-schema-fields”
この 2 つは、Solr がスキーマレス モードでデータのインデックスを作成しながら新しいフィールドを追加するための機能です。また、xml のコメント内では「--」を使用できないため、この xml ノード内のコメントを削除します。
最後に、managed-schema のファイル名を schema.xml に変更します。これで、手動で作成されたスキーマを Solr で使用する準備ができました。
スキーマの作成
次に、スキーマ内のフィールドを定義します。schema.xml を開き、id、_text_、_root_ の定義がある部分まで下へスクロールします。
各フィールドは、次の属性を含む <field> XML ノードとして定義されています。
- name:各フィールドの名前です。
- type:フィールドの型です。各型の処理方法を schema.xml 内で変更できます。
- indexed:true にすると、このフィールドを検索で使用できます。
- stored:true にすると、このフィールドを表示用に返すことができます。
- required:true にした場合は、このフィールドのインデックスを作成する必要があります。インデックスがない場合は、エラーが発生します。
- multiValued:true にすると、このフィールドに複数の値を含めることができます。
最初は戸惑うかもしれませんが、これらの属性を利用すると、インデックスの作成後に表示できても検索できないフィールドや検索できても取得できないフィールドを設定することができます。他にも高度な属性がありますが、ここでは詳しい説明を省略します。
図 2 に、投稿用のスキーマのフィールド定義を示します。テキストの型として、string、text_get_sort、text_general などがあります。検索の主な目的はテキスト検索なので、さまざまなテキストに対応する型を用意しています。さらに、日付、整数、浮動小数点、複数値を保持するフィールド (タグ) もあります。
図 2. schema.xml 内のフィールド
<field name=”id” type=”string” indexed=”true” stored=”true”
required=”true” multiValued=”false” />
<field name=”postTypeId” type=”pint” indexed=”true” stored=”true” />
<field name=”title” type=”text_gen_sort” indexed=”true”
stored=”true” multiValued=”false”/
<field name=”body” type=”text_general” indexed=”false”
stored=”true” multiValued=”false”/>
<field name=”tags” type=”string” indexed=”true” stored=”true”
multiValued=”true”/>
<field name=”postScore” type=”pfloat” indexed=”true” stored=”true”/>
<field name=”ownerUserId” type=”pint” indexed=”true” stored=”true” />
<field name=”answerCount” type=”pint” indexed=”true” stored=”true” />
<field name=”commentCount” type=”pint” indexed=”true” stored=”true” />
<field name=”favoriteCount” type=”pint” indexed=”true” stored=”true” />
<field name=”viewCount” type=”pint” indexed=”true” stored=”true” />
<field name=”creationDate” type=”pdate” indexed=”true” stored=”true” />
<field name=”lastActivityDate” type=”pdate” indexed=”true” stored=”true” />
<field name=”closedDate” type=”pdate” indexed=”true” stored=”true” />
次の作業は実装によって異なります。さしあたり、フィールドの数が多いため、検索できるフィールドと、そのフィールドが他のフィールドに比べてどの程度重要かを指定できます。
ここではまず、汎用のフィールド _text_ を使用してすべてのフィールドを検索します。copyField を作成し、すべてのフィールドのデータを既定のフィールドにコピーするように Solr を設定します。
<copyField source=”*” dest=”_text_”/>
検索を実行すると、Solr はこのフィールドの内容を調べ、クエリと一致するドキュメントを返します。
次に、次のコマンドを実行して Solr を再起動すると、コアが読み込まれ、変更が適用されます。
> bin\solr.cmd restart
これで、C# アプリケーションの作成を開始する準備ができました。
SolrNet の入手とデータのモデル化
Solr には、アプリケーションから簡単に使用できる REST に似た API が用意されています。さらに、Solr を抽象化する SolrNet というライブラリも用意されています (bit.ly/2XwkROA)。このライブラリは、厳密に型指定されたオブジェクトを簡単に操作でき、検索アプリケーションの開発を効率化するさまざまな機能を備えています。
SolrNet を入手するには、NuGet から SolrNet パッケージをインストールするのが最も簡単です。ここでは、Visual Studio 2017 を使用して作成した新しいコンソール アプリケーションにこのライブラリを組み込みます。他の転置制御メカニズムや追加機能を使用する場合に必要な SolrCloud など、追加のパッケージをダウンロードすることもできます。
このコンソール アプリケーションでは、インデックス内のデータをモデル化する必要があります。この作業はとても簡単です。図 3 に示した Post.cs という名前の新しいクラス ファイルを作成するだけです。
図 3. 投稿ドキュメントのモデル
class Post
{
[SolrUniqueKey(“id”)]
public string Id { get; set; }
[SolrField(“postTypeId”)]
public int PostTypeId { get; set; }
[SolrField(“title”)]
public string Title { get; set; }
[SolrField(“body”)]
public string Body { get; set; }
[SolrField(“tags”)]
public ICollection<string> Tags { get; set; } = new List<string>();
[SolrField(“postScore”)]
public float PostScore { get; set; }
[SolrField(“ownerUserId”)]
public int? OwnerUserId { get; set; }
[SolrField(“answerCount”)]
public int? AnswerCount { get; set; }
[SolrField(“commentCount”)]
public int CommentCount { get; set; }
[SolrField(“favoriteCount”)]
public int? FavoriteCount { get; set; }
[SolrField(“viewCount”)]
public int? ViewCount { get; set; }
[SolrField(“creationDate”)]
public DateTime CreationDate { get; set; }
[SolrField(“lastActivityDate”)]
public DateTime LastActivityDate { get; set; }
[SolrField(“closedDate”)]
public DateTime? ClosedDate { get; set; }
}
これはインデックス内の個々のドキュメントを表す単純な従来の CLR オブジェクト (POCO) ですが、各プロパティのマップ先となるフィールドを SolrNet に指示する属性が追加されています。
検索アプリケーションの作成
検索アプリケーションを作成するときは、通常、次の 2 つの機能を別個に作成します。
インデクサー: これは、最初に作成する必要があるアプリケーションです。簡単に言うと、検索するデータを準備するために、そのデータを Solr にフィードする必要があります。そのためには、複数のソースからデータを読み込み、さまざまな形式のデータを変換して、データが最終的に検索できる状態になるまで処理します。
検索アプリケーション: インデックス内のデータが準備できたら、検索アプリケーションの作業を開始できます。
どちらの場合も、最初の手順としてコンソール アプリケーションで次の行を使用して SolrNet を初期化する必要があります (Solr が実行されていることを確認してください)。
Startup.Init<Post>(“http://localhost:8983/solr/msdnarticledemo”);
次に、アプリケーション内の各機能のクラスを作成します。
インデクサーの作成
ドキュメントのインデックスを作成するには、最初に SolrNet サービス インスタンスを取得して、サポートされている操作を開始できるようにします。
var solr = ServiceLocator.Current.GetInstance<ISolrOperations<Post>>();
次に、Posts.xml の内容を XMLDocument に読み込む必要があります。そのためには、各ノードを反復処理し、新しい Post オブジェクトを作成し、XMLNode から各属性を抽出して、それを対応するプロパティに割り当てます。
情報取得 (検索) のフィールド内では、データが正規化されていないことに注意してください。対照的に、データベースを使用する場合は、通常、重複を避けるためにデータを正規化します。投稿の所有者を追加する代わりに、整数の ID を追加し、別個のテーブルを作成して ID を名前と照合します。しかし、検索では、名前を投稿の一部として追加するため、データが重複します。その理由は、データが正規化されていると、情報取得のために負荷の高い結合を実行する必要があるためです。しかし、検索エンジンの主な目的の 1 つは速度です。ユーザーは、ボタンを押したらすぐに必要な結果が得られると期待しています。
さて、post オブジェクトの作成に戻ります。図 4 では、3 つのフィールドのみを示します (他のフィールドは簡単に追加できます)。Tags が複数値のフィールドであり、例外を避けるために null 値をチェックしていることに注意してください。
図 4. フィールドの設定
Post post = new Post();
post.Id = node.Attributes[“Id”].Value;
if (node.Attributes[“Title”] != null)
{
post.Title = node.Attributes[“Title”].Value;
}
if (node.Attributes[“Tags”] != null){
post.Tags = node.Attributes[“Tags”].Value.Split(new char[] { ‘<’, ‘>’ })
.Where(t => !string.IsNullOrEmpty(t)).ToList();}
// Add all other fields
オブジェクトを設定したら、Add メソッドを使用して各インスタンスを追加できます。
solr.Add(post);
または、投稿のコレクションを作成し、AddRange を使用して一括で投稿を追加することもできます、
solr.AddRange(post_list);
どちらの方法でも問題ありませんが、多くの本番環境の配置では、100 個のドキュメントを一括で追加したときにパフォーマンスが向上することが確認されています。ドキュメントを追加しただけでは、まだ検索できません。次のようにコミットする必要があります。
solr.Commit();
これを実行したときの処理時間は、インデックスを作成するデータの量と処理を実行するマシンによって、数秒から数分の範囲で異なります。
処理が完了したら、Solr Admin UI に移動し、中央左の [Core Selector] というドロップダウンを探して、該当するコア (msdnarticledemo) を選択します。[Overview] タブに、インデックスを作成したドキュメントの数を示す統計情報が表示されます。
データ ダンプには 25,488 件の投稿が含まれていましたが、表示された数値と一致しています。
Statistics
Last Modified: less than a minute ago
Num Docs:25488
Max Doc:25688
これでインデックス内のデータが準備できたので、検索側の作業をいつでも開始できます。
Solr での検索
Visual Studio に戻る前に、Solr の Admin UI で簡単な検索を実行し、利用可能なパラメーターについて説明しましょう。
msdnarticledemo コアで、[Query] をクリックして、下部にある [Execute Query] という青いボタンを押します。そうすると、図 5 に示すように、すべてのドキュメントが JSON 形式で返されます。
図 5. Solr の Admin UI によるクエリ
では、具体的にどのような処理が行われ、なぜインデックス内のすべてのドキュメントが返されたのでしょうか。答えは簡単です。上部の [Request-Handler (qt)] 列のパラメーターをご覧ください。ご覧のように、q というパラメーターがあり、その値は *:* です。 すべてのドキュメントが返されたのは、このパラメーターのせいです。実際、実行したクエリは、キーと値のペアを使用してすべてのフィールドですべての値を検索するものでした。Title で Solr を検索するだけの場合、q の値は Title:Solr になります。
これは、(当然必要な処理である) 各フィールドに異なる重みを設定する複雑なクエリを作成する場合にとても便利です。Title に含まれる単語や語句は、内容に含まれるものより重要です。たとえば、ドキュメントの Title に「Enterprise Search」が含まれている場合は、ドキュメント全体がエンタープライズ検索に関連している可能性が高くなります。しかし、この語句がドキュメントの Body の一部に含まれている場合は、ドキュメントとあまり関係のない話題に言及しているだけかもしれません。
q パラメーターは、スコアを計算して関連性の高い順にドキュメントを取得するので、特に重要です。しかし、Solr による要求の処理方法を構成するために要求ハンドラーで使用できるパラメーターは、フィルター クエリ (fq) や、ソート、フィールド リスト (fl) など、他にもいろいろあります。詳細については、Solr のドキュメント (bit.ly/2GVmYGl) を参照してください。これらのパラメーターを使用して、より複雑なクエリの作成を始めることができます。慣れるのに多少時間がかかりますが、使いこなせるようになると、関連性ランキングの精度が向上します。
Admin UI はアプリケーションから Solr を利用するための手段ではないことに注意してください。その目的には、REST に似たインターフェイスを使用します。検索結果のすぐ上を見ると、灰色のボックスの中に、このクエリの呼び出しを含むリンクが表示されています。このリンクをクリックすると、新しいウィンドウが開き、応答が表示されます。
今回は、次のクエリが生成されました。
http://localhost:8983/solr/msdnarticledemo/select?q=*%3A*&wt=json
SolrNet は、内部でこのような呼び出しを行いますが、.NET アプリケーションから使用できるオブジェクトも提供します。次に、基本的な検索アプリケーションを作成します。
検索アプリケーションの作成
ここでは、用意したデータセットのすべてのフィールドで質問を検索します。データには質問と回答の両方が含まれているので、PostTypeId を (質問を意味する) 1 に設定してフィルター処理します。 そのためには、フィルター クエリ (fq パラメーター) を使用します。
さらに、結果を一度に 1 ページずつ返すために、いくつかのクエリ オプションを設定します。具体的には、検索結果の数を指定する Row とオフセット (開始位置) を指定する StartOrCursor です。そして、当然ながらクエリを設定します。
図 6 に、基本的な検索を実行するのに必要なコードを示します。クエリは検索するテキストです。
図 6. 基本的な検索の実行
QueryOptions query_options = new QueryOptions
{
Rows = 10,
StartOrCursor = new StartOrCursor.Start(0),
FilterQueries = new ISolrQuery[] {
new SolrQueryByField(“postTypeId”, “1”),
}
};
// Construct the query
SolrQuery query = new SolrQuery(keywords);
// Run a basic keyword search, filtering for questions only
var posts = solr.Query(query, query_options);
クエリを実行すると、投稿 (SolrQueryResults オブジェクト) が返されます。このオブジェクトには、検索結果のコレクションと、追加機能を提供する複数のオブジェクトを含む多くのプロパティが格納されています。検索結果が返されたので、これをユーザーに表示することができます。
検索結果の絞り込み
多くの場合、最初の検索結果で問題ありませんが、特定のメタデータ フィールドによって結果を絞り込むこともできます。フィールドによってドリルダウンするには、ファセットを使用します。ファセットで、キーと値のペアのリストを取得します。たとえば、Tags を使用して各タグと各タグの出現回数を取得します。ファセットは通常、数値フィールド、日付フィールド、または文字列フィールドとともに使用します。テキスト フィールドの場合は注意が必要です。
ファセットを有効にするには、新しい QueryOption である Facet を追加します。
Facet = new FacetParameters
{
Queries = new[] {
new SolrFacetFieldQuery(“tags”)
}
}
これで、posts.FacetFields[“tags”] からファセットを取得できるようになります。これは、各タグと結果セット内での各タグの出現回数を含むキーと値のペアのコレクションです。
これで、ユーザーが絞り込むタグを選択すると、フィルター クエリによって結果の数が減り、理想としては関連性の高いドキュメントが返されるようになります。
検索の改善: 次の段階
ここまで、C# で StackExchange サイトの質問を使用して Solr と SolrNet による基本的な検索を実装する方法の要点を説明してきました。しかし、これは Solr を使用して関連性の高い結果を返すテクニックを紹介する新しい取り組みの始まりに過ぎません。
次の段階として、個々のフィールドに異なる重みを指定して検索する方法、結果の強調表示によってコンテンツ内の一致部分を示す方法、シノニムを適用して、関連性はあっても検索した単語自体は含まれていない結果を返す方法、ステミング (単語を語幹に絞り込んで再現率を高める方法)、各国のユーザーにとって便利な音声検索などがあります。
全体として、検索の実装方法の習得は開発者にとって将来大きな利益を生む可能性がある貴重なスキルです。
この記事に添えて、図 7 に示すような基本検索プロジェクトを作成しました。このプロジェクトをダウンロードすれば、エンタープライズ検索の詳細を確認できます。検索をお楽しみください!
図 7. サンプル検索プロジェクト
Xavier Morera氏は、エンタープライズ検索とビッグ データを開発者にわかりやすく解説する仕事をしています。Pluralsight や Cloudera でトレーニング コースの開発もしています。長年にわたって Search Technologies (現在は Accenture の一部門) に勤務し、検索の実装に取り組んできました。現在はコスタリカに住み、xaviermorera.com で活動しています。
この記事のレビューに協力してくれた次の技術スタッフに心より感謝いたします。Jose Arias 氏 (Accenture)、Jonathan Gonzalez 氏 (Accenture)
Jose Arias 氏は、検索とビッグ データに関する (特にデータ分析で使用される) テクノロジに情熱を注いでいます。 現在、Accenture の Search & Content Analytics 部門の上級開発者として働いています。https://www.linkedin.com/in/joseariasq/
Jonathan Gonzalez 氏 <(j.gonzalez.vindas@accenture.com)>
Jonathan Gonzalez 氏は、Accenture のシニア管理アーキテクトです。18 年以上にわたってソフトウェアの開発と設計を経験し、ここ 13 年間は情報取得、エンタープライズ検索、データ処理、コンテンツ分析を専門としています。https://www.linkedin.com/in/jonathan-gonzalez-vindas-ba3b1543/