Suphi's DataStore Module

Is it possible to get each value saved in a scope (so you for example can create a search tab of top players and see all their stats)

You can by loading all datastores to workout the top players but this is going to use all your datastore request so the correct way to do this is to use a ordered datastore like this

local DataStoreModule = require(11671168253)
local orderedDataStore = game:GetService("DataStoreService"):GetOrderedDataStore("Coins")

local function Saving(value, dataStore)
    -- everytime the players datastore saves also save there coins amount into the ordered datastore
    orderedDataStore:SetAsync(dataStore.Key, value.Coins)
end

game.Players.ChildAdded:Connect(function(player)
    local dataStore = DataStoreModule.new("Player", player.UserId)
    dataStore.Saving:Connect(Saving)
end)

this will create a ordered datastore and save the coins amount into it
then you can get the top players like this

-- get pages of entries in decending order with 100 items per page
local pages = orderedDataStore:GetSortedAsync(false, 100)
-- get the first 100 entries
local entries = pages:GetCurrentPage()
-- loop the first 100 entries and print the index, player.UserId and Coins that player has
for index, entry in entries do
	print(index, ")  ", entry.key, ":", entry.value)
end

don’t forget to use pcall for SetAsync and GetSortedAsync as they both can error
you can get more information here:

1 Like

(my game isn’t yet released so I don’t need to worry about migrating data around can just start clean)
Chat do you recommend this Datastore module over ProfileService? While I do enjoy using ProfileService some of it is a bit confusing so I haven’t been able to use it to the fullest extent.

Basically the game I’m working on is a Roleplay game so I won’t have a lot of data to save but I will have quite a bit stacked up,

Just basically an idea of the data were saving right now

  • Purchased Vehicles

    • Customization Data
      • License Plate
      • Body Color
      • (Rim Color)
      • (Headlight Color)
      • (Window Tint - Transparency)
      • (Other Body Customization, not added yet but planned for future)
    • Purchase Information
      • OS Date of Purchase
      • Price vehicle purchased at
  • Accessible Roles

    • List of roles user has access to (wont be many in this list)
  • Player History (Expected to store a lot of data)

    • Player Join/Leave History
    • Admin Commands executed on player
    • Money Purchased/Spent
    • Vehicles Purchased/Sold
    • Other Player History
  • Transaction History (Expected to store a lot of data)

    • Vehicle Purchase/Sold
    • Weapons Purchased
    • Items Purchased
    • Withdraws/Deposits/Transfers
    • (Really anything with the cash system)
  • Criminal History (Not expected to store a whole lot of information but I would like to be fetch and add data if the player is in another server or not in the server)

    • (You get the idea)
2 Likes

Some people might recommend SDM and some might recommend PS the only way to know for sure would be to just try both and pick the one you like more. Looking at the source code might also help you decide

When it comes to the data your going to save, no datastore module is going to restrict you. The only restrictions are set by Roblox not by the module, so it does not really mater what data your going to save all modules will have no problem saving it as long as Roblox accepts it.

I want to open a players DS so should I use find or new? I’m not 100℅ sure of the difference even though i watched your videos on it. It’s a gang/crew system and when the owner deletes their crew I want to edit the members data so they’re not in the crew anymore.

Hello, sorry for bumping on this topic. I have a question regarding this module in itself:

  1. Does it support typechecking? (i.e: I have a module that’s based on typechecking; meaning I’d have to define the type for it to work.) Previously, I’m using ProfileService, however; it doesn’t seem to have any typecheck support unfortunately.
type ModuleObject = {
   Name: string,
   Data: CustomDataStore? -- like this...

} & Methods;

type Methods = {
   . . .
}

  1. Is there a proper way to determine if a data is loaded or not (i.e: Signal) (and also a way to handle data that failed to load?) I have a custom player loading system, where the client will send an event to the server and the server will verify that player’s custom class object. I wanna make sure that their data is loaded properly before they are verified (AKA prevent exploiters from bypassing their load before their data is even fully loaded if that makes sense.)
