Cloud Firestore は、データを読み書きするアトミック オペレーションをサポートしています。一連のアトミック オペレーションでは、すべてのオペレーションが正常に完了するか、またはどのオペレーションも適用されないかのいずれかです。Cloud Firestore には 2 種類のアトミック オペレーションがあります。
- トランザクション: トランザクションは、1 つ以上のドキュメントに対して読み書きを行う一連のオペレーションです。
- バッチ書き込み: バッチ書き込みは、1 つ以上のドキュメントに対して書き込みを行う一連のオペレーションです。
1 回のトランザクションまたはバッチ書き込みでは、最大 500 のドキュメントに書き込みを行うことができます。書き込みに関連するその他の制限については、割り当てと上限をご覧ください。
トランザクションでデータを更新する
Cloud Firestore クライアント ライブラリを使用して、複数のオペレーションを 1 つのトランザクションにまとめることが可能です。フィールドの値を、その現行値またはその他のフィールドの値に基づいて更新する場合には、トランザクションが便利です。
トランザクションは、任意の数の get()
オペレーションと、その後に続く任意の数の書き込みオペレーション(set()
、update()
、delete()
など)で構成されます。同時編集の場合、Cloud Firestore はトランザクション全体を再実行します。たとえば、トランザクションがドキュメントを読み取り、別のクライアントがそれらのドキュメントを変更すると、Cloud Firestore はトランザクションを再試行します。この機能により、常に整合性のある最新データに対してトランザクションが実行されます。
トランザクションでは、書き込みが部分的に適用されることはありません。成功したトランザクションの完了時にすべての書き込みが実行されます。
トランザクションを使用する場合は、次の点に注意してください。
- 読み取りオペレーションは書き込みオペレーションの前に実行する必要があります。
- トランザクションが読み取るドキュメントに対して同時編集が影響する場合は、トランザクションを呼び出す関数(トランザクション関数)が複数回実行されることがあります。
- トランザクション関数はアプリケーションの状態を直接変更してはなりません。
- クライアントがオフラインの場合、トランザクションは失敗します。
次の例は、トランザクションを作成して実行する方法を示します。
ウェブ
// Create a reference to the SF doc. var sfDocRef = db.collection("cities").doc("SF"); // Uncomment to initialize the doc. // sfDocRef.set({ population: 0 }); return db.runTransaction(function(transaction) { // This code may get re-run multiple times if there are conflicts. return transaction.get(sfDocRef).then(function(sfDoc) { if (!sfDoc.exists) { throw "Document does not exist!"; } // Add one person to the city population. // Note: this could be done without a transaction // by updating the population using FieldValue.increment() var newPopulation = sfDoc.data().population + 1; transaction.update(sfDocRef, { population: newPopulation }); }); }).then(function() { console.log("Transaction successfully committed!"); }).catch(function(error) { console.log("Transaction failed: ", error); });
Swift
let sfReference = db.collection("cities").document("SF") db.runTransaction({ (transaction, errorPointer) -> Any? in let sfDocument: DocumentSnapshot do { try sfDocument = transaction.getDocument(sfReference) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil } guard let oldPopulation = sfDocument.data()?["population"] as? Int else { let error = NSError( domain: "AppErrorDomain", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)" ] ) errorPointer?.pointee = error return nil } // Note: this could be done without a transaction // by updating the population using FieldValue.increment() transaction.updateData(["population": oldPopulation + 1], forDocument: sfReference) return nil }) { (object, error) in if let error = error { print("Transaction failed: \(error)") } else { print("Transaction successfully committed!") } }
Objective-C
FIRDocumentReference *sfReference = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) { FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer]; if (*errorPointer != nil) { return nil; } if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot" }]; return nil; } NSInteger oldPopulation = [sfDocument.data[@"population"] integerValue]; // Note: this could be done without a transaction // by updating the population using FieldValue.increment() [transaction updateData:@{ @"population": @(oldPopulation + 1) } forDocument:sfReference]; return nil; } completion:^(id result, NSError *error) { if (error != nil) { NSLog(@"Transaction failed: %@", error); } else { NSLog(@"Transaction successfully committed!"); } }];
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF"); db.runTransaction(new Transaction.Function<Void>() { @Override public Void apply(Transaction transaction) throws FirebaseFirestoreException { DocumentSnapshot snapshot = transaction.get(sfDocRef); // Note: this could be done without a transaction // by updating the population using FieldValue.increment() double newPopulation = snapshot.getDouble("population") + 1; transaction.update(sfDocRef, "population", newPopulation); // Success return null; } }).addOnSuccessListener(new OnSuccessListener<Void>() { @Override public void onSuccess(Void aVoid) { Log.d(TAG, "Transaction success!"); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.w(TAG, "Transaction failure.", e); } });
Kotlin+KTX
val sfDocRef = db.collection("cities").document("SF") db.runTransaction { transaction -> val snapshot = transaction.get(sfDocRef) // Note: this could be done without a transaction // by updating the population using FieldValue.increment() val newPopulation = snapshot.getDouble("population")!! + 1 transaction.update(sfDocRef, "population", newPopulation) // Success null }.addOnSuccessListener { Log.d(TAG, "Transaction success!") } .addOnFailureListener { e -> Log.w(TAG, "Transaction failure.", e) }
Java
// Initialize doc final DocumentReference docRef = db.collection("cities").document("SF"); City city = new City("SF"); city.setCountry("USA"); city.setPopulation(860000L); docRef.set(city).get(); // run an asynchronous transaction ApiFuture<Void> futureTransaction = db.runTransaction(transaction -> { // retrieve document and increment population field DocumentSnapshot snapshot = transaction.get(docRef).get(); long oldPopulation = snapshot.getLong("population"); transaction.update(docRef, "population", oldPopulation + 1); return null; }); // block on transaction operation using transaction.get()
Python
transaction = db.transaction() city_ref = db.collection(u'cities').document(u'SF') @firestore.transactional def update_in_transaction(transaction, city_ref): snapshot = city_ref.get(transaction=transaction) transaction.update(city_ref, { u'population': snapshot.get(u'population') + 1 }) update_in_transaction(transaction, city_ref)
C++
DocumentReference sf_doc_ref = db->Collection("cities").Document("SF"); db->RunTransaction([sf_doc_ref](Transaction* transaction, std::string* out_error_message) -> Error { Error error = Error::Ok; DocumentSnapshot snapshot = transaction->Get(sf_doc_ref, &error, out_error_message); // Note: this could be done without a transaction by updating the // population using FieldValue::Increment(). std::int64_t new_population = snapshot.Get("population").integer_value() + 1; transaction->Update( sf_doc_ref, {{"population", FieldValue::FromInteger(new_population)}}); return Error::Ok; }).OnCompletion([](const Future<void>& future) { if (future.error() == Error::Ok) { std::cout << "Transaction success!\n"; } else { std::cout << "Transaction failure: " << future.error_message() << '\n'; } });
Node.js
// Initialize document let cityRef = db.collection('cities').doc('SF'); let setCity = cityRef.set({ name: 'San Francisco', state: 'CA', country: 'USA', capital: false, population: 860000 }); let transaction = db.runTransaction(t => { return t.get(cityRef) .then(doc => { // Add one person to the city population. // Note: this could be done without a transaction // by updating the population using FieldValue.increment() let newPopulation = doc.data().population + 1; t.update(cityRef, {population: newPopulation}); }); }).then(result => { console.log('Transaction success!'); }).catch(err => { console.log('Transaction failure:', err); });
Go
ref := client.Collection("cities").Doc("SF") err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error { doc, err := tx.Get(ref) // tx.Get, NOT ref.Get! if err != nil { return err } pop, err := doc.DataAt("population") if err != nil { return err } return tx.Set(ref, map[string]interface{}{ "population": pop.(int64) + 1, }, firestore.MergeAll) }) if err != nil { // Handle any errors appropriately in this section. log.Printf("An error has occurred: %s", err) }
PHP
$cityRef = $db->collection('cities')->document('SF'); $db->runTransaction(function (Transaction $transaction) use ($cityRef) { $snapshot = $transaction->snapshot($cityRef); $newPopulation = $snapshot['population'] + 1; $transaction->update($cityRef, [ ['path' => 'population', 'value' => $newPopulation] ]); });
Unity
DocumentReference cityRef = db.Collection("cities").Document("SF"); db.RunTransactionAsync(transaction => { return transaction.GetSnapshotAsync(cityRef).ContinueWith((snapshotTask) => { DocumentSnapshot snapshot = snapshotTask.Result; long newPopulation = snapshot.GetValue<long>("Population") + 1; Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", newPopulation} }; transaction.Update(cityRef, updates); }); });
C#
DocumentReference cityRef = db.Collection("cities").Document("SF"); await db.RunTransactionAsync(async transaction => { DocumentSnapshot snapshot = await transaction.GetSnapshotAsync(cityRef); long newPopulation = snapshot.GetValue<long>("Population") + 1; Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", newPopulation} }; transaction.Update(cityRef, updates); });
Ruby
def run_simple_transaction project_id: # project_id = "Your Google Cloud Project ID" firestore = Google::Cloud::Firestore.new project_id: project_id city_ref = firestore.doc "cities/SF" firestore.transaction do |tx| new_population = tx.get(city_ref).data[:population] + 1 puts "New population is #{new_population}." tx.update city_ref, population: new_population end puts "Ran a simple transaction to update the population field in the SF document in the cities collection." end
トランザクションから情報を渡す
トランザクション関数の内部でアプリケーション状態を変更しないでください。トランザクション関数は複数回実行でき、UI スレッドに対して実行される保証はないため、このように変更すると同時実行の問題が発生します。代わりに、トランザクション関数から必要な情報を渡します。次の例は、前の例に基づいて、トランザクションから情報を渡す方法を示します。
ウェブ
// Create a reference to the SF doc. var sfDocRef = db.collection("cities").doc("SF"); db.runTransaction(function(transaction) { return transaction.get(sfDocRef).then(function(sfDoc) { if (!sfDoc.exists) { throw "Document does not exist!"; } var newPopulation = sfDoc.data().population + 1; if (newPopulation <= 1000000) { transaction.update(sfDocRef, { population: newPopulation }); return newPopulation; } else { return Promise.reject("Sorry! Population is too big."); } }); }).then(function(newPopulation) { console.log("Population increased to ", newPopulation); }).catch(function(err) { // This will be an "population is too big" error. console.error(err); });
Swift
let sfReference = db.collection("cities").document("SF") db.runTransaction({ (transaction, errorPointer) -> Any? in let sfDocument: DocumentSnapshot do { try sfDocument = transaction.getDocument(sfReference) } catch let fetchError as NSError { errorPointer?.pointee = fetchError return nil } guard let oldPopulation = sfDocument.data()?["population"] as? Int else { let error = NSError( domain: "AppErrorDomain", code: -1, userInfo: [ NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(sfDocument)" ] ) errorPointer?.pointee = error return nil } // Note: this could be done without a transaction // by updating the population using FieldValue.increment() let newPopulation = oldPopulation + 1 guard newPopulation <= 1000000 else { let error = NSError( domain: "AppErrorDomain", code: -2, userInfo: [NSLocalizedDescriptionKey: "Population \(newPopulation) too big"] ) errorPointer?.pointee = error return nil } transaction.updateData(["population": newPopulation], forDocument: sfReference) return newPopulation }) { (object, error) in if let error = error { print("Error updating population: \(error)") } else { print("Population increased to \(object!)") } }
Objective-C
FIRDocumentReference *sfReference = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [self.db runTransactionWithBlock:^id (FIRTransaction *transaction, NSError **errorPointer) { FIRDocumentSnapshot *sfDocument = [transaction getDocument:sfReference error:errorPointer]; if (*errorPointer != nil) { return nil; } if (![sfDocument.data[@"population"] isKindOfClass:[NSNumber class]]) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-1 userInfo:@{ NSLocalizedDescriptionKey: @"Unable to retreive population from snapshot" }]; return nil; } NSInteger population = [sfDocument.data[@"population"] integerValue]; population++; if (population >= 1000000) { *errorPointer = [NSError errorWithDomain:@"AppErrorDomain" code:-2 userInfo:@{ NSLocalizedDescriptionKey: @"Population too big" }]; return @(population); } [transaction updateData:@{ @"population": @(population) } forDocument:sfReference]; return nil; } completion:^(id result, NSError *error) { if (error != nil) { NSLog(@"Transaction failed: %@", error); } else { NSLog(@"Population increased to %@", result); } }];
Java
final DocumentReference sfDocRef = db.collection("cities").document("SF"); db.runTransaction(new Transaction.Function<Double>() { @Override public Double apply(Transaction transaction) throws FirebaseFirestoreException { DocumentSnapshot snapshot = transaction.get(sfDocRef); double newPopulation = snapshot.getDouble("population") + 1; if (newPopulation <= 1000000) { transaction.update(sfDocRef, "population", newPopulation); return newPopulation; } else { throw new FirebaseFirestoreException("Population too high", FirebaseFirestoreException.Code.ABORTED); } } }).addOnSuccessListener(new OnSuccessListener<Double>() { @Override public void onSuccess(Double result) { Log.d(TAG, "Transaction success: " + result); } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { Log.w(TAG, "Transaction failure.", e); } });
Kotlin+KTX
val sfDocRef = db.collection("cities").document("SF") db.runTransaction { transaction -> val snapshot = transaction.get(sfDocRef) val newPopulation = snapshot.getDouble("population")!! + 1 if (newPopulation <= 1000000) { transaction.update(sfDocRef, "population", newPopulation) newPopulation } else { throw FirebaseFirestoreException("Population too high", FirebaseFirestoreException.Code.ABORTED) } }.addOnSuccessListener { result -> Log.d(TAG, "Transaction success: $result") }.addOnFailureListener { e -> Log.w(TAG, "Transaction failure.", e) }
Java
final DocumentReference docRef = db.collection("cities").document("SF"); ApiFuture<String> futureTransaction = db.runTransaction(transaction -> { DocumentSnapshot snapshot = transaction.get(docRef).get(); Long newPopulation = snapshot.getLong("population") + 1; // conditionally update based on current population if (newPopulation <= 1000000L) { transaction.update(docRef, "population", newPopulation); return "Population increased to " + newPopulation; } else { throw new Exception("Sorry! Population is too big."); } }); // Print information retrieved from transaction System.out.println(futureTransaction.get());
Python
transaction = db.transaction() city_ref = db.collection(u'cities').document(u'SF') @firestore.transactional def update_in_transaction(transaction, city_ref): snapshot = city_ref.get(transaction=transaction) new_population = snapshot.get(u'population') + 1 if new_population < 1000000: transaction.update(city_ref, { u'population': new_population }) return True else: return False result = update_in_transaction(transaction, city_ref) if result: print(u'Population updated') else: print(u'Sorry! Population is too big.')
C++
// This is not yet supported.
Node.js
let cityRef = db.collection('cities').doc('SF'); let transaction = db.runTransaction(t => { return t.get(cityRef) .then(doc => { let newPopulation = doc.data().population + 1; if (newPopulation <= 1000000) { t.update(cityRef, {population: newPopulation}); return Promise.resolve('Population increased to ' + newPopulation); } else { return Promise.reject('Sorry! Population is too big.'); } }); }).then(result => { console.log('Transaction success', result); }).catch(err => { console.log('Transaction failure:', err); });
Go
ref := client.Collection("cities").Doc("SF") err := client.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error { doc, err := tx.Get(ref) if err != nil { return err } pop, err := doc.DataAt("population") if err != nil { return err } newpop := pop.(int64) + 1 if newpop <= 1000000 { return tx.Set(ref, map[string]interface{}{ "population": pop.(int64) + 1, }, firestore.MergeAll) } return errors.New("population is too big") }) if err != nil { // Handle any errors in an appropriate way, such as returning them. log.Printf("An error has occurred: %s", err) }
PHP
$cityRef = $db->collection('cities')->document('SF'); $transactionResult = $db->runTransaction(function (Transaction $transaction) use ($cityRef) { $snapshot = $transaction->snapshot($cityRef); $newPopulation = $snapshot['population'] + 1; if ($newPopulation <= 1000000) { $transaction->update($cityRef, [ ['path' => 'population', 'value' => $newPopulation] ]); return true; } else { return false; } }); if ($transactionResult) { printf('Population updated successfully.' . PHP_EOL); } else { printf('Sorry! Population is too big.' . PHP_EOL); }
Unity
DocumentReference cityRef = db.Collection("cities").Document("SF"); db.RunTransactionAsync(transaction => { return transaction.GetSnapshotAsync(cityRef).ContinueWith((task) => { long newPopulation = task.Result.GetValue<long>("Population") + 1; if (newPopulation <= 1000000) { Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", newPopulation} }; transaction.Update(cityRef, updates); return true; } else { return false; } }); }).ContinueWith((transactionResultTask) => { if (transactionResultTask.Result) { Console.WriteLine("Population updated successfully."); } else { Console.WriteLine("Sorry! Population is too big."); } });
C#
DocumentReference cityRef = db.Collection("cities").Document("SF"); bool transactionResult = await db.RunTransactionAsync(async transaction => { DocumentSnapshot snapshot = await transaction.GetSnapshotAsync(cityRef); long newPopulation = snapshot.GetValue<long>("Population") + 1; if (newPopulation <= 1000000) { Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", newPopulation} }; transaction.Update(cityRef, updates); return true; } else { return false; } }); if (transactionResult) { Console.WriteLine("Population updated successfully."); } else { Console.WriteLine("Sorry! Population is too big."); }
Ruby
def return_info_transaction project_id: # project_id = "Your Google Cloud Project ID" firestore = Google::Cloud::Firestore.new project_id: project_id city_ref = firestore.doc "cities/SF" updated = firestore.transaction do |tx| new_population = tx.get(city_ref).data[:population] + 1 if new_population < 1_000_000 tx.update city_ref, population: new_population true else false end end if updated puts "Population updated!" else puts "Sorry! Population is too big." end end
トランザクションの失敗
トランザクションが失敗する可能性のある原因を次に示します。
- トランザクションで書き込みオペレーションの後に読み取りオペレーションが実行される。読み取りオペレーションは常に、書き込みオペレーションの前に実行する必要があります。
- トランザクションが、トランザクション外部で変更されたドキュメントを読み取る。この場合、トランザクションは自動的に再実行されます。トランザクションは一定の回数で再試行されます。
トランザクションが最大リクエスト サイズの 10 MiB を超えました。
トランザクションのサイズは、トランザクションによって変更されたドキュメントとインデックス エントリのサイズによって異なります。削除オペレーションの場合、対象ドキュメントのサイズと、オペレーションに伴い削除されるインデックス エントリのサイズが含まれます。
トランザクションが失敗するとエラーが返され、データベースには何も書き込まれません。トランザクションをロールバックする必要はありません。これは Cloud Firestore により自動的に実行されます。
バッチ書き込み
オペレーション セットでドキュメントを読み取る必要がない場合は、複数の書き込みオペレーションを 1 つのバッチとして実行できます。このバッチには、set()
、update()
、delete()
オペレーションを自由に組み合わせて含めることができます。書き込みのバッチはアトミックに実行され、また複数のドキュメントに対する書き込みを実行できます。次の例は、書き込みバッチを作成してコミットする方法を示しています。
ウェブ
// Get a new write batch var batch = db.batch(); // Set the value of 'NYC' var nycRef = db.collection("cities").doc("NYC"); batch.set(nycRef, {name: "New York City"}); // Update the population of 'SF' var sfRef = db.collection("cities").doc("SF"); batch.update(sfRef, {"population": 1000000}); // Delete the city 'LA' var laRef = db.collection("cities").doc("LA"); batch.delete(laRef); // Commit the batch batch.commit().then(function () { // ... });
Swift
// Get new write batch let batch = db.batch() // Set the value of 'NYC' let nycRef = db.collection("cities").document("NYC") batch.setData([:], forDocument: nycRef) // Update the population of 'SF' let sfRef = db.collection("cities").document("SF") batch.updateData(["population": 1000000 ], forDocument: sfRef) // Delete the city 'LA' let laRef = db.collection("cities").document("LA") batch.deleteDocument(laRef) // Commit the batch batch.commit() { err in if let err = err { print("Error writing batch \(err)") } else { print("Batch write succeeded.") } }
Objective-C
// Get new write batch FIRWriteBatch *batch = [self.db batch]; // Set the value of 'NYC' FIRDocumentReference *nycRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"NYC"]; [batch setData:@{} forDocument:nycRef]; // Update the population of 'SF' FIRDocumentReference *sfRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"SF"]; [batch updateData:@{ @"population": @1000000 } forDocument:sfRef]; // Delete the city 'LA' FIRDocumentReference *laRef = [[self.db collectionWithPath:@"cities"] documentWithPath:@"LA"]; [batch deleteDocument:laRef]; // Commit the batch [batch commitWithCompletion:^(NSError * _Nullable error) { if (error != nil) { NSLog(@"Error writing batch %@", error); } else { NSLog(@"Batch write succeeded."); } }];
Java
// Get a new write batch WriteBatch batch = db.batch(); // Set the value of 'NYC' DocumentReference nycRef = db.collection("cities").document("NYC"); batch.set(nycRef, new City()); // Update the population of 'SF' DocumentReference sfRef = db.collection("cities").document("SF"); batch.update(sfRef, "population", 1000000L); // Delete the city 'LA' DocumentReference laRef = db.collection("cities").document("LA"); batch.delete(laRef); // Commit the batch batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { // ... } });
Kotlin+KTX
val nycRef = db.collection("cities").document("NYC") val sfRef = db.collection("cities").document("SF") val laRef = db.collection("cities").document("LA") // Get a new write batch and commit all write operations db.runBatch { batch -> // Set the value of 'NYC' batch.set(nycRef, City()) // Update the population of 'SF' batch.update(sfRef, "population", 1000000L) // Delete the city 'LA' batch.delete(laRef) }.addOnCompleteListener { // ... }
Java
// Get a new write batch WriteBatch batch = db.batch(); // Set the value of 'NYC' DocumentReference nycRef = db.collection("cities").document("NYC"); batch.set(nycRef, new City()); // Update the population of 'SF' DocumentReference sfRef = db.collection("cities").document("SF"); batch.update(sfRef, "population", 1000000L); // Delete the city 'LA' DocumentReference laRef = db.collection("cities").document("LA"); batch.delete(laRef); // asynchronously commit the batch ApiFuture<List<WriteResult>> future = batch.commit(); // ... // future.get() blocks on batch commit operation for (WriteResult result :future.get()) { System.out.println("Update time : " + result.getUpdateTime()); }
Python
batch = db.batch() # Set the data for NYC nyc_ref = db.collection(u'cities').document(u'NYC') batch.set(nyc_ref, {u'name': u'New York City'}) # Update the population for SF sf_ref = db.collection(u'cities').document(u'SF') batch.update(sf_ref, {u'population': 1000000}) # Delete DEN den_ref = db.collection(u'cities').document(u'DEN') batch.delete(den_ref) # Commit the batch batch.commit()
C++
// Get a new write batch WriteBatch batch = db->batch(); // Set the value of 'NYC' DocumentReference nyc_ref = db->Collection("cities").Document("NYC"); batch.Set(nyc_ref, {}); // Update the population of 'SF' DocumentReference sf_ref = db->Collection("cities").Document("SF"); batch.Update(sf_ref, {{"population", FieldValue::FromInteger(1000000)}}); // Delete the city 'LA' DocumentReference la_ref = db->Collection("cities").Document("LA"); batch.Delete(la_ref); // Commit the batch batch.Commit().OnCompletion([](const Future<void>& future) { if (future.error() == Error::Ok) { std::cout << "Write batch success!\n"; } else { std::cout << "Write batch failure: " << future.error_message() << '\n'; } });
Node.js
// Get a new write batch let batch = db.batch(); // Set the value of 'NYC' let nycRef = db.collection('cities').doc('NYC'); batch.set(nycRef, {name: 'New York City'}); // Update the population of 'SF' let sfRef = db.collection('cities').doc('SF'); batch.update(sfRef, {population: 1000000}); // Delete the city 'LA' let laRef = db.collection('cities').doc('LA'); batch.delete(laRef); // Commit the batch return batch.commit().then(function () { // ... });
Go
// Get a new write batch. batch := client.Batch() // Set the value of "NYC". nycRef := client.Collection("cities").Doc("NYC") batch.Set(nycRef, map[string]interface{}{ "name": "New York City", }) // Update the population of "SF". sfRef := client.Collection("cities").Doc("SF") batch.Set(sfRef, map[string]interface{}{ "population": 1000000, }, firestore.MergeAll) // Delete the city "LA". laRef := client.Collection("cities").Doc("LA") batch.Delete(laRef) // Commit the batch. _, err := batch.Commit(ctx) if err != nil { // Handle any errors in an appropriate way, such as returning them. log.Printf("An error has occurred: %s", err) }
PHP
$batch = $db->batch(); # Set the data for NYC $nycRef = $db->collection('cities')->document('NYC'); $batch->set($nycRef, [ 'name' => 'New York City' ]); # Update the population for SF $sfRef = $db->collection('cities')->document('SF'); $batch->update($sfRef, [ ['path' => 'population', 'value' => 1000000] ]); # Delete LA $laRef = $db->collection('cities')->document('LA'); $batch->delete($laRef); # Commit the batch $batch->commit();
Unity
WriteBatch batch = db.StartBatch(); // Set the data for NYC DocumentReference nycRef = db.Collection("cities").Document("NYC"); Dictionary<string, object> nycData = new Dictionary<string, object> { { "name", "New York City" } }; batch.Set(nycRef, nycData); // Update the population for SF DocumentReference sfRef = db.Collection("cities").Document("SF"); Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", 1000000} }; batch.Update(sfRef, updates); // Delete LA DocumentReference laRef = db.Collection("cities").Document("LA"); batch.Delete(laRef); // Commit the batch batch.CommitAsync();
C#
WriteBatch batch = db.StartBatch(); // Set the data for NYC DocumentReference nycRef = db.Collection("cities").Document("NYC"); Dictionary<string, object> nycData = new Dictionary<string, object> { { "name", "New York City" } }; batch.Set(nycRef, nycData); // Update the population for SF DocumentReference sfRef = db.Collection("cities").Document("SF"); Dictionary<string, object> updates = new Dictionary<string, object> { { "Population", 1000000} }; batch.Update(sfRef, updates); // Delete LA DocumentReference laRef = db.Collection("cities").Document("LA"); batch.Delete(laRef); // Commit the batch await batch.CommitAsync();
Ruby
firestore.batch do |b| # Set the data for NYC b.set "cities/NYC", name: "New York City" # Update the population for SF b.update "cities/SF", population: 1_000_000 # Delete LA b.delete "cities/LA" end
一括書き込みでは最大 500 件のオペレーションを実行できます。バッチ内の各オペレーションは別々に Cloud Firestore の使用量として計上されます。書き込みオペレーション内では、serverTimestamp
、arrayUnion
、increment
などのフィールド変換は、それぞれ追加オペレーションとして計上されます。
バッチ書き込みはトランザクションと同様にアトミックです。トランザクションとは異なり、一括書き込みでは、読み取りドキュメントが変更されていないことを確認する必要がないため、失敗するケースが少なくなります。それらは再試行の影響または過剰な再試行による失敗の影響を受けません。バッチ書き込みは、ユーザーのデバイスがオフラインの場合でも実行できます。
アトミック オペレーションに関するデータの検証
モバイル / ウェブ クライアント ライブラリの場合は、Cloud Firestore セキュリティ ルールを使用してデータを検証できます。関連ドキュメントが常に自動的に更新され、かつ常にトランザクションまたはバッチ書き込みの一環として更新されるようにできます。一連のオペレーションが完了した後、Cloud Firestore がオペレーションを commit する前の時点で、ドキュメントの状態にアクセスして検証するには、getAfter()
セキュリティ ルール関数を使用します。
たとえば、cities
の例のデータベースに countries
コレクションも含まれている場合を考えます。それぞれの country
ドキュメントで、last_updated
フィールドを使用して、その国に関連する都市が最後に更新された時刻を追跡します。次のセキュリティ ルールでは、city
ドキュメントを更新する場合は、関連する国の last_updated
フィールドもアトミックに更新することを要求しています。
service cloud.firestore { match /databases/{database}/documents { // If you update a city doc, you must also // update the related country's last_updated field. match /cities/{city} { allow write: if request.auth.uid != null && getAfter( /databases/$(database)/documents/countries/$(request.resource.data.country) ).data.last_updated == request.time; } match /countries/{country} { allow write: if request.auth.uid != null; } } }
セキュリティ ルールの制限
バッチ内の各ドキュメント オペレーションの呼び出し回数の上限は通常 10 回ですが、これに加え、トランザクションまたはバッチ書き込みのセキュリティ ルールでは、アトミック オペレーション全体についてドキュメント アクセス呼び出しの回数の上限が 20 回となっています。
チャット アプリケーションの場合の例を以下に示します。
service cloud.firestore { match /databases/{db}/documents { function prefix() { return /databases/{db}/documents; } match /chatroom/{roomId} { allow read, write: if roomId in get(/$(prefix())/users/$(request.auth.uid)).data.chats || exists(/$(prefix())/admins/$(request.auth.uid)); } match /users/{userId} { allow read, write: if userId == request.auth.uid || exists(/$(prefix())/admins/$(request.auth.uid)); } match /admins/{userId} { allow read, write: if exists(/$(prefix())/admins/$(request.auth.uid)); } } }
以下のスニペットは、いくつかのデータアクセス パターンに使用されるドキュメント アクセス呼び出しの回数を示しています。
// 0 document access calls used, because the rules evaluation short-circuits // before the exists() call is invoked. db.collection('user').doc('myuid').get(...); // 1 document access call used. The maximum total allowed for this call // is 10, because it is a single document request. db.collection('chatroom').doc('mygroup').get(...); // Initializing a write batch... var batch = db.batch(); // 2 document access calls used, 10 allowed. var group1Ref = db.collection("chatroom").doc("group1"); batch.set(group1Ref, {msg: "Hello, from Admin!"}); // 1 document access call used, 10 allowed. var newUserRef = db.collection("users").doc("newuser"); batch.update(newUserRef, {"lastSignedIn": new Date()}); // 1 document access call used, 10 allowed. var removedAdminRef = db.collection("admin").doc("otheruser"); batch.delete(removedAdminRef); // The batch used a total of 2 + 1 + 1 = 4 document access calls, out of a total // 20 allowed. batch.commit();