Minimize server-side-calculations: Example Weekly Tournament

As mentioned in my previous post, as a indie-developer you have to watch your butdget and therefore save with the more expensive things, like servercost – now having a free(or low cost) server-/app-system still won’t give you the freedom to do any amount of calculation you desire. So what your goal should be, is to keep the amount of caluclations and cron-jobs the server has to do to an absolut minimum, and optimize everything – trust me, there is ALLWAYS something you can optimize. I’m going to explain this now on the example of weekly tournaments of my facebook-app ZeroPilot – i know this is a rather extreme example, but non the less shows the point of doing calculations only when they are needed and at best: do them on the client-side.

So here are a few basic points what I’ve learned while coding the server-side of my app that you might be able to apply to your app:

  • do calculations only when a user logs on to your app, especially if the calculation only affects the user
  • do NOT use bulk calculations/queries unless they’re absolutely neccessary

    e.g.: don’t run a cronjob, that involves a big amount of(or even all) users

  • MOST bulk calculations can be solved in another way, it’s usually more tricky to accomplish but it is possible in pretty much any case

    e.g.: in the case of time-based events, lets say “User A started a contract, that will be finished in 1h” – the worst thing you can do is to run a cronjob that checks every couple seconds if the contract is finished yet – instead save the timestamp when that contract will finish and whenever the user logs back on you do the actions according to the state – e.g.: if the contract was finished 2h ago, then add the according resources ect…

Now to the specific example that I had to struggle with:

Weekly Tournament: At the end of every tournament the highscore is resetted and every player gets awarded a trophy: #1 = Gold, #2 = Silver, #3 = Bronce, >#4 = Wooden. (ranking is based on the scores of friends, that are playing)

Now there are several ways to calculate who gets which trophy, i’m going to list two of them:

1. Heavy server-side calculation(easy way)


The crowbar method of doing this is to run a cronjob and when the tournemt ends, the server goes through every user, will collect the score of each friend of this user und then compare their scores and assign a trophy to the selected player, then reset their highscores.

Some might say “Yea, works…and it’s only needed once a week, so where’s the problem, it doesn’t matter if the server needs a couple seconds to run this job.”

Okay – but if time is not the issue, then traffic and database-queries definitly are, based on my server-setup you are very likely to run into problems here if the database is and external service.

Heavy-Version number of queries:  min. 1 read + 1 (batch)write for every user (can be more, depending on the implementation)

Heavy-Version queries at logon: 0 for calculation purposes, however 1 read is done anyways, that is not beeing utilized in this version

2. Light server-side calculation(more tricky)


The light version is, to calculate the trophy the player earned the next time the player logs onto the game. However it’s not as easy as that – for example what happens if a player logs on and creates a new highscore after the tournament ended – how will players, that logon later know that this new highscore should not be held into account with the old tournament. I’m going to show how this is accomplished in ZeroPilot, it’s a little bit tricky but in terms of server-calculations totally worth it:

Every tournament has an ID, and every high score is saved with a reference to the current tournament ID. So whenever the player logs on, the server compares the ID from the current tournament with the ID of the latest player’s high score – if they don’t match then it’s probably because this is a new week and it’s time to check what trophy that player should get. To be certain this is checked only once, I also set a flag in the userdata, that this player already got awarded for that tournament. To make it more visible here’s a code-snippet:

servCt = server[:currentTournament]
servLt = server[:lastTournament]
if user[:currentTournament] != servCt
user[:lastTournament] = user[:currentTournament]
user[:currentTournament] = servCt
calculateAndAssignTrophy(user, servLt)
user[:wasAwarded][servLt] = true
# Since I don't need any score that are older than 2 weeks
# I just delete the old ones to save datastorage, but this
# step is not neccessary if your db is big enough
deleteTournamentScoreBeforeLast(user)
end

And the following is all that is done within the cron-job when the tournament ends:

server[:lastTournament] = server[:currentTournament]
server[:currentTournament] += 1

Light-Version cron number of queries:  1 read, 2 writes

Light-Version queries on logon:  1 read, 1 write per user, however it is very likely that users will logon over a period of time, and those queries are done anyways at logon.


So as you can see it makes quite a difference, if you rely on easy-to-implement algorithms or if you use a less obviouse way of calculating things and do the calculations in single steps just in time when the data is needed. Another point for thisis, that the calculations and queries are spread out over a period of time as the user logon to the game, this will not cause any spikes like it is very likely the case with the heavy-version.