Sign In Register

How can we help you today?

Start a new topic
Answered

DB Design and Future-Proofing

Our games' player data (exp, energy, stats, tokens, etc) is currently stored/retrieved by serializing/deserializing my player object. It works, but I am worried about optimization, future-proofing (what if we want to change how data is structured later?), and I'm not sure if it's a good idea to store all of our player data in one collection.


I don't have a lot of experience in database management. Do you have any tips or reading material on how to structure data?


Best Answer
Hey Tom,

So if you want to remove a certain field you can get the doc and then 'delete' the field, and pass it back into the collection through an update. Here is an example...

var doc = collection.findOne({ query });

delete doc.experience;

collection.update ({ same query as above }, doc);

With regards breaking up your player information, that's really an optimization consideration based on how many players you are expecting (and how often you query those collections). Certainly, as Liam suggested, I'd stay away from adding complex data to the player-doc directly using the 'setScriptData', but feel free to add tags, or single fields like email there.

If you are expecting millions then separate collection for player-things might be a better option as they can be indexed for your needs and you can dictate the doc layout (which you cant with the system-player collection). If you are expecting a few hundred thousand players then you should get away with whatever you like, so long as the player doc isnt overly filled with data.

So separating out the collections then depends on context. If you have an inventory for each player and the items in the inventory are complicated then defiantly having an separate collection for this is a good idea. But you only have a few bits of data to store here then its fine having a mirrored player collection where you can throw all the data. The trade-off is that using the player-doc is quicker to query, but the doc itself is larger so responses might be slower.

Hope that helps,
Sean

 


1 person has this question

Hi Tommy,


Storing the data on the player is fine but it can become a bit tricky to manage when the list grows quite large so a custom collection would suit much better here. We have a guide in our Tips & Tricks forum that details how to set up a custom player list for storing info, you can read more about that here. You can read some more about Mongo data structures here. I have some sample code here for you that might suit the structure you need more than the playerList guide above, you can place this in your AuthenticationResponse and each time a new player Authenticates it will add the default stat structure to the custom collection. You can also use this in the RegistrationResponse although it won't need the newPlayer check there as a RegistrationRequest is always from a new player.


 

    //load the collection
var PlayerStorage = Spark.runtimeCollection("PlayerStorage");
 
//check if this is a new player
var newPlayer = Spark.getData().newPlayer
   
    //if its a new player
  if(newPlayer === true){
         
        //get the playerID  
        var playerID = Spark.getPlayer().getPlayerId()

        //insert them into a unique document
        var success = PlayerStorage.insert({"_id":playerID, "magic":{"fireSpell":0,"healingSpell":0},"stats":{"kills":0,"lives":0},"exp":0,"energy":0})
        Spark.setScriptData("Success", playerID + " has been added to the PlayerStorage collection !")
    }
    //if this is a returning player, set the response scriptData
    else
    {
        Spark.setScriptData("Player Status", "Returning Player")
    }

  

Hope this helps, if you have anymore questions just let me know.


Thanks,

Liam

Hi Liam!

Thanks for the reply, there were several useful points.
Though, I should have been more specific about the current data structure. We are using a MongoDB collection. Here is a trimmed entry, for brevity:

 

{
    "level": 3,
    "nickname": "NONE",
    "experience": 260,
    "maxEnergy": 100,
    "energy": 100,
    "energyRegen": 0.2
}

 When I refer to future-proofing, let's say I want to rename "experience" to "EXP" (obviously not a priority) how would you manage this? Would the code be responsible for checking if the old declaration exists and converting it? Maybe some sort of versioning should be used? Should this be done as a query from NoSql?


One of my biggest questions is whether I should break my data up so it's stored across multiple collections instead of just my custom playerData collection, I'm not sure what the technical pros and cons are due to lack of experience.


