Python
GAE
datastore
searchapi

Google App EngineのDatastoreとSearch APIの連携

More than 3 years have passed since last update.


Google App EngineのDatastoreとSearch APIの連携

Datastoreにデータを保存しつつ、Search APIのIndexに登録したいというシチュエーションはかなりあると思われる。しかし、トランザクションに注意しなければ、Datastoreに保存されているにも関わらず、Search APIのIndex(以下、IndexといえばSearch APIのIndexを指すこととする)に登録されていないということが起こりえてしまう。

そこで、DatastoreとIndexに(ほぼ)同時にデータを登録する方法について考える。


  1. _post_put_hookを使う方法(一貫性が保証されない)

  2. シンプルに@ndb.transacationalを使う方法(一貫性が保証)

  3. Task Queueを使う方法(一貫性が保証)

  4. 結論

1の方法はおすすめしない。ただ、1の方法を使っている例が見受けられる(Stack Overflowで回答として提出されていたのが1つ。そして、Ferris2.2が1の方法使っている。)ため、一応紹介している。


1. _post_put_hookを使う方法

特に理由がない限り、おすすめしない

class User(ndb.Model):

name = ndb.StringProperty()

def _post_put_hook(self, future):
result = future.get_result()
# Search APIのIndexに登録
...

post_put_hookは、Userがputされた後に呼び出される。もし、putが失敗していたら、future.get_result()の部分でエラーが送出されるため、Indexに登録されることはない。しかし、Userの登録が成功したのに、Indexへの登録が失敗する可能性はある。

つまり、この方法ではDatastoreとIndexの一貫性が保証されていない。一貫性が保証されなくてもよいなら問題はないが、そうでないなら避けるべきだ。


2. シンプルに@ndb.transacationalを使う方法

user = User(name='Yohei')

@ndb.transactional
def put():
user.put()
doc = search.Document(
doc_id = person.key.urlsafe(),
fields=[
search.TextField(name='name', value=user.name),
],)
index.put(doc)

put()

これならば一貫性は保証される。しかし、次のような例では気をつけなければいけない。

user = User(name='Yohei')

@ndb.transactional
def put():
user.put()
doc = search.Document(
doc_id = person.key.urlsafe(),
fields=[
search.TextField(name='name', value=user.name),
],)
index.put(doc)
# do something
...

put()

ここでdo somethingの部分でエラーが起こると、Datastoreに登録されないが、Indexには登録されるということが起こりえる。


3. Task Queueを使う方法

Task Queueはtransactionalに処理することができる。1よってTask Queueに「Search APIのIndexへの登録」というタスクを積めば、一貫性が保証できる。

user = User(name='Yohei')

user2 = User(name='Yohei2')

@ndb.transactional(xg=True)
def put():
user.put()
user2.put()
taskqueue.add(
url='/put_to_index',
params={
'key': user.key.urlsafe(),
'name': user.name},
transactional=True,)
taskqueue.add(
url='/put_to_index',
params={
'key': user2.key.urlsafe(),
'name': user2.name},
transactional=True,)
# do something
...

put()

これならばたとえdo somethingでエラーが発生したとしても、一貫性が保証される。例のように、同時に2つのIndexへの登録も可能だ。注意するのはtransactionalなTask Queueは5つまでしか積めないということ。同時に書き込みが多数あるシチュエーションでは避けるべきだろう。


4. 結論

2か3の方法を使う。シチュエーションに応じて使い分ける。