firestore: transactions and batch writes

Transactions and Batched Writes

Cloud Firestore supports atomic operations for reading and writing data. In a set of atomic operations, either all of the operations succeed, or none of them are applied. There are two types of atomic operations in Cloud Firestore:

  • Transactions: a transaction is a set of read and write operations on one or more documents.
  • Batched Writes: a batched write is a set of up to 500 write operations on one or more documents.

Updating data with transactions

Using the Cloud Firestore client libraries, you can group multiple operations into a single transaction. Transactions are useful when you want to update a field's value based on its current value, or the value of some other field. You could increment a counter by creating a transaction that reads the current value of the counter, increments it, and writes the new value to Cloud Firestore.

A transaction consists of any number of get() operations followed by any number of write operations such as set()update(), or delete(). In the case of a concurrent edit, Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data.

Transactions never partially apply writes. All writes execute at the end of a successful transaction.

When using transactions, note that:

  • Read operations must come before write operations.
  • A function calling a transaction (transaction function) might run more than once if a concurrent edit affects a document that the transaction reads.
  • Transaction functions should not directly modify application state.
  • Transactions will fail when the client is offline.

The following example shows how to create and run a transaction:

WEB
SWIFT
OBJECTIVE-C
ANDROID
JAVA
PYTHON
MORE
 
   
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 ];

 
[ 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!" );
 
}
}];
 

Passing information out of transactions

Do not modify application state inside of your transaction functions. Doing so will introduce concurrency issues, because transaction functions can run multiple times and are not guaranteed to run on the UI thread. Instead, pass information you need out of your transaction functions. The following example builds on the previous example to show how to pass information out of a transaction:

WEB
SWIFT
OBJECTIVE-C
ANDROID
JAVA
PYTHON
MORE
 
   
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 );
 
}
}];
 

Transaction failure

A transaction can fail for the following reasons:

  • The transaction contains read operations after write operations. Read operations must always come before any write operations.
  • The transaction read a document that was modified outside of the transaction. In this case, the transaction automatically runs again. The transaction is retried a finite number of times.

A failed transaction returns an error and does not write anything to the database. You do not need to roll back the transaction; Cloud Firestore does this automatically.

Batched writes

If you do not need to read any documents in your operation set, you can execute multiple write operations as a single batch that contains any combination of set()update(), or delete()operations. A batch of writes completes atomically and can write to multiple documents.

Batched writes are also useful for migrating large data sets to Cloud Firestore. A batched write can contain up to 500 operations and batching operations together reduces connection overhead resulting in faster data migration.

Batched writes have fewer failure cases than transactions and use simpler code. They are not affected by contention issues, because they don't depend on consistently reading any documents. Batched writes execute even when the user's device is offline. The following example shows how to build and commit a batch of writes:

WEB
SWIFT
OBJECTIVE-C
ANDROID
JAVA
PYTHON
MORE
 
   
// 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." );
 
}
}];
 

猜你喜欢

转载自blog.csdn.net/leonqiu/article/details/79428090