日本語全文検索(Google App Engine Search API)
日本語で全文検索可能なSearch APIについて説明します。
実際に動くサーブレットと運用まで
はじめに
すでに登録しているデーターを検索できるわけではありません。
別途 検索データ(インデックス)を作成して、そのデーターを検索します。
Search APIにかかる料金
公式サイトのSearch API Price(2014.01.31版)
ドル円100円で計算 | 
Free(無料枠) 1日当たり | Free(無料枠) 月間 | 料金 1日当たり | 料金 月間 | 100円でできること | 
|---|---|---|---|---|---|
| 基本操作(Other API Calls) | 1千回 | 1万回につき10円 | 10万回の基本操作(1日当たり) | ||
| ドキュメントの追加(Adding documents to Indexes) | 10MB | 1GBにつき200円 | 500MBの送信 | ||
| ドキュメントの保存容量(Total Storage) | 250MB | 1GBにつき18円 | 約5.5GBの保存(月間) | ||
| 複雑な検索(Simple Queries) | 100回 | 1万回につき60円 | 約1万6千回の複雑な検索 | ||
| 簡単な検索(Complex Queries) | 1千回 | 1万回につき13円 | 約7万6千回の簡単な検索 | 
基本操作
ほんんど何をやっても1回は発生します。 あと、送信と削除をすると、ドキュメントの数だけ発生します。
ドキュメントの送信
インデックスにPUTすると発生します。
元のテキストの大きさは関係なく、テキストの内容で容量は変化します。
間違ってGWTファイルを送るとひどい目にあります。 あとHTMLをsetText()として登録してもインデックスが肥大化します。
ただし、毎日リセットされます。蓄積されません。
初期の大量データーを登録時にのみ、数十円の出費は仕方ありません。 
 また登録データを消したからと言って減るものではありません。減るのはドキュメントの保存容量の方です。 
ドキュメントの保存容量
元のドキュメントと、作成されたインデックスの双方の容量です。
複雑な検索
未テスト
簡単な検索
Queryもlimitぐらいなら、簡単な検索で収まりました。
無料枠内で運用できるか?
アクセスが1日に1千回のサイト(無料枠で運用できるウェブサイト・サービス)で、検索が1千回超えることはないだろう。 更新内容も数十件程度だし、ドキュメントの送信も問題にならないでしょう。
ただし、初めて使う際にホームページの内容をすべて登録する際に、無料枠を超える可能性が高い。(HTML100ページ 700kbぐらいでも、10M以上になることもある)
費用は、数十円程度だが、有料化していないと、面倒だ。
HTMLを間違ってテキストとして登録するだけで、軽く無料枠を超えることもある。
(元のテキストが小さくでも、インデックスに対応したデーター量は数十倍に膨れ上がる)
元のテキスト
My name is aki.
インデックスに登録後(想像)
My
My name
My name is
name is
name is aki
...中略
is
aki.
使い方
注意事項
一度作成したSearch-Indexを削除する方法は無さそうです。 (将来修正されるでしょう:stackoverflow質問 および 公式issueを確認してみてください。)
無駄に作りまくったり、変な名前付けると悲しい目に合うでしょう。
その他にも、公式issueが多々あります。
テキストの登録 テキストはドキュメントクラスを作成して登録します。
ドキュメントクラスはインデックスクラスに追加します。 手順は
- インデックスの取得
 - ドキュメントの作成
 - ドキュメントの追加
 
インデックスの取得
登録するためのIndexを取得します。
IndexSpec indexSpec = IndexSpec.newBuilder().setName(HTML_DB).build();
Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
ドキュメントの作成
DocumentBuilderで作成します。 ドキュメントにはユニークなID((IDはアスキーのみ)および、項目フィールドを作成します。 項目フィールドには、TextあるいはHTML(タグは無視)として設定します。
 Document document = Document.newBuilder().setId(entity.getPath()).addField(Field.newBuilder().setName("text").setHTML(text))
     .addField(Field.newBuilder().setName("title").setText(title)).build();
ドキュメントの追加
同じIDがあれば上書きされます
この時に、indexing documents/Adding documents to Indexesが発生します。 
try {
index.put(document);
} catch (PutException e) {
if (StatusCode.TRANSIENT_ERROR.equals(e.getOperationResult().getCode())) { 
System.out.println("TRANSIENT_ERROR"); } }
全文検索
- インデックスを取り出す。
 - 検索する
 - 返って来たドキュメントを処理する
 
インデックスを取り出す
Index index=SearchServiceFactory.getSearchService().getIndex(IndexSpec.newBuilder().setName(HTML_DB).build());
検索する
単純な検索な場合、文字を渡すだけでいいです。
ただ内部で処理される文字がいくつかあるようです。
  Results<ScoredDocument> result= index.search(query);
ドキュメントを処理する
返って来たリザルトから、ドキュメントを処理して出力内容を作成します。
   Results<ScoredDocument> result= index.search(query); 
   String ret="search="+query+"<hr/>";
    for(ScoredDocument doc:result.getResults()){ 
    String title=doc.getOnlyField("title").getText();
    String id=doc.getId();
    String alink="<a href='"+id+"'>"+title+"</a>"; ret+=alink+"<br/>";
    }
登録ドキュメントの削除
まとめて削除する方法はありません。 一つづつ削除します。
インデックスを取得します。
 IndexSpec indexSpec = IndexSpec.newBuilder().setName(HTML_DB).build(); 
 Index index = SearchServiceFactory.getSearchService().getIndex(indexSpec);
すべて消す場合。検索して取り出します。
 QueryOptions options = QueryOptions.newBuilder() .setLimit(300).setReturningIdsOnly(true).build(); 
 com.google.appengine.api.search.Query query = com.google.appengine.api.search.Query.newBuilder() .setOptions(options) .build(""); 
 Results<ScoredDocument> result =index.search(query);
IDを渡して消します。IDがわかっている場合、requestからidを取り出して、そのままIndexに渡してもいい。
 for(ScoredDocument doc:result){
  index.delete(doc.getId()); 
  }
実例サーブレット
Google App Engineで全文検索サーブレットの例 - 公式のサンプル見ずに作ったのでIndexをstatic化してないなど欠陥が多少有ります。いずれ修正します。
運用
処理はそこそこ重いので、Entityを更新した時にではなく Backendsサービスをcronから数時間おきに呼び出して、まとめてインデックスを更新します。 処理内容としては
- 前回の更新日時を調べる
 - それ以降に追加されたコンテンツを取り出す。
 - それらをまとめて更新する
 - 現在の時間を保存する
 
具体例は、Google App Engineで全文検索サーブレットの例 のdoUpdate()
エラー
これまでに経験したエラー内容を列挙しておきます。
いずれ改善策も示したいです。
java.lang.IllegalArgumentException: Document id must be ASCII visible printable:
ドキュメントのidに日本語が有るとき
com.google.appengine.api.search.SearchQueryException: Unable to parse query:
<から検索のqueryに渡すテキストが始まる時
com.google.apphosting.api.ApiProxy$OverQuotaException: The API call search.IndexDocument() required more quota than is available.
Quotaを超えて規制状態になっている時に、ドキュメントをputした時