Event.Load:Connect(function(player: Player): boolean
	local playerObject = PlayerService:GetPlayer(player) -- custom PlayerService module; returns player object

	if playerObject then
        -- data is a BasicState (a public resource module)
		if not playerObject.Data:Get("IsLoaded") then
			-- Mark player data as loaded and signal player loading.
			playerObject.Data:Set("IsLoaded", true)
			PlayerService.Signal.PlayerLoaded:Fire(playerObject)

			return true
		end
	end

	return false
end)

  1. Does this module support the use of ReplicaService? My game heavily rely on the use of Replicas to communicate between client/server.

new will give you a existing datastore object but if one does not exist it will create a new one

find will give you a existing datastore object but if one does not exist it will return nil and not make a new one


normally you use new inside the playeradded event and find everywhere else

  1. yes it does have typechecking

  2. there is the StateChanged signal that will tell you when the datastore loads

dataStore.StateChanged:Connect(function(state, datastore)
    if state == true then
        print("Datastore is ready to use")
    end
end)

or you can just check the state using the datastore object directly no need make your own “IsLoaded” property

if dataStore.State == true then
    print("Datastore is ready to use")
end

you can check if there datastore fails to load like this

-- try to open the session
local response, responseData = dataStore:Open()

-- If the session fails to open lets warn
if response ~= "Success" then
    warn(dataStore.Id, response, responseData)
end
  1. iv never used ReplicaService but datastore.Value is just a simple variable that can be set to anything in most cases its set to a table so if ReplicaService is capable of replicating a standard table then it should work

I would recommend watching the basics video as it covers questions 1 and 2

Does find not work in the command bar? I have a module that handles something and uses find within the module. When required in a regular script it works but when the module is used on the command bar, find doesn’t work

1 Like

The command bar works in a different VM/Actor this means that when you require the module your not requiring the same module all your scripts are but a brand new module and in that brand new module the datastore object does not exist so find is not able to find it

2 Likes

I had a studio crash. This seemingly locked the datastore for roughly 5-10 minutes.

  • Is this intended behavior on server crash? (the session didn’t close)
  • Any way to change this time?

If I understand correctly, the advantage of MemoryStores is that they’re more precise in timing compared to os.time. So this time could maybe be reduced?

LockInterval(60 seconds) * LockAttempts(5 attempts) = LockTime(5 min)

you could reduce LockInterval to 30 and LockAttempts to 3 but I would not really recommend going any lower

LockInterval(30 seconds) * LockAttempts(3 attempts) = LockTime(1 min 30 seconds)

the lower you go the higher the chance the session might close if Roblox servers have micro outages but the higher you go the more resilient it will be to micro outages

2 Likes

Thanks for your reply.

Could you explain what you mean with the micro outages?

The way I understand it is that any server crash will cause the sessions to remain locked for 5 mins.

Case A
if you set LockInterval to 60 and LockAttempts to 5 then the memorystore will need to fail 15 times over a time span of 5 minutes before the session will close


Case B
if you set LockInterval to 30 and LockAttempts to 3 then the memorystore will need to fail 9 times over a time span of 1 minutes and 30 seconds before the session will close


lets say for example Roblox memorystore servers go down for 3 minutes then comes back online

in Case A the session will stay open and you wont notice anything

in Case B the session will automatically close after 1 minutes and 30 seconds and the state of the datastore object will change back to false while the state is false you wont be able to use the datastore until you reopen the datastore using the Open() function

(if your using my player example, that example will keep trying to reopen the session every 6 seconds until Roblox servers start working again)

but if Roblox memorystore servers go down for longer then 5 minutes then both Case A and Case B will close

so this means that Case A will allow Roblox servers to go down for up to 5 minutes and it wont be effected

and this means that Case B will allow Roblox servers to go down for up to 1 minutes and 30 seconds and it wont be effected

if you look here you can see that sometimes there are internal errors and having a longer lock time will prevent these errors from effecting the status of your datastore

2 Likes

OK, thanks for the clear reply.

What would be the best way to deal with these outages that occur while players are in the server?

Right now, I have the following state change listener that just retries opening the datastore (from your tutorial video):

