Rollback mechanism for storage objects and wallet

  1. Versions: Nakama {3.14.0}, {Docker}
  2. Server Framework Runtime language{Go}

I know it is mentioned in the docs that creating and managing custom tables in the database is discouraged, however; there are some pretty neat features the databases have, and I was wondering if nakama’s storage engine (and possibly user’s wallet) have these features or not. To name one:

transactions.

Using the (db *sql.DB) object that is sent to every rpc we write in nakama code, we can do something like this:

tx, err := db.Begin()
if err != nil {
	return err
}
defer func() { _ = tx.Rollback() }()

tx.Exec()

// some code logic

tx.Query()

// some more code logic

tx.Exec()

// and then at the end

return tx.Commit()

In the code above, if the execution of statements or queries return an error, or even if somewhere within my own logic I decide to return error from this function, then the entire transaction will roll back, as if nothing has happened.

I was wondering if actions done on storage objects using storage engine (create, update and delete) and user’s wallet also support something like this.

If not, any recommendation on how to revert the changes done to storage objects and wallets halfway through the server rpc (other than reverting each one of them one by one of course) is so much appreciated.

Operations done through the storage API within the same API call are all done atomically and allow you to use a - read, modify then write - pattern with the help of conditional writes to ensure that a write fails if another happens concurrently in between for the same object (making the read stale).

It is generally best to avoid keeping transactions open unless absolutely necessary, so the API also protects from issues stemming from having many long-running transactions open in a highly concurrent environment at scale.

If you need to atomically update multiple different entities (such as a storage object and a wallet) you can use the runtime multiUpdate function.

Ultimately you can use custom queries if you need to, the *sql.DB handle is exposed in the Go runtime, as well as the ability to run custom queries in the Lua and JS runtimes for this reason, but we discourage it because the storage engine APIs are optimised around performance, and if you write your own queries we cannot give any guarantees. Custom tables however, create a plethora of other issues so we strongly advise against it.

Dear @sesposito

Thanks for your quick and informative response. Based on your answer, I could use the multiUpdate feature and leave the update of storage objects and wallet as the very last action I do in my rpc. The question is:

Does multiUpdate guarantee that if the update fails midway, all the previous ones are rolled back?.

Also, the downsides of this approach are:
1- the logic of my rpc might now allow the storage object updates to be the last action
2- multiUpdate does not support deleting storage objects (if I am not mistaken).

However, my concern is not operating atomically on storage objects. I am familiar with the conditional writes api. As I have specified in the title of the topic, I am looking for rollback mechanisms on storage objects in case something goes wrong in the rpc after I have created/updated/deleted the objects. Do I have to go back and delete the created objects, re-create the deleted objects and update the updated objects to the state they previously were?

Yes, that would be the case.

Ok! Thanks for clearing that out.