Converting Datastore system to use OrderedDatastore + Datastore (data history support)

The Goal
I’m currently in the progress of converting a simple DataStore system to support data backups with OrderedDatastores.

Currently, the Datastore will use getAsync and setAsync to load the players Data. Last week, there was a Datastore outage or something and Data was lost.

We are now in the process of trying to use OrderedDatastores so that we can have a history of data to rollback on when data loss occurs for a key.

How we are trying to handle it:
When player joins the game, we do game:GetService("DataStoreService"):GetOrderedDataStore("SaveData",Player.UserId) to get all the data history for the player.

This has a list of keys, with the most recent one being the most recent save. If there are 0 entries in the OrderedDatastore, then they have not saved with new system yet, and may just load with old method of getAsync.

If there are greater than 0 entries in the OrderedDatastore, then we can use the most recent save key from OrderedDatastore to load player data.

local DS = game:GetService("DataStoreService"):GetDataStore("SavedGameData") loadedData = DS:GetAsync(keys[1])

This is where it seems to get a bit tricky, and I’m not sure if I have all the possible outcomes covered.

If keys[1] exists in OrderedDatastore, it might be possible that it failed to get saved to in the other Datastore.

If it exists, then great no problems. Loading is done.
If it doesn’t exist, then what should we do? We can’t just “try again” with that same key, because its gonna be nil forever since it was never set.

We could try to load with the next previous key and hope that works. If that works, then problem solved, and only a tiny amount of dataloss since data is saved every minute.

If that also fails, we throw error and script tries again after a cooldown (say 6 seconds).
If this fails 3 times, we kick the player and say Roblox datastores are having an issue.

Is this correct handling of data? Are we missing any other situations that may occur? Here is how we currently handle things. Let me know if it could be more efficient or if there is a better way.

Current Implementation

local keys = {}
local clientInfo = {}
local function attemptLoad()
    local k = Plr.UserId..dataVersion

	if forceKey then 
    	k = forceKey
    end
    		
    keys = {}
    if not NO_LOAD then
    	local orderedData = game:GetService("DataStoreService"):GetOrderedDataStore("SaveData",Plr.UserId)
    	local dataVersions = orderedData:GetSortedAsync(false,10)
    	local page = dataVersions:GetCurrentPage()
    		
    	clientInfo = {}
    			
    	for entry,data in pairs(page) do
    		table.insert(keys,data.key)
    				
    		local newClientEntry = {
    			["key"] = data.key;
    			["date"] = data.value
    		}

    		table.insert(clientInfo,newClientEntry)	
    		print(entry .. " " .. newClientEntry.key .. " " .. newClientEntry.date)
    	end
    			
    	print("NUMBER OF BACKUP KEYS:",#keys)
    			
    	--if keys = 0 then just load based on the old system
    	if #keys == 0 then
    		plrData[plrName] = DSService:GetAsync(k)-- or defaultData
    	else
    		print("LOADING SAVED DATA WITH NEW BACKUP SYSTEM")
    		local loadedDataStore = game:GetService("DataStoreService"):GetDataStore("SavedGameData")
    		local loadedData = loadedDataStore:GetAsync(keys[1])
    				
    		if loadedData then
    			--Success
    			print("Successfully loaded slot 1")
    			plrData[plrName] = loadedData
    		else
    			print("Data did not exist for slot 1, try slot 2?")
    			if (keys[2] ~= nil) then
    				loadedData = loadedDataStore:GetAsync(keys[2])
    				if loadedData then
    					--Success
    					plrData[plrName] = loadedData
    				else
    					--Fail
    					error("There is no second key. No Data loaded due to dataloss")
    				end
    			else
    				--Only one key exists
    				error("Failed Attempt. Data is nil for key.")
    			end

    		end
    			
    				
    	end
    end
    	
    		
    if NO_LOAD then
    	plrData[plrName] = defaultData
    	print("DATA WAS FORCED NOT TO LOAD FOR TESTING")
   	else
   		print("PRINT NO_LOAD IS FALSE")
    end
end
3 Likes