I need help with data store :GetSortedAsync()

I have almost finished making a game and I’ve seen a cool gui on someone else’s game that caught my attention. It was a gui that you type in players username and a date that the player provides in case a data corruption happens. From this gui you can revert players data to older data that was saved before the data corruption. So i tried to recreate that in my game.

I made 2 data stores (one is primary for loading and saving data, the second one is for backing up data) the secondary data store autosaves every 5 minutes using a different key for example: “Player_48930234_5325532523” the first set of numbers are player userid and the second are os.time(os.date(“!*t”)).

The next step was to make the gui which lists 10 datas around the time you’ve typed in.
This is how the gui looks:
RobloxStudioBeta_sDbusXR02p
But here is where i hit a roadblock.
I don’t know how to sort out datastore by the date given.
Btw the date is typed in in this form: YYYY/MM/DD and then its converted to unix epoch.
I want to sort out data so it creates like 10 of these frames and puts them in the scrolling frame and if there isn’t an exact time i would like it to find the data saved closest to the time given.

Im really bad at explaining so please ask if you need any info.
Also the scripts are too long to post here and they are kinda messy.

  • the N/A on the gui screenshot i’ve provided is text label for the date of saved data

I believe if you aren’t saving references to other data store keys then you are at a loss for obtaining previous versions going backward because OrderedDataStores lack data versioning and they lack :ListKeysAsync so you aren’t able to look at all keys starting with “Player_48930234_”. But moving forward you should be able to periodically cache a user’s data by looking at their current value in the ordered data store and caching it in their primary data table.

Moving forward though, it should be easy enough with what Roblox gives us. The best version I can think of without any external tooling would be to append to the main player data that is being stored with backups, not using separate keys.

If the player has > 10 backups, remove the oldest one.

It would probably be a good idea to only append to the data cache if their current data is not equal to the previous backup. And there are other constraints you could impose like only backing up if the last backup was taken a certain amount of time ago, and you could add a data cache to avoid the call to :UpdateAsync when the player leaves. But the specifics are up to you to decide.

There are a few things you could get and save if they aren’t a part of of the OrderedDataStore, but the biggest thing in your case would be a timestamp of the data (which would ideally be the unix timestamp at the time the user’s data is saved).

So all in all here’s some pseudocode of what it could look like:

local mainStore = dataStoreService:GetDataStore('PlayerData')
local orderedStore = dataStoreService:GetOrderedDataStore('SomeTopStat')

players.PlayerRemoving:Connect(function(player)
    local shouldMakeBackup = false
    
    local coinsToBeSaved = player:WaitForChild('Coins').Value -- probably shouldn't yield unconditionally but you'd add guard clauses earlier in the function ideally

    local success, newResult = pcall(orderedStore.UpdateAsync, orderedStore, player.UserId, function(oldData)
        if oldData ~= coinsToBeSaved then -- their coins now is not equal to the last coins value that was saved so we should make a backup
            shouldMakeBackup = true
        end
        return coinsToBeSaved
    end)

    if not success then -- if the call to UpdateAsync fails we don't want to create a backup because the newResult will be equal to a string with the reason the update call failed. This will likely be because of server issues but I also might be missing something since I'm sleep deprived
        warn(`Can't save the player's data because {newResult}, not appending to backup.`)
        shouldMakeBackup = false
    end
    
    local playerData = {}
    -- compile their data...
    -- ...

    local backups = playerData.Backups
    
    if not backups then -- the player doesn't have any backup data so we need to create a new table
        playerData.Backups = {}
        
    end
    
        
    if shouldMakeBackup then
        if #playerData.Backups >= 10 then -- when we add the last entry there will be greater than 10 entries so we need to remove the oldest object
            table.remove(playerData.Backups, #playerData.Backups) -- remove the last entry of the backups table
        end
        table.insert(playerData.Backups, { -- first entry is the timestamp, second is the value. remember this for later
            os.time(); coinsToBeSaved
        })
    end
end)

-- now for the UI, which would be in a local script probably.

for index, backupData in data do
    local timestamp = backupData[1]
    local coinsValue = backupData[2]

    -- now that we have the previous two entries, we can create the new template and put in the relevant text
    local newCoinsTemplate = coinsTemplate:Clone()
    newCoinsTemplate.Time.Text = timestamp -- should probably format it but it works for the case of simplicity
    newCoinsTemplate.CoinsValue.Text = coinsValue
    -- now to make it abide by a UIListLayout we set the LayoutOrder property of the template
    newCoinsTemplate.LayoutOrder = index -- to sort it from newest to oldest because we enter the newest backup at the start of the table (first index)
    -- finally, we set the parent
    newCoinsTemplate.Parent = scrollingFrame
end

This is good but is there a way to make it use the backups from like few days ago, because this will autosave every 5 minutes and i will very quickly reach the data store limit because the value im storing will have like 1000 backups in it.

My reply would only keep 10 of the most recent saves, but based on what you’re telling me you could probably just check the date between the last and current supposed backup, if the time between now and then is within a certain amount of time (ie a day or so), you could just not store a backup.

local TIME_THRESHOLD = 86400 -- equivalent to 1 day in seconds

local recentBackup = playerData.Backups[1] -- recent save,
if os.time() - recentBackup[1] < TIME_THRESHOLD then -- not enough time has passed, so we can say not to backup
    shouldMakeBackup = false
end

By the way, in the future you should click the reply button or ping the person using an @, so the person who is answering your question gets notified

i would like to save every autosave data on separate key, but the problem is i don’t know how to sort data with a key for example: i want to get every autosave for a specific player in one day, so i would provide the date (like 2023/05/05) and it will convert it to unix and then return all the data for that one day i specified with the player userid and the date.
i clicked the wrong reply button thats why it didn’t notify you

i have everything ready but the sorting is making a lot of headaches
Because i can’t find anyone doing anything similar to what i want to accomplish

Oh okay I see what you mean I think. The problem with that is that you can’t make that many requests.

Since there are over 80k seconds in a day you would have to do trial and error for each second in the day for the key, if you don’t have any references to the datastore keys saved elsewhere. And again since we don’t have any option to ListKeysAsync on ordered data stores you can’t do that moving backwards, but as I said you should attach backup data to their primary data. If you are worried about exceeding the 4KB limit, split it up across multiple keys if one of the keys exceed 4 million characters (ie. Player_48930234_1, Player_48930234_2, Player_48930234_3…), then for each entry in their backup data, use os.date(‘!*t’) as you were doing before to check if the year, month, and day components are the same as what you want, if they are, list them.

Then, you’d just sort each entry in the table from largest (most recent) to smallest (least recent), then use the indexes of the sorted table as the LayoutOrder to make it list properly.

Thanks man, it works now perfectly, i made it so it makes a new key that can save up to 10 backups and checked if the backup is different to last backup and if there are 10 backups and it’s trying to save, it will create a new key that can save 10 backups again.

1 Like

Realistically you would be able to save way more than 10 in a single key and that’s what I would recommend. You have 4 million characters to work with, so even if you took up 100 characters per backup, you’d still be able to store 40,000 backups.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.