Firestoreのbatch制限に関するトラップ対策

2 min読了の目安(約2300字TECH技術記事

こんにちは。virapture株式会社もぐめっとです。

最近人生のイベントがあって友人がたくさん写真をとってくれたので自分の写真のネタには困らないです。
今回zenn.dev初公開です!!

今回のお話はfirestoreのbatchは500個までしか書き込み出来ないというのは有名な話なのですが、なぜか255個しか書き込んでないのに下記エラーが出たのでその原因と対策の軽いメモです。

INVALID_ARGUMENT: maximum 500 writes allowed per request

原因

原因はちゃんとマニュアルに書いてありました。。。

一括書き込みでは最大 500 件のオペレーションを実行できます。バッチ内の各オペレーションは別々に Firestore の使用量として計上されます。書き込みオペレーションでは、serverTimestamp、arrayUnion、increment などのフィールド変換がそれぞれ追加オペレーションとして計上されます。

ドキュメント1個に対して1オペレーションだけではなく、その中にserverTimestamparrayUnionincrementが入ってたらそれも追加オペレーションとして計上されるというものでした。

これ結構知らない人いるんじゃないかと思ったのですが、、、もぐめっとだけですかね?

対策

今回はiosでの実装でしたが、書き込みするEntityに対してFieldValueのプロパティ分の数だけカウントを返すような定数をとりあえず返すことで、batch書く時はそれを考慮しながらコミットする形に修正しました。

Vote.swift
struct Vital: FirestoreCodable {
    let createdAt: FieldValue
    var updatedAt: FieldValue
    let voteName: String
    static let writeOperationCount: Int = 2
}
Batch.swift
    func createVotes(votes: [Vote], completion: @escaping CompletionCallback) {
        let batchMaxCount = 500
        let batch = Firestore.firestore().batch()
        var tmpVotes = votes
        let maxWriteCount = batchMaxCount / (Vote.writeOperationCount + 1) // Voteのoeperation数を定義したのを用いる
        for _ in 0 ..< maxWriteCount {
            guard let vote = tmpVotes.popLast(),
                  let voteData = try? Firestore.Encoder().encode(vote)
            else { break }
            let reference = Firestore.firestore().collection("votes").document()
            batch.setData(voteData, forDocument: reference)
        }
        batch.commit { [weak self] error in
            if let error = error {
                completion(.failure(error))
                return
            }
            // まだ未コミットがあれば再帰的に処理する
            if !tmpVotes.isEmpty {
                self?.createVotes(vitals: tmpVotes, completion: completion)
                return
            }
            completion(.success(nil))
        }
    }

ただ、Vote.swiftがこのままではFieldValueのプロパティが追加される度に定数を更新しないといけないみたいな感じになってるので、リフレクションとか使って、型をチェックしてその数を数えるみたいな感じにするとより良い実装になるかなぁと思いました。

まとめ

serverTimestamparrayUnionincrementも書き込みの数として計上する!!