Database caching system

Sometimes between requests I’m getting the database out of sync. Well, truth be told, I think the write operation is ignored

nk.storage_write({ collection ="savegame", key = "savegame", user_id = player_id, value={..."some_value":2...} })

-- on the next request

local savegame_entry = nk.storage_read({ collection = "savegame", key = "savegame", user_id = player_id })
local savegame = object_id[1].value
print( savegame.some_value ) -- prints the old value

Then when I look the entry in the dashboard it has the old value indeed.
What really confuses me even more is if I execute this code on the first request…

nk.storage_write({ collection ="savegame", key = "savegame", user_id = player_id, value={..."some_value":2...} })
local savegame_entry = nk.storage_read({ collection = "savegame", key = "savegame", user_id = player_id })
local savegame = object_id[1].value
print( savegame.some_value ) -- prints the new value

But then again, the database has the old value.
On top of that, is not deterministic, sometimes I got the old value, but other time the new one. I guess this is due to the cache system

Is there anyway to check if the write operation has been successful? Could it be that it is just written on the cache?

EDIT:
Doing some little investigations I have two rpc request very close to each other and it seems like that when one is performed correctly the other one doesn’t.

Thanks in advance.
Kind regards

Nakama does not add a cache layer in front of the database for storage objects.

If you’re seeing stale data in the console it might be because you looked up the value before changing it. The console view reads the data when you search for a value, not when you click it in the list. Make sure you refresh the view or search again to get the most recent data.

Otherwise it depends on which order you perform operations in. If new queries come in before or after the value is written they may see different data.

I’ll use some code as example for explaining what’s happening

Here’s the client code (Unity):

AddGold( 50 ); // these two functions call an async-await call inside
AddGold( 50 );

And this is the server code:

    local nk = require("nakama")
    nk.register_rpc(addGold, "addGold")

    local function addGold(context, payload)
      local json = nk.json_decode(payload)
      local gold = json.qty

      local object_id = {{ collection = "savegame", key = "inventory", user_id = context.user_id }}
      local inventory = nk.storage_read({object_id})[1].value

      inventory.gold = inventory.gold + gold

      nk.storage_write(object_id)

    end

The amount of gold in the database after executing the client code is 50, one of the request is lost.
I’m running nakama server locally, I don’t know if it has something to do

If the AddGold functions execute concurrently then what’s likely happening is both read the original record (which I assume has a 0 value currently written), both add 50 to it (each going from 0 to 50), and both write it to database. It’s a typical concurrent access problem.

What you’ll want to do is use the version parameter to ensure you can sequence the writes:

local nk = require("nakama")

local function addGold(context, payload)
  local json = nk.json_decode(payload)
  local gold = json.qty

  local object_id = {{ collection = "savegame", key = "inventory", user_id = context.user_id }}
  local existing_object = nk.storage_read({object_id})[1]

  local new_object = {
    collection = existing_object.collection,
    key = existing_object.key,
    user_id = existing_object.key,
    value = existing_object.value,
    version = existing_object.version
  }
  new_object.value.gold = existing_object.value + gold

  local status, err = pcall(nk.storage_write, {new_object})
  if (not status) then
    -- The write has been rejected, decide if you want to
    -- retry or return an error to the client, who may then retry
    -- or display a warning to the user etc
  end
end
nk.register_rpc(addGold, "addGold")

The important part is the version field in the new object above. That instructs the server to only run that storage write operation if the version of the object in the database hasn’t changed between when you read it and when you try to re-write it.

We’ll improve the docs to highlight this feature better, but you can see some examples of how it’s used in the “Storage Write” section of the runtime function reference.