If I have have multiple clients writing to the same document in a transaction, is every transaction triggering the snapshot listener? If yes is it guarenteed that the listener are triggered in the order the transactions are commited? I would expect yes, but am a bit unsure because:
If a document write is performed the local Snapshot gets notified immediately cause of latency concerns. Does this "local" notification only get triggered if the transaction is guranteed to be successfull? Otherwise order would not be guaranteed right?
There is a onSnapshotsInSync method. Out of the documentation I don't really get if this is related to my question, but the method irritates me a bit. When should this method be considered, is my question somehow related to it?
Thanks
I would assume that a listener is only triggered, when the transaction is actually completed. And after the transaction is finished, I'd expect all changes to be committed to the database at once.
onSnapshotInSync only ensures that multiple listeners are in sync with each other.
Related
I'm working on improving code in an application that uses Firestore. I would like to ensure that Firestore will cleanup some bits of a transaction after the transaction is completed. Instead of doing this, I may opt to run the transaction and cleanup in one transaction. However, I believe this may also introduce a race condition if other architectural changes are not made.
Anyways, in working on this problem, I realized I don't know of a way (besides making a task queue) to dispatch a one-off task in Firestore.
I think the toy example below will help explain what I want to do.
// push cleanup task to Firestore to execute in 3000ms
await dispatchTransactionCleanup({id : "xxx", inMs : 3000}); // !important
// run the actual transaction, which should not take more than 1000 ms
await runMyTransaction({id : "xxx"});
// also dispatch a cleanup here, to reduce latency over the other cleanup.
// but, importantly this cleanup isn't guaranteed to run, e.g.,
// the server crashes between request to run transaction
// and this part of the program.
await runManualCleanup({id : "xxx"});
So, how would I dispatchTransactionCleanup to run a one-off task at some point in the future? Is this possible without my own task queue? Or, do I need to introduce additional structure?
Firestore doesn't have any sense of "task" or "queue". It just responds to the API calls you make, at the time you make them.
If you want a queuing mechanism, you'll have to build that, perhaps with some other software products. You already linked to one solution that uses Cloud Functions on the backend.
You might want to reconsider simply doing all the work in a single transaction. It's not clear why you think that this would be problematic. Maybe you could post a second question to explain in more detail what you tried that wouldn't work the way you expect.
After reading the documentation, there is one thing that is not completely clear to me, I am hoping someone can clarify.
Obviously if you have two read-then-write transactions that occur concurrently, one of them will fail. However, if you have one read-then-write transaction in progress, and then another read occurs that was not part of a transaction, will that other non transactional read cancel the transaction, i.e. A transaction to read-then-write on a payment/transaction record should not be cancelled by the non transactional "Get all payment data" report. Is that correct?
Yes, that's correct. Non-transactional reads and read-only transactions do not contend with read-then-write transactions.
I wonder if firebase listeners incur more reads than 'normal', snapshot gets from firestore database. I assume a listener has to be constantly checking the DB to see if something new is in there and therefore that would add up to the reads on my app.
A get() is not necessarily more expensive than a snapshot listener. The difference between a get and a listen boils down to the fact that get will read a document exactly once, and a listen will read at least once, which each change to that document incurring another read, for as long as the listener is active.
A listener will only cause more billing for reads if the listener is left active, and the document changes over the time while it's active.
You could instead poll the document with several gets over time, but each one of those gets will cost a document read, even if the document is unchanged. So you will have to determine for yourself what the best way is to get updates to a document, or even if you want updates at all.
You have two ways to retrieve data from Firestore:
Promise
Listener
When you make use of promise with get() method, you are sending lazy request to server. While listener method opens a connection channel that retrieve information in real time on every change. (It doesn't mean such a process making request every time)
When you access to data using the Listener strategy, you are opening a communication channel that, of course, consumes connection resources. But you can unsubscribe it: Check How to remove listener for DocumentSnapshot events (Google Cloud FireStore)
The whole thing with a listener is that you don't want it to constantly check the database, like on an interval. Instead, a firestore listener opens up a websocket connection which, in contrast to http, allows two way communication - both hosts can be both server and client. This means that instead of constantly checking the database, the server tells you when there are updates, after which your snapshot callback code is run.
So like others has stated, attaching a listener consumes one read per document when the listener is attached, plus one per potential document update for as long as the listener is active.
From following document: https://cloud.google.com/datastore/docs/concepts/transactions
What would happen if transaction fails with no explicit rollback defined? For example, if we're performing put() operation on value arguments.
The document states that transaction should be idempotent, what does this mean with respect to put() operation? It is not clear how idempotency is applied in this context.
How do we detect failure if failure from commit is not reliable according to the documentation?
We are seeing some symptoms where put() against value argument is sometimes partially saving the data. Note we do not have explicit rollback defined.
As you may already know, Datastore transactions are guaranteed to be atomic, which means that it applies the all-or-nothing principle; either all operations succeed or they all fail. This ensures that the data in your database remains consistent over time.
Now, regardless whether you execute put or any other operation in your transaction, your implementation of the code should always ensure that your transaction has either successfully commited or rolled back. This means that if you aren't fully sure whether the commit succeeded, you should explicitly issue a rollback.
However, there may be some exceptions where a commit might fail, and this doesn't necessarily mean that no data was written to your database. The documentation even points out that "you can receive errors in cases where transactions have been committed."
The simple way to detect transaction failures would be to add a try/catch block in your code for when an Exception (failed transactional operation) or DatastoreException (errors related to Datastore - failed commit) are thrown. I believe that you may already have an answer in this Stackoverflow post about this particular question.
A good practice is to make your transactions idempotent whenever possible. In other words, if you're executing a transaction that includes a write operation put() to your database, if this operation were to fail and needed to be retried, the end result should ideally remain the same.
A real world example can be - you're trying to transfer some money to your friend; the transaction consists of withdrawing 20 USD from your bank account and depositing this same amount into your friend's bank account. If the transaction were to fail and had to be retried, the transaction should still operate with the same amount of money (20 USD) as the final result.
Keep in mind that the Datastore API doesn't retry transactions by default, but you can add your own retry logic to your code, as per the documentation.
In summary, if a transaction is interrupted and your logic doesn't handle the failure accordingly, you may eventually see inconsistencies in the data of your database.
Is there any way in SQLite to determine when a new transaction has started, or which transaction is currently in progress?
The purpose for this is in a trigger which is logging certain changes to a database. As far as I can tell, a trigger has no indication as to whether the given operation is by itself or part of a set of other operations. Something like a transaction count would allow for a clear delineation of which changes occurred atomically at the same time (e.g. for the purpose of playback).
SQLite has commit and rollback notification callbacks, which are called at the end of any transaction.