Here is a greatly trimmed down version of the player class:

	// Player object definition
	function player()
	{
		this.id                 = Spark.getPlayer().getPlayerId();
		this.initialized        = true;
		this.level              = 0;
    
		////////////////////////////////////
		// Methods
		////////////////////////////////////
    
		// Player Saving and Loading
		this.Load = function()
		{
			// Load our player's data from the mongoDB
			var playerData = Spark.runtimeCollection('playerData').find({"playerID":this.id}).limit(1);
        
			// If the player data doesn't exist yet, we must create a new player
			if( null == playerData ) { return false; }
			if( playerData.size() == 0 ) { return false; }
			if( !playerData.hasNext() ) { return false; }
        
			playerData.next();
			var row = playerData.curr();
        
			if( undefined != row.nickname )     {   this.nickname       = row.nickname; }
			else
			{
				Spark.getLog().error("Failed to load player["+this.id+"] data - Nickname");
				this.UpsertData( "nickname", this.nickname );
			}
        
			if( undefined != row.level )        {   this.level          = row.level; }
			else
			{
				Spark.getLog().error("Failed to load player["+this.id+"] data - Level");
				this.UpsertData( "level", this.level );
			}

			this.initialized        = true;
        
			return true;
		}
    
		this.UpsertData = function( fieldName, fieldValue )
		{
			var collection = Spark.runtimeCollection('playerData');
			collection.update( {"playerID":this.id}, {fieldName:fieldValue}, true, false );
		}
    
		this.Save = function()
		{
			// Load our player's data from the mongoDB
			var playerData = Spark.runtimeCollection('playerData');
        
			if( null == playerData )
			{
				return;
			}
        
			playerData.update(
				{"playerID":this.id},               // Query
				{ 
					"playerID":this.id,             // Update
					"level":this.level,
				},
				true,                               // Upsert
				false );                            // Multi
    }


Obviously the load function, due to validating each field, gets very bloated very fast. 
As is probably obvious NoSql and JavaScript out of my coding comfort zone. I come from a C background, and may not be taking full advantage of the language's feature set. Any criticisms you have are greatly appreciated!

- Tom

Answer
Hey Tom,

So if you want to remove a certain field you can get the doc and then 'delete' the field, and pass it back into the collection through an update. Here is an example...

var doc = collection.findOne({ query });

delete doc.experience;

collection.update ({ same query as above }, doc);

With regards breaking up your player information, that's really an optimization consideration based on how many players you are expecting (and how often you query those collections). Certainly, as Liam suggested, I'd stay away from adding complex data to the player-doc directly using the 'setScriptData', but feel free to add tags, or single fields like email there.

If you are expecting millions then separate collection for player-things might be a better option as they can be indexed for your needs and you can dictate the doc layout (which you cant with the system-player collection). If you are expecting a few hundred thousand players then you should get away with whatever you like, so long as the player doc isnt overly filled with data.

So separating out the collections then depends on context. If you have an inventory for each player and the items in the inventory are complicated then defiantly having an separate collection for this is a good idea. But you only have a few bits of data to store here then its fine having a mirrored player collection where you can throw all the data. The trade-off is that using the player-doc is quicker to query, but the doc itself is larger so responses might be slower.

Hope that helps,
Sean

 

Hey Tom,

Did you get this working?

Sean

 

Hi Sean,

Sorry for getting back to you so late. You made a lot of good points, and we've done a lot of work to decentralize our data. For some of the more complex database management tasks we are creating several external tools to reduce user error and version/protect player data. As a part of this we have also moved several of our game features to be more data-driven, which has allowed us to better leverage JavaScript's natural dynamism and externalize concrete structure.

I do have two questions related to database management now:
1) Is there any way to directly connect our local tools to the GameSparks MongoDB?

2) Are there any performance advantages or consequences for using a meta collection as opposed to a runtime collection?

"1) Is there any way to directly connect our local tools to the GameSparks MongoDB?"


The REST API is probably what you need.

https://docs.gamesparks.net/documentation/rest-api/nosql-explorer-rest-api

 Just to refresh this thread a bit...

[1] - We dont currently offer anyway to connect a mongodb client to GameSparks DB instances, but you do have access to those via rest.

[2] - metacollection are specifically for game-config data that shouldnt be changed at runtime. But the performance is the same for both.


Login to post a comment