公式ドキュメントの説明
「トランザクションとデータ競合」には以下の記載があります。
Firestore では、いずれかのオペレーションを遅延または失敗させることで、データ競合を解決します。データ競合が原因で失敗したトランザクションは、Firestore クライアント ライブラリによって自動的に再試行されます。一定回数の再試行が行われると、トランザクション オペレーションは失敗し、エラー メッセージが返されます。
つまりデータ競合によりトランザクションが失敗するのは、一定回数リトライしても競合を解決できなかった場合ということになります。
実際に起きた例外
ところが Java版のサーバクライアントライブラリを使用したアプリで、割とあっさりとこのエラーが発生しました。以下がその時のログに出ていた例外とそのメッセージ。
java.util.concurrent.ExecutionException: com.google.api.gax.rpc.AbortedException: io.grpc.StatusRuntimeException: ABORTED: Aborted due to cross-transaction contention. This occurs when multiple transactions attempt to access the same data, requiring Firestore to abort at least one in order to enforce serializability.
検証用にアプリを作って故意にデータ競合を起こしてみると、以下の例外が発生しました。別アプリなのでJavaライブラリのバージョンも違っていたかもしれません。
java.util.concurrent.ExecutionException : com.google.cloud.firestore.FirestoreException: Transaction was cancelled because of too many retries.
この例外のgetCause()メソッドで得られたThrowableは、以下。
com.google.cloud.firestore.FirestoreException : Transaction was cancelled because of too many retries.
ネットで検索してみるとStack Overflowにも同様の現象に遭遇した質問「Firestore retry transaction logic」が見つかります。状況は最初の方と同じです。この記事には回答がまったく付いていなくてさみしい限りです。
考察
例外が発生するときと、例外が起きずに正常動作する時とで実行時間にほとんど差がないことから、公式ドキュメントの説明と異なり、実際には以下の実装ではないかと考えられます。
- トランザクションはリトライしていない、あるいは
- リトライはしているがウェイトがないのでデータ競合が解決することなく規定回数を回ってしまう
Javaクライアントライブラリのソースを探して解析すれば何かわかるかもしれませんが、面倒ですしそんな時間もありません。
回避策
ベストな解決策は、Googleさんがこの問題を解決したバージョンのライブラリを公開してくれることです。
しかしそれを待っていられないなら、自分でウェイト付きリトライをトランザクションにかましてみればいいでしょう。私はこの方法で、上記の例外がほぼ発生しなくなっています。
最後に
公式ドキュメントにはリトライはクライアントライブラリが実行しているとの記載があります。今回発見した現象はJavaライブラリによるものですが、他の言語のライブラリは使ったことがないので未確認です。
本日、Javaライブラリをひととおり新しくしてみたら、上記の例外は発生しなくなっていました。どのあたりのバージョンからかは分かりませんが、修正されたようです。
0 件のコメント:
コメントを投稿