DBオペレーションに応じてメール送信したい



taskqueue.add に transactional という kwargs を発見したので使い途を考えていたのですが、例えば Entity が put された時にメールを一通送信したいというケースに使えるのではないかと考えています。

まずはこの transactional がどんな効果があるか説明しておきましょう。db の transaction 内で transactional=True を指定して taskqueue.add を実行すると、その transaction が成功した時のみ queue に task が積まれます。

では put 時のメール送信を taskqueue 無しで実装してみましょう。下記のコードは拙作の Kay を使用しています。Kay について知らない場合でも、コード自体単純なので pseudo コードとして読んでもらえれば良いかなと思います。

def add_entity(request):
  # まず Post 値(request.form)を元に Entity を Put
  new_entity = MyModel(name=request.form.get('name'))
  new_entity.put()

  # 次にメール送信
  my_send_mail()

  # クライアントにレスポンスを返す
  return Response("New entity saved.")

このコードだと、db のオペレーションが成功した後にメール送信が失敗した場合に db とメール送信状況に齟齬が生じます。API の呼出し順を変えても問題は解消しません。

そこで transactional な taskqueue を使用する事を考えます。

def send_mail(request):
  # taskqueue で呼ぶハンドラです
  if my_send_mail():
    # 成功した時だけ 200
    return Response("OK")
  else:
    # 失敗したら 500(Retryするはず)
    raise InternalServerError()

def add_entity(request):
  # トランザクションで実行する inner メソッドを定義
  def txn():
    # まずは taskqueue.add を呼ぶ
    taskqueue.add('/send_mail', transactional=True)

    # 次に Post 値(request.form)を元に Entity を Put
    new_entity = MyModel(name=request.form.get('name'))
    new_entity.put()
    return new_entity

  # transaction を実行(本当はエラー処理した方が良い)
  db.run_in_transaction(txn)

  # クライアントにレスポンスを返す
  return Response("New entity saved.")

こうしておくと transaction が成功した場合のみ task が積まれます。また my_send_mail() 呼び出しが失敗したら 500 を返す事で、task をリトライする事ができます。唯一 my_send_mail() が終了してから 200 を返す前に DeadLineExceededError 等になった場合のみ不整合が発生します。Entity個別の情報が必要な場合は、予め id を割り当てる必要があるかもしれません。

論理的にはこれで実用に耐えうるんじゃないかと思いますが、なんせまだ机上で考えただけで実際には試してないので、いずれ試してみたいと思います。

Comments

blog comments powered by Disqus