Datastore data loss

I’ve been using datastore for roughly 3 months now. Its been working perfectly for the past 3 months, but lately I am getting a bunch of reports of data loss. About 1 in 10 players lose all their data. People think I am scamming them and this is causing my game to be reported and lose ratings. I suspect this issue is on Roblox’s end because I didn’t change the datastore code at all. Is there anything i can do about this?

2 Likes

Roblox data stores are prone to having many issues still and are not too reliable. I would recommend you look into caching to prevent data loss. Caching data allows you to only need to save the data when the player leaves reducing the likeness of ever hitting the limits. In addition, if it fails, then you still have all the data on the server and can continue to try to save the data until it is successful.

Essentially, you will grab the player’s data when they join and then store it on the server. Then simply update the data on the server for every change. When the player leaves, you will then keep attempting to save the data every few seconds until it is successful and then remove the cached data from the server. You can also autosave everyone’s data every 30-60 seconds in case Roblox servers ever go down.


@Kampfkarren has an awesome, open-source module for datastore caching you should look into!

1 Like

I had the exact same issue with data loss when I was using SetAsync so I switched over to UpdateAsync and my issues were solved. SetAsync has been known for its data loss issues for a long time now, UpdateAsync has yet to fail.

How to use UpdateAsync

DataStore:UpdateAsync(Player.UserId.."/CASH",function(cash)
local CurrentCash = cash
return CurrentCash
end)

(I cannot confirm this will work, I typed it within 15 seconds)

1 Like

So do I simply just replace the SetAsync with Update async without changing anything? I can’t really read up roblox wiki/devhub because the site is down

UpdateAsync would not make much of a difference. I used SetAsync and people lost their data a LOT, I switched to UpdateAsync and there is NO DIFFERENCE. I would recommend using @Kampfkarren’s DataStore2 as @UnderMyWheel suggested. This has fixed my issue and there has been 0 reports of data loss since I’ve used it.

1 Like

@ClientScripts Yeah, that probably won’t. As an example, sure, but returning the already-saved data to be saved again is a little much.

@Rezault UpdateAsync makes somewhat of a difference but if you don’t practice proper saving techniques, then yes it doesn’t make a difference. This especially goes if you use UpdateAsync like SetAsync and disregard possibilities with it. That being said though, you are grounded to the limitations of DataStores which DataStore2 breaks via its dual-DataStore access method.

4 Likes

If you don’t want to use DataStore2 or an external database, there’s still some ways to make your system a bit more reliable.

If distance is always an increasing number, UpdateAsync could be helpful here – even if it’s not in the way ClientScripts said. The good thing about UpdateAsync is that it will use the previously saved value instead of just getting rid of it. That means if your initial GetAsync fails, by the time the player leaves you could be able to increment their old data instead of overwriting it.

The problem with this is that it means you would have to store two distances – the distance they’ve gained in that session (internally, for saving) and their overall distance (externally, for the leaderboard). This shouldn’t be a big change, but you would still have to rework your code a bit. Alternatively, you could keep the original data fetched from the datastore somewhere (if it’s nil, you’d have it be 0 or whatever your default value is) and find the difference to use as session time.

An example using both methods: (this is just the saving part, it wouldn’t replace your pcalls or if statements)

--at the top of the script outside any functions
-- this assumes it's all one script
local ppTable = {}

--later when you get data:
pp = DataStorage:GetAsync(player.UserId .. "-dist")
ppTable[player] = pp -- store original value for later

-- somewhere far away in the script:
local totalTime = player.leaderstats.Distance.Value
DataStorage:UpdateAsync(player.UserId .. "-dist", function(old)
     if old then -- if there's anything saved
          return old + (totalTime - (ppTable[player] or 0)) --add the session time to total time and save
     else -- if nothing's saved
          return totalTime -- no need to subtract out old data
     end
end)
--when you get data:
local sessionData = Instance.new("IntValue")
sessionData.Name = "sessionDistance"
sessionData.Parent = player -- not leaderstats because you probably don't want to display that number
local success, errormessage = pcall(function()
     pp = DataStorage:GetAsync(player.UserId .. "-dist")
end)
-- whenever you increase the one distance thing in leaderstats, you'd have to increase the other.
--later, when saving data:
local sessionDist = player.sessionDistance.Value
DataStorage:UpdateAsync(player.UserId .. "-dist", function(old)
     if old then -- if there's anything saved
          return old + sessionDist --add the session time to total time and save
     else -- if nothing's saved
          return sessionDist -- no need to add to old
     end
end)

Keep in mind, this system assumes that your distance can only go up or stay the same in any given session, and that it can’t go down. If that’s not true, don’t use this system. Another method of using UpdateAsync, which assumes that your distance saved should be the greatest distance you’ve ever gotten in a session (i.e., the best distance you’ve ever had) might be helpful, too. This would be a lot easier to implement, since you wouldn’t need anything special until you save data. Then, you would just make sure that your current session’s best is better than your saved best before saving.

local bestTime = player.leaderstats.Distance.Value
DataStorage:UpdateAsync(player.UserId .. "-dist", function(old)
     if old and old > bestTime then -- if there's anything saved and it's better than your best
          return old -- save the old value
     else -- if either are not true
          return bestTime -- save the current best
     end
end)

The problem here is with unclear variable names, lack of comments, and not explaining what your “Distance” represents in a clear way. In the future, explaining what the data means could be helpful when asking for alternative methods of saving. I can’t be sure if you want to save the highest distance you’ve ever had, like a “best time” thing in a racing game, or if you want to increment the old value to have a distance counter sort of thing (that increases similar to a “wins” counter). That aside, there’s a few other ways to improve your code.

  • Using DataStore2 or an external database is going to be more reliable than using datastores no matter what you do, since it gives access to all past saves of the data and makes restoring it much easier. An external database is the most reliable because you don’t have to worry about Roblox datastores being down.

  • You don’t check if data exists before distance.Value to it. Try something like this:

if success then
     distance.Value = pp or 0 -- 0 being the default value
else
     -- do your current error handling, whatever
end

since this won’t error if nothing is saved. Basically, this just replaces the saved value with 0 if it would have been nil. If you try to set it to nil, I’m pretty sure it errors, but if I’m wrong somebody please correct me.

I’m not sure how to fix formatting for the final bullet, sorry about that.

6 Likes