概要
業務で使用していたfirebase functions
をトランザクション処理に修正するようになったのでざっくりと内容をまとめた
公式ドキュメント
トランザクション処理(transaction)とは
以下のような特徴があります。
- 複数の処理を一括で行う
- 常に最新のデータに対して処理を行う
- 全ての処理が成功することが保証されてから処理を行う
- どれか一つの処理が失敗した場合、他の全ての処理も実行されない
複数の処理を一括で行う
以下のようなトランザクション処理ではref1
とref2
の更新が同時に実行され、それぞれのtime
は必ずイコールになる
db.runTransaction((transaction) => {
const ref1 = db.collection('sample').doc('1')
const ref2 = db.collection('sample').doc('2')
transaction.update(ref1, {
time: firebase.firestore.FieldValue.serverTimestamp()
})
transaction.update(ref2, {
time: firebase.firestore.FieldValue.serverTimestamp()
})
return
})
常に最新のデータに対して処理を行う
トランザクション処理を実行中に他の処理でデータが更新された場合、再度トランザクション処理を実行する
例えば、以下のような流れのトランザクション処理があった時
-
ref1
のデータを読み取る -
ref2
のデータに読み取ったref1
のデータをコピーする
db.runTransaction(async (transaction) => {
const ref1 = db.collection('sample').doc('1')
const ref2 = db.collection('sample').doc('2')
const doc = await transaction.get(ref1)
// このタイミングで読み取ったデータが外部の処理で更新される
transaction.update(ref2, doc.data())
return Promise.resolve()
})
1と2の処理の間にref1
のデータが更新された場合、再度ref1
を読み取る
つまり上記のトランザクション処理は以下のような流れになる
-
ref1
のデータを読み取る - 外部の処理で
ref1
が更新される - 再度
ref1
のデータを読み取る ← 更新後の最新のデータを取得している -
ref2
のデータに読み取ったref1
のデータをコピーする
全ての処理が成功することが保証されてから処理を行う
firestoreのルールでref2
に対しての書き込み処理が禁止されているなど、何かしらの要因でref2
の更新が失敗した場合、ref1
の更新も実行されない
db.runTransaction((transaction) => {
const ref1 = db.collection('sample').doc('1')
const ref2 = db.collection('sample').doc('2')
transaction.update(ref1, {
time: firebase.firestore.FieldValue.serverTimestamp()
})
// この処理が失敗するとき、`ref1`の更新も実行されない
transaction.update(ref2, {
time: firebase.firestore.FieldValue.serverTimestamp()
})
return
})
まとめ
いくつかメリットはありますが、特に「常に最新のデータに対して処理を行う」の恩恵が大きいと感じました。
funcitons
はトリガーごとにそれぞれ非同期で実行されるので、通常のドキュメント操作の処理ではデータの整合性を取るのが難しいですがトランザクション処理を実装することで解決できました。