2012年9月3日月曜日

App Engine Full-text Search API の使いどころ

今年になって Google App Engineに Full-text Search(以下FTS)という機能が追加されています。全文検索機能の事で、日本語にも対応しています。リリース前に私も appengine ja night 20 で紹介をしました。その時にも「日本語検索はおまけ、それ以外の機能が強力」という話をしたのですが、それについて少し具体例を交えて説明をしようと思います。

ログインユーザごとのアクセス権限を参照する処理

RDBでは簡単でも、データストアで苦戦することまちがいなしの要件として、次のような要件がよく出てきます。

  1. 企業向けアプリケーション等で、リソースへのアクセス制御をしたい
  2. リソースに対するアクセス許可リスト(ACL)として、ユーザIDだったり、ユーザを束ねたグループIDだったりを定義する
  3. ログインユーザに対して、リソースの一覧を提供する。もちろん、アクセスできるもののみが一覧表示される。

企業向けアプリケーションではまず必要になる、よくある処理だと思います。ここでは企業向けアプリケーションと書いていますが、例えばTwitterなどの、ユーザ間の関係が発生するアプリケーションでも同じような要件が出てきます。なお、ここでいうグループはディレクトリ的なものではなく、一ユーザが複数のグループに所属できるような関係を想定しています。

データストアで実装する

これをデータストアで実装しようとするとなかなか厄介ですが、大きく分けると「保存時に頑張る」「クエリ時に頑張る」の二種類の方針が考えられます。

後者の方針は早々に破綻する可能性があるため(一覧表示くらいならなんとかなるが、なんらかのフィルタがくっつくと処理に相当無理が出るし、固定の件数でページングするのも難しい)、多くの場合は前者の「保存時に頑張る」方法で次のように実現することになると思います。

  • リソースが保存された時に、それにアクセスできると定義されたグループから、ユーザを展開する
  • 展開したユーザ(リソースにアクセスできるユーザ)分だけ、「ユーザごとにアクセスできるリソース」を作成し、保存する。このエンティティはユーザのIDとリソースのIDを保持します。カインド Readable としましょう。
  • ログインユーザがリソース一覧を表示する場合、Readableに対してクエリを発行します。

独自にインデックス用のエンティティを作るということです。この実装方法ですとアクセスの制限が広いリソース(全社員向け、とか!)が保存された場合、ものすごく大量の Readable エンティティが作成・保存されますし、それ以外に面倒な非同期処理も必要になります。例えば、グループ内のユーザが変更された場合や、ユーザが新たに発生した場合です。しかし、「クエリ時に頑張る」実装よりは、ユーザに対して良いパフォーマンスを提供できるので、保存時に頑張る、を選択することが多いのではないかと思われます。

FTSを利用する

FTSを使うとかなり簡単になります。

  1. リソースが保存された時に、リソースの主キーと、アクセスを許可するグループやユーザのIDを多値フィールドとしてFTSのDocumentに追加して、インデクスさせる。DocumentのIDとしては、リソースの主キーをエンコードした文字列を使用しとくと便利です(FTSにもkeysOnlyなオプションがあるのです)。アクセスを許可するグループやユーザのIDは ACL というフィールド名とします。
  2. 例えばログインユーザが g1 ,g2, g3 というグループに所属している u1 というユーザであれば、FTSで「ACL=u1 or ACL=g1 OR ...」とORでつなぐだけ。ソートやフィルタが必要であれば、それらのフィールドをDocumentに追加しておくと良いです。で、取得したリソースのキーを使って、データストアから必要なリソースをバッチGETすればリソース本体も取得できます。それが面倒なら、Documentの方に必要なフィールドを含めても良いでしょう(そうこうするうちにデータストアが不要になった、となる可能性もるかも)

工夫も何もありません、特にめづらしい実装にも思えませんが、FTSを日本語検索ではない目的で使用するのが強力だ、と言っていたのはこういう処理で使うことを想定していたためです。データストアだけでは実装コストもランニングコストも高くなってしまう処理が、工夫もなく簡単に実現できてしまうのです。グループのメンテナンスが発生した場合の整合性を維持する非同期処理は同じように残ってしまいますが、そこはデータストアに対するメンテナンスよりも軽くなるはずです。

これに日本語検索を加えてもいいです。今のところ、FTSを多用する場合の注意点は次のようなものがあるとおもいます。

  • FTSはまだExperimentalなため、APIのRateと保存容量にLimitが存在する上、利用料金も決まっていません。これらふたつのLimitはGooglerに連絡すれば回避できますが、料金はちょっと心配です。 しかし、独自にインデックス用のエンティティを作って実装している場合はWrite Operationsもかなり大きいので、それら程度であれば、実装のコストが削減できる分FTSの方が良いですね。
  • FTSはまだExperimentalなため、不具合が無いとはいいきれません。
  • クエリに使える文字列は 2000文字が限界です. ひとりのユーザが数百のグループに所属…となると、それにひっかかってしまうかもしれません。それが心配な場合は、ユーザやグループのメールアドレスなど長い文字は使わず、別の短い値に変換して使うと良いでしょう。

まとめ

SDK1.7.0 から Datastore に対してもORによるフィルタが指定できるようになりましたが、その機能ですとカーソルを返せない(実際には複数クエリを並列実行してるだけなので、当たり前ですね)という問題があります。

Cloud SQLを組み合わせればFTSなしでも同じことができますが、テスト環境の手間が多少なりとも増えてしまうのがイヤな人にはデメリットです。が、このあたりは好みによりますね、素直にCloud SQLで慣れたRDBの設計・実装を行うのも実装コストを減らす選択肢のひとつです。Cloud SQLはスケールアウトしないという特徴にも注意が必要かも。一社のみで使うアプリケーションであれば良いですが、複数社に提供する業務アプリケーションであれば、ボトルネックになる可能性があります。

おまけ

肝心の日本語検索ですが、 Google検索 >>> Gmail検索 = FTS > Google Docs検索 といったかんじの性能じゃないかなーと感じました。

コメントを投稿