複数の処理が連続して行われる場合にtransactionの排他制御を用いることは多くあると思います。
今回firestoreのtransaction中で悲観ロックを用いたのですがなぜかうまくいかない事象(分離レベル勘違いしていただけ)が起きたので解決法をまとめます。
問題
簡潔に言うと次の処理をしていた。
- tokenを取得
- apiを叩く(token)
- tokenが更新されるので保存
admin.firestore().runTransaction(async (transaction) => {
const token = await transaction.get(...)
const res = await postApi(token)
transaction.set(res.token)
})
ここでtransaction A,Bが連続して実行された時、transaction.getの時点でロックされ、transactionの終了まで待機する想定であった。
1. [A] 開始
1. [B] 開始
1. [A] get
1. [B] getを待機
1. [A] postApi
1. [A] 終了
1. [B] get完了
1. [B] postApi
1. [B] 終了
実際は以下のようになりロックできていなかった?
1. [A] 開始
1. [B] 開始
1. [A] get
1. [B] get
1. [A] postApi
1. [B] postApi
1. [B] エラー
1. [A] 終了
解決法
普通にtransaction自体は正しく動いていたので任意の回数retryさせた。
admin.firestore().runTransaction(async (transaction) => {
const token = await transaction.get(...)
const res = (await postApi(token)).catch((e) => 再度実行)
transaction.set(res.token)
})
原因としては分離レベルの理解が甘かった。
firestoreの分離レベルが"READ COMMITTED"であり、更新を検知した時に再度getするみたい。
🔽詳細