2
\$\begingroup\$

I have to implement cron jobs in to my MMO browser game. Its like in Travian game - you build something, and wait few minutes or hours.. But the problem is, that cron job shedulers made to be sheduled every second/minute/day etc.. But in my game i need to shedule thousands of the same jobs, which must run separately and after some unique time to be released. So far i am unlucky with that idea. I am using Node.js and I just tried so many node shedulers, they seem are not supposed to have thousands of jobs.. I can run few unique only. I trough maybe then i can make like - cron job, which runs every 1 second and checks the Redis DB for any finished proccess. But that sounds crazy.. if i have few jobs only and they are in 24 hous only, my server checks that every 1 second.. Any ideas what should i do?


So far, the best option would be to remove cron jobs. I was doing some my processes without cron jobs. But other processes were really hard to calculate, and it was much easier with cron jobs. But i trough deeper and i think it is possible to calculate eveyrthing and simulate everything without cron jobs.

For example, the user has ressources inside Stats model. And i have hourly ressource income there. If the user wants to build anything, for example, traps, i have to recalculate the user ressources and save new value, set the new updatedAt. This is how i am updating my user ressources (inside Stats model), when some user wants to build a trap.

Stats.findOne({user_id: user_id}).exec(function (err, newstats){
  var time_in_ms = (new Date()).getTime() - new Date(newstats.updatedAt).getTime();
  var ressources_per_second = (newstats.base_income + newstats.field_income) / 60 / 60;
  var current_ressources = newstats.resources + parseInt(time_in_ms / 1000 * ressources_per_second) - traps_price;
  if(current_ressources < traps_price) return res.ok();
  Stats.update(newstats.id, {resources: current_ressources});
});
\$\endgroup\$
  • \$\begingroup\$ Is it really necessary that the building be built at the completion time, or only that it is there when you next log into the game? If you're not trying to send push notifications for every building completion, you may be able to simplify dramatically by just storing a timestamp of the start or completion time, and then on log-in (or periodically with a single shared cron job) evaluating the outcome based on the amount of time that has passed since that stored moment. \$\endgroup\$ – DMGregory May 15 '17 at 12:56
  • \$\begingroup\$ Yea it is necessary to be built at the exact time and not at when the user is logged in. I was thinking about this overcome, but its wont work like that.. So i must use cron jobs anyways \$\endgroup\$ – mansim May 15 '17 at 13:15
  • \$\begingroup\$ Can you describe why this is necessary? If we know what feature we're supporting, we can provide more relevant suggestions. \$\endgroup\$ – DMGregory May 15 '17 at 13:17
  • \$\begingroup\$ This is something similar like travian. But, in my game, there is a huge map. And there is unlimited users who can watch this map at one time. And in this map i want to build a wall. Ok, it takes 5 minutes. And after 5 minutes i have to send socket notification to all users who watch this map. And also update the user data. \$\endgroup\$ – mansim May 15 '17 at 13:21
  • \$\begingroup\$ Why not update the wall in response to users who are watching it? So I wander into the area where the wall is being built and the server tells me "this wall's completion time stamp is X" — if X is in the past, I see a completed wall. If X is shortly in the future and I wait, my client already has the info needed to replace the wall with its completed version at the right time, without any additional cron/socket notification from the server. The user data can similarly be updated on request. Data that no one is looking at can sit idle, and be updated when someone first asks to see it. \$\endgroup\$ – DMGregory May 15 '17 at 13:43
4
\$\begingroup\$

Do lazy evaluation of player build progression.

Whenever you need to retrieve the state of a player from your database for any reason, check how old that state is, progress the time which elapsed since the last save, and then put it back into the database.

For example: The current timestamp is 2017-06-15 15:49:23. You want to know how many windmills player 16433531 has. You retrieve the state from the database and it looks something like this:

{
    last_update: "2017-06-15 12:48:03",
    windmills: 12,
    active_tasks: [
         { task: "build_windmill", time_left: "0:23:12" },
         { task: "recruit_swordman", time_left: "14:48:33" }
    ]
}

You check the last_update timestamp and notice that this state is 3 hours, 1 minute and 20 seconds old. So you advance it by 3 hours, 1 minute and 20 seconds by simulating their progress during his timespan. During that time, the windmill they are currently building is finished. So afterwards your state looks like this:

{
    last_update: "2017-06-15 15:49:23",
    windmills: 13,
    active_tasks: [
         { task: "recruit_swordman", time_left: "11:47:13" }
    ]
}

Use this for whatever you are doing right now and also put it back into the database.

The advantage of this method over regular cronjobs is that you no longer waste time with updating the states of any players which are of no interest for anyone right now. So your total server load only depends on your current active users, not your total registered users. Considering that most online games accumulate a huge amount of dead accounts over their running history, this will really save you a lot of server capacity.

Also, updating longer timespans in bulk is likely faster than doing many frequent updates because they allow you to make a lot of optimizations. When a player has an income of 2 gold per second and you need to process 3000 seconds of game time, then incrementing their gold once by 6000 requires far less CPU time than incrementing it 3000 times by 2. And that doesn't account for the huge overhead the cron job scheduling system will cause for running a job every second.

\$\endgroup\$
  • \$\begingroup\$ Yes, you are right. I am already doing that calculation for many other processes, but I was doing cron jobs for some works, which is hard to simulate. But i trough again - i must find a way how to solve everything without cron jobs. That seem much more complicated than with cron jbos, bt i care my cpu speed more than the simplicity of my code :) \$\endgroup\$ – mansim May 15 '17 at 14:52
  • \$\begingroup\$ @mansim I think it might actually make your code simpler to do lazy evaluation, because now you only have one function for updating your game state. With cronjobs, the game mechanics are distributed over a lot of different cronjob functions which might or might not be scheduled to be executed in the intervals you think they get executed in. \$\endgroup\$ – Philipp May 15 '17 at 15:00
  • \$\begingroup\$ Ok, for example another situation: In travian there is attacking. And it says, that the player is attacked after 30 minutes. How do i simulate this without cron jobs? \$\endgroup\$ – mansim May 15 '17 at 15:05
  • \$\begingroup\$ You put the pending battle (with an unique battle-id) into the active_tasks array of both players. You also store battles by battle-id in your database. When you update one of the two players and the time to battle has elapsed, you first simulate the game until the battle. You then check the database if the battle outcome was already calculated. If it wasn't you do that now and store the outcome of the battle. Then you apply the battle outcome to the player's state and process the remaining time. \$\endgroup\$ – Philipp May 15 '17 at 15:13
  • 1
    \$\begingroup\$ @mansim When there are async events like that you put them in a queue, then when you need current information you process the queue. When a player's queue is threatening to overflow you process the events in that queue immediately. You can also add a low priority job to update players periodically, you can tie that to a premium service for email/sms updates for events that happen to the player (they pay to get updated) \$\endgroup\$ – ratchet freak May 15 '17 at 15:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.