A No-Frills introduction to Session Systems and Efficient Management of Data

I have seen a lot of people getting confused on how to efficiently manage and save data, so I thought I’d go over the theory and basics behind it.
I am by no means an expert, so take this with a grain of salt.
Feel free to correct or improve any of my points!

So, What is a Session System?
We’ll trim over the basics first.

A session usually refers to limited communication time between a client and the server.
For instance, having a temporary table of the player’s data when they’re in the game.
Whenever a player leaves, the connection gets cut off, and thus the session ends.

Visualized in code, it would look like this (my usage of semi-colons is just a preference, please don’t mind it);

local Players = game:GetService'Players';
local sessionData = {};

Players.PlayerAdded:Connect(function(Player)
    --// Player joined, create a new session!
    sessionData[Player.UserId] = {};
end);
Players.PlayerRemoving:Connect(function(Player)
    --// Player left, remove the player's session.
    sessionData[Player.UserId] = nil;
end);

Now, how can this be useful?
Well, data stores have a max upload limit (not to mention it’s yield).
This means that updating the data store directly each time a player does anything would result in an instant clogging of the data store.

The concept of data stores is simple - update a player’s value in our temporary session data table!
Tables obviously wouldn’t over-clog anything, and they’re very efficient!

The concept of session systems is very simple, and it’s point is to reduce clogging to a minimum.
You would only have to use the data store twice - getting the player’s data when they join, and saving the player’s data when they leave.

Visualized, it would look like this;

local Players = game:GetService'Players';
local dataStoreService = game:GetService'DataStoreService';
local exampleStore = dataStoreService:GetDataStore'Example Store';
local sessionData = {};

Players.PlayerAdded:Connect(function(Player)
    --[[If the player has no saved data in 
    the data store (new player for example),
    then just set it to an empty table.
    ]]
    sessionData[Player.UserId] = exampleStore:GetAsync(Player.UserId) or {};
end);
Players.PlayerRemoving:Connect(function(Player)
    --[[Player is leaving. Save their data
    to the session data they had in the game.
    ]]
    exampleStore:SetAsync(sessionData[Player.UserId]);
    sessionData[Player.UserId] = nil;
end);

...
--// Changing some value would look like this!
sessionData[Player.UserId].Coins = 1000;

And it is as simple as that.
Simplified - you can use a temporary data table to update a player’s stats, and when a player leaves, simply set the player’s data store data as that table.

Side-note: please don’t forget to use game.BindToClose, saving every player’s data when a server is about to close. Otherwise when a server closes, this can cause huge problems!

side-side note: if you know what I based my title off of, bonus points to you :slight_smile:

2021 update:

Alright, so after having this being bumped, I’d heavily recommend to just use ProfileService. Insanely useful module which implements session locking I talked about! This tutorial is still of some use if you want to understand a bit about data structure :smiley:

15 Likes

P.S: The sesionData’s keys could be the Player Instances instead of the UserId. Doing this is equivalent to using UserIds due to Instances always having an exclusive hexadecimal memory address.

I prefer to use it over UserIds however UserIds are always a fine solution (especially over Names that could possibly change).

2 Likes

I actually created a module for this a month ago. It’s pretty much the same thing except I also combined this with ObjectValues. For bigger and more complex data(inventory, tables, etc.), I used tables and RemoteEvents/RemoteFunctions to manipulate it. But, for more simpler data(coins, currency, etc.), I used InstanceValues because of the :GetPropertyChangedSignal(property) event. There was no point in using a RemoteFunction and invoking the server to retrieve something like currency, so that’s why I did it. Overall, I guess you could call it a “Hybrid SessionData” module.

Even though I understand what you mean, I don’t think it’s good practice to use instances as keys, even if it works. When dealing with larger projects, modularity, and especially when it comes to testing; you want your code to be as “pure” (with as little side effects) as possible.

For example, in order to test your system for potential flaws, you’d need a player instance for getting the session data, while a user id would be much more safe and sufficient. And names as keys is a no-go :wink:

5 Likes

You could describe what this means? I have always used the Player instance to store data about them in a table, and I’ve never had any problems with it. This also means that when the player leaves, the table automatically garbage collects and you don’t need to set it to nil yourself.

I’m fairly certain this introduces problems when the server/client crashes, as the Player object could be deleted before a save can be performed (which would not happen if you just used UserIds).

While it is smart to use this, in some cases it could potentially cause trouble.
When saving data for example, you loop through the session table.
Data Stores don’t accept instances as the key, so you’d have to constantly grab each players UserId when saving it.
While you only have to add .UserId to the code, it’s still a cause.

When an Instance is destroyed, their references stay intact. Destroy only Parents the Instance to NULL, locks it’s Parent and removes all children.

1 Like

I’m new to all of this, where will this data save and how can I see it if possible?

This would be great if it could post each username that joins onto a trello, specifically for interview and training sessions.

sry to bump but it shows a 404 error