Experience-wide Unique User Id

What do you want to achieve?
I want to set up my game such that players will have experience-wide unique User Identifier, which applies to all servers of the game. For ease of typing, I shall be referring to this experience-unique identifier as ProfileId. Note that this cannot be the existing value of Player.UserId, as explained in the section below.

What is the issue?
The use case is that the game I’m setting up has items that are tradeable, but the player who created the item is tracked. This is used in cases where an item’s description may say something like: "A legendary item, created by Billy", where Billy is the name of the player who made the item. The naive approach would be to save the items in datastores with the player’s ROBLOX-native UserId.

However, the above approach will be an issue once a GDPR data removal request has been submitted by a specific player. In the case of a GDPR request, the UserId saved to items of all players that have referenced the original user would expect to be deleted. As far as my research goes, there would be no easy way to iterate over the datastores of all players, and remove the UserId for the affected items.

What solutions have you tried so far?

We have taken this current approach:

  • We have a DataStore, named ProfileIdCounter
    • This stores a single variable with the use of an arbitrary key, which counts upwards starting from 1 for each unique profile id that will be added to ProfileIdStore.
  • We have a DataStore, named ProfileIdStore
    • This maps a player’s ROBLOX-native UserId to a ProfileId as selected by what is available on the ProfileIdCounter.
  • The implementation is that every few minutes, the server will retrieve all players who have not yet been assigned a ProfileId. The players will be generated ProfileId’s in bulk by checking what is available in the ProfileIdCounter datastore, and then mapping it to the ProfileIdStore, using the player’s ROBLOX-native UserId.
  • When saving items, each item will be saved with the creator’s ProfileId. This allows the game to claim the original user’s details (id, username, displayname) by simply processing backwards from the mappings.
  • In the instance of a GDPR request, we would simply remove the player’s UserId mapping to their ProfileId on the ProfileIdStore. Items that try to claim the original user using the ProfileId saved would not be able to retrieve that player’s data anymore. This effectively cuts the reference to the original player, as required by GDPR.

While the solution we have already accomplishes the requirement, we noticed a flaw due to datastore limitations. We realized that the datastore limits will eventually cause the game to bottleneck processing on the ProfileIdCounter, as it would be accepting a continuous amount of UpdateAsync requests throughout all servers, assuming the game gets popular enough.

We were wondering if there are alternatives to this approach, that can be supported with a larger amount of servers.

Visualization - For visualizing the impact of the datastore limit of 6 seconds per data assignment (set, update, etc.)

  • For 1 server, the ProfileIdCounter store can be safely updated once every 6 seconds. Though, this can be setup to be performed per 1 minute for optimization.
  • For 2 servers, if the store is updated per minute for each server, there is a 10% chance that the datastore would be locked, as it may have been recently assigned on the other server.
  • For 10 servers, attempting to generate ProfileId’s every minute will likely collide with other server’s generation attempts.
  • This gets worse as more servers are available, and even if the amount of time before saves are triggered is increased, starvation may still occur.

References

1 Like

image

1 Like

How I understood your reply: (Please note if this is not aligned)
Your suggestion would be to save the hexadecimal version of the player’s UserId into the data of the items, instead of using their raw UserId. You also suggested to add extra characters, or modify the hex code in some way, likely as a way to make it different from just a UserId converted into its hexadecimal form, similar to adding salt to encryption.

My response:

Based on my understanding of the GDPR, this approach is still has a live reference to the original player. Doing this would be equivalent to saving a hexadecimal variant of the player’s UserId as a key directly on the datastore. If the player’s original UserId can be decrypted from the hexadecimal key, the GDPR delete request would not have been accomplished correctly.

For example:

-- Case 1:
local player = ...some existing player
SomeDataStore:SetAsync("Player_"..player.UserId, "Player Data to Store")

-- Case 2:
local player = ...some existing player
local userIdInHex = ConverterUtils.decimalToHex(player.UserId)
SomeDataStore:SetAsync("Player_"..userIdInHex, "Player Data to Store")

In both cases above, the data of the user is saved. If a GDPR request was made, both the data in Case 1 and Case 2 should be de-referenced from the original user. Because the user’s id can be recalculated from its hexadecimal counterpart, storing the UserId or a hexadecimal variant of it should be the same.

As for the suggestion to add an extra character as some form of “salt” to the hexadecimal id, I’m not sure if this would count as fulfilling the requirements on GDPR. I’ll have to research further on this case.

Also you can use normal User Id but save the creator of tool with use modified user Id.