UPDATE:
This is the console.error(err)
message i get:
Error: 6 ALREADY_EXISTS: Document already exists: projects/projectOne-e3999/databases/(default)/documents/storage/AAAAAAAAAA1111111111
I do get why I get the error. Because a transaction running in parallel was faster executing the else
Part of the transaction. So the transaction that is returning the error does not have to create but to update the document. It would be enough if the transaction woul re-run. But it does not altough it states Transactions are committed once 'updateFunction' resolves and attempted up to five times on failure
.
And that is the whole point of the question. How can i prevent the transaction from failing when the document already exists, because it was created in parallel or how to re-run the transaction?
Original Question:
I have a cloud function that updates another document whenever a document in another collection gets created. As there can be multiple documents created in parallel which all access the same document this will run as transaction, idempotance is insured.
The structure of the function looks like the following. The problem is the very first write (so the creation of the storage doc). As i've said two documents can get created in parallel. But now the transaction reads a document which is not there and tries to create it, but as the other transaction (parallel running one) already created the document the transaction fails. (Which i do not understand in the first place, as I thought the transaction will block all access while executing)
This is not a problem at all, but it does not retry (although it states it will retry up to 5 times automatically). I think this has to do something with my try & catch block for the async function.
If it would retry it would detect that the document exists and automatically update the existing one.
Let's put it simple. This transaction must never fail. It would leave the database inconsistent without a possible automatic way to recover.
.onCreate(async (snapshot, context) => {
//Handle idempotence
const eventId = context.eventId;
try {
const process = await shouldProcess(eventId)
if (!process) {
return null
}
const storageDoc = admin.firestore().doc(`storage/${snapshot.id}`)
await admin.firestore().runTransaction(async t => {
const storageDbDoc = await storageDoc.get()
const dataDb = storageDbDoc.data()
if (dataDb) {
//For sake of testing just rewrite it
t.update(storageDoc, dataDb )
} else {
//Create new
const storage = createStorageDoc()
t.create(storageDoc, storage )
}
})
return markProcessed(eventId)
} catch (err) {
console.error(`Execution of ${eventId} failed: ${err}`)
throw new Error(`Transaction failed.`);
}
Your transaction insists on being able to create a new document:
t.create(storageDoc, storage)
Since create() fails on the condition of the document already existing, the entire transaction simply fails, without the ability to recover.
If your transaction must be able to recover, you should check if that document exists prior to trying to write it, and decide what you want to do in that case.