appengine ja hack-a-thon #3でやること・やりたいこと

さて、今週末にappengine ja hack-a-thon #3を予定しています。当日何やるかメモっておこうと思います。

当日は、bamlysundaycandy-scalaのエンハンスをしようとしてます。いくつかgoogle code にissueは挙げていますが、土曜日は長らく続いているhttpリクエストをまたいだトランザクションが実行できない問題を緩和するのが目標です。


ApiCallHandlerは本当にTransactionを処理できないのか?でtagomoris氏が結論してるように、今のappengineではhttpリクエストをまたいだトランザクションは多分実装不能です。

javaでも起きる現象はほぼ同様で、僕の調査だとlow level apiを経由しないcommit/rollbackでもhttpリクエストをまたぐとinvalid handleとなります。


tagomoris氏は、

1. 実はproduction環境に特有の情報が何か足りない
2. BeginTransaction/Commit/Rollbackは実際には別のノードで処理されている
3. Datastore側で、どのトランザクションハンドラがどのノードから来たものかチェックしている
4. HTTPレスポンスを返したら、取得したトランザクションハンドルの後始末が行われている?

という整理をしています。個人的にはどれもありそうだなーというのが感想です。感覚的には 4. が一番それっぽいかなと。

ちなみに、appengine/javaではTransactionCleanupFilterでlow level apiで確保したトランザクションで解放されていないトランザクションをrollbackしていますが、これはlow level apiのstackに積まれたトランザクションを解放する処理です。なので、lower level apiを触っていても発生することから考えると、別な仕組みでrollbackされていることがわかります。このため、トランザクションの有効性検証になんらかの条件が付与されている(1. 2. 3.)stubより下で二重でトランザクションを解放している(4.)、の、どっちかですね。

1. 2. 3. については、環境変数とApiProxy.getCurrentEnviromentで持っている可能性があると思われますが、その気のある人が触ればどうにかできるような実装でガードしてるとは考えずらいです。4. は、どうしようもないですね。


なので、bamlysundaycandy-scalaではトランザクションは使えません。





それでは困るので、問題を緩和する方向で考えます。あきらめたらそこで試合終了ですよ。





考えられるシンプルな方法は、関数をappengineに転送してevalする方法です。土曜日はこれをがんばります。

transactional {
  // ここの中身がリモート実行される txを勝手にbeginしててlow level api経由の操作はtxを勝手に使う
  val e1 = new Entity("test")
  val key = datastore.put(e1)
  val e2 = new Entity("test", key)
  datastore.put(e2)
}

// たぶんこれは難しい
val e1 = new Entity("test")
transactional {
  val key = datastore.put(e1)
  val e2 = new Entity("test", key)
  datastore.put(e2)
}

とか、

def function :List[Entity] = {
  Datastore.query.asList // slim3だとtxを使って全件とってくれるはず
}
val result = remote function // remoteというオブジェクトに関数を渡すとリモート実行してくれる
result.foreach(println) // REPLに結果が引き継がれてて、普通に触れる

// たぶんこれも難しい
def function2(a:Int) :Int = {
  a
}
val result = remote function2(100) // 100がかえってほしい

とか。

実は似たようなことをやっているletreplsというプロジェクトがあって、参考にできそうなソースもあります(@yuroyoroさんに教えてもらいました!)。



実現方法は、
1. ローカルで定義した関数が依存するクラスをリモートのscala.tools.nsc.Interpreterで使えるようにロードする
2. ローカルで定義した関数をシリアライズしてリモートのscala.tools.nsc.Interpreterに処理させる
3. 評価結果をシリアライズしてローカルの関数の実行結果とする

という感じでしょうか。結構難題です。


1. は、ローカルで定義した関数が依存するクラスをロードさせるところで、scalaのREPLが使っているscala.tools.nsc.interpreter.AbstractFileClassLoaderあたりを調べつつ、どうにかする方針です。
2. は、引数なしの関数であれば多分できる気がします。引数ありの関数を引数なしの関数に変換できれば、「たぶんこれは難しい」のところもできる気がしますが、これは土曜日では難しげです。
3. は、リモート呼び出しにはprotocol bufferを使用しているので、remoteで実行する関数の戻り値をresponseのprotocol bufferからリモートでの実行結果として取得すれば良さげですが、リモートでのscala.tools.nsc.Interpreterの評価結果を取得する必要があります。


問題を単純化して、ローカルのREPLで定義したclass/objectは使用不可の制約をつけて1. を無視し、引数/戻り値なしの関数だけ評価可能として3. を無視、とすれば、

「ロードしておくクラスを決め打ってscala.tools.nsc.Interpreterのsettingにclasspathを通しておく」のと、「関数をprotocol bufferで転送してscala.tools.nsc.Interpreterで関数を評価」、で実現できそうです。



ということで、とりあえず簡単にした上記の「」から着手して、あまった時間で実現方法を調査する方針にしようと思ってます。