local function keepTryingToOpen(dataStore)
	while dataStore.State == false do
		if dataStore:Open(DATA_TEMPLATE) ~= DataStoreModule.Response.Success then
			task.wait(5)
		end
	end
end
local function stateChanged(_, dataStore)
	if dataStore.State == false then
		keepTryingToOpen(dataStore)
	end
end

Is this the recommended way to deal with in-game outages?
I always do the following checks on the datastore object:

if dataStore.State ~= true then
	return false
end

Assuming a player’s session never succeeds in reopening from the micro outage, wouldn’t this lead to worse data loss as the player continues playing?

I think ProfileService recommends kicks the player in that case, though I could be wrong.

That’s all you can do just keep trying to reopen until roblox comes back online

if you watch my advance video i show you how to notify the player when there datastore closes

i personally would not kick them from the game
id just show a notification on there screen and still let them interact with parts of the game that does not use the datastore

but your free to handle it anyway you like

2 Likes

Would using ‘find’ in MarketplaceService.ProcessReceipt not cause any issues when this gets called on join? (from your basics video)

local dataStore = DataStoreModule.find("Player", receiptInfo.PlayerId)

From the roblox docs they say the callback is only ran when the player joins or when they buy another DevProduct. Worst case situation I can imagine is that the player data setup (under PlayerAdded), runs after the ProcessReceipt callback, making it so you’re always stuck until buying a new DevProduct.

Your right Roblox does not state if ProcessReceipt will fire after PlayerAdded and even if it did the datastore state might still be false if it fires to quickly after playeradded

so in order to solve this problem we could do

MarketplaceService.ProcessReceipt = function(receiptInfo)
    local dataStore = DataStoreModule.new("Player", receiptInfo.PlayerId)
    if dataStore:Open(template) ~= "Success" then return Enum.ProductPurchaseDecision.NotProcessedYet end
    dataStore.Value.Coins += 1
    if dataStore:Save() == "Saved" then
        return Enum.ProductPurchaseDecision.PurchaseGranted
    else
        dataStore.Value.Coins -= 1
        return Enum.ProductPurchaseDecision.NotProcessedYet
    end
end

it should be safe to use open here because PlayerRemoving should fire after ProcessReceipt causing the datastore to still get closed


if you want to be extra safe you could also record the purchases

MarketplaceService.ProcessReceipt = function(receiptInfo)
    local dataStore = DataStoreModule.new("Player", receiptInfo.PlayerId)
    if dataStore:Open(template) ~= "Success" then return Enum.ProductPurchaseDecision.NotProcessedYet end
  
    if dataStore.Value.Purchases[receiptInfo.PurchaseId] then
        -- the item was already given lets not give it again
        return Enum.ProductPurchaseDecision.PurchaseGranted
    end

    -- set this so other servers don't give the item again
    dataStore.Value.Purchases[receiptInfo.PurchaseId] = true 
    dataStore.Value.Coins += 1

    if dataStore:Save() == "Saved" then
        return Enum.ProductPurchaseDecision.PurchaseGranted
    else
        dataStore.Value.Coins -= 1
        -- remove this so that when ProcessReceipt fires again we give them the coin the next time
        dataStore.Value.Purchases[receiptInfo.PurchaseId] = nil
        return Enum.ProductPurchaseDecision.NotProcessedYet
    end
end
2 Likes

Ok that seems much more robust.
I’ll use the ‘.new()’ instead.

Worst-case scenario is that player has to wait 5 mins or so right (using default settings)? Their session still being ‘open’ on another server.

if we read MarketplaceService | Documentation - Roblox Creator Hub it says

The player needs to be in the server for the callback to be invoked,
but does not need to be in the server for the result of the callback
to be recorded on the backend.

So my understanding is that ProcessReceipt can only be called while the player is on the server and that should be before the PlayerRemoving event

so even if you call datastore:Destroy() while your in the middle of opening the destroy will be scheduled by the task scheduler to take place after the open has finished so the worst case should never be possible

but if ProcessReceipt did get called after PlayerRemoving then the datastore will get stuck open until the server shuts down but I don’t believe this is possible if it does happen we should send a bug report to Roblox

2 Likes