Writing a Highly Scalable, Server-Authoritative Game – Part 3

In the previous instalment of the ‘writing a highly scalable, server-authoritative game’ series, we looked at optimizing client-server communication, and ensuring your communications won’t prevent scaling of your game to millions of players. Now, let’s look at some server-side techniques for ensuring that your code won’t become a bottleneck when you are handling large numbers of concurrent players.


Be specific

The previous part of this series focused mainly on minimizing your network traffic. But why stop there? Let’s take a look at reducing all of your IO traffic in general.

When you retrieve data from the database in cloud code, try to minimize the data you retrieve. Don’t just retrieve everything and then filter it out in code – target your database query to only retrieve the exact data you need.

As a trivial example, imagine you have a collection (e.g. called “demographic”) that stores demographic details of players such as name, age, sex, etc. A typical document might look like:



Let’s say you want to find all of your players who are male, aged 34.

You could write cloud code like this:



OK, this works. But it’s not efficient. It’s pretty obvious we should be targeting our database query instead of doing this work in cloud code.

This might not matter much when you test your game – you will probably only have a few documents in this collection for testing purposes, and the performance of this query will be absolutely fine. However, once this collection contains several million records, that’s a lot of disk reads, and a lot of memory allocation.


The query should, of course, be written:



This is much better – instead of retrieving every record from the database (then filtering in code) we only retrieve the ones we’re interested in. It’s also worth noting that if you know you’re going to do this a lot, adding an index on the fields you query regularly (in the “Game Published” system script) will speed up this database query massively.


But wait a minute. We’re retrieving the entire document, when all we really use is the id of each document. We can improve this query even more:



By specifying the fields we want to retrieve, the database only return that data to us – if the documents in the collection were very large, this would be a significant improvement in both speed and efficiency.


Similarly, we shouldn’t do more database hits than absolutely necessary. Let’s say we wanted to update the age for a specific player. One way (assuming we know the id for the document we want to update) would be as follows:



This works fine. But think about what is actually happening here. First we retrieve the entire document from the database, into memory (if this is a large document, that’s potentially a lot of disk reads and data transfer). Then we update the “age” field in memory, and save the entire document back to the database (again, lots of data transfer and disk writes).


A better solution would be:



In this case, we never read the document into memory at all, and we only send the information that is being updated to the database (in this case, the new age).

This is exactly the same pattern we saw earlier, when discussing network IO.


Another anti-pattern (i.e. a pattern to be avoided) is repeatedly retrieving the same data. This might sound obvious – why would you deliberately retrieve the same thing multiple times? But it’s surprisingly easy to make this mistake if you’re not careful.


For example, you may have several functions which require some data from the database. The document ID might have been passed up from the client (or otherwise determined on the server). It’s not uncommon to pass the document ID between functions, e.g:



This ends up retrieving the data twice. A better pattern is to retrieve the document once, and pass that document to each function that needs it:



These sorts of optimizations may not seem like a big deal – and when you’re testing, they almost certainly won’t be. But when you’re running your game at large scale, these optimizations can make a big difference – think about all those saved disk reads/writes and memory allocation when you do this for several million players. It all adds up!


In the next part, we will discuss testing techniques specifically aimed at testing server-side code and functionality, rather than just testing the client-side of the game, and why this is so vital to ensuring a smooth game launch and day-1 player retention.



Read more from this series:

  1. An introduction: Always be prepared
  2. Don’t get chatty!
  3. Be Specific
  4. Test under load

Who uses GameSparks?