SlideShare a Scribd company logo
1 of 13
Download to read offline
Real-Time
Stats for
Candy Box!
by Jay Oster
www. .com
We like games here at PubNub, but not as much as we like real-
time. Combine the two, and you’ve got pure mega-awesome.
During the PubNub Hackathon, I took a popular text adventure
game called Candy Box, and updated its stats page to provide a
real-time overview of the global game statistics. The updated
game can be played at candybox.pubnub.co, and the new real-
time stats page is here. Full source code is available on github.

In this article, we’ll guide you through how the game was
modified, and how to build a very simple, yet hyper-scalable
server infrastructure to serve real-time statistics. Today we’re
using JavaScript on the client-side, and Python on the server-
side. So let’s dig in!

Since Candy Box is a very new game, there isn’t a public source
code repository available. So I started this project by mirroring
the original website, candies.aniwey.net, with wget. It’s also
possible to load the URL into a browser and use File->Save Page
As… to get a complete local copy of the game.
www. .com
Download the Game Code

With all of the JavaScript
downloaded, I had to make
some minor adjustments in
some places to get a few things
working correctly. Loading and
saving had to be modified;
moving some of the original
server-side logic right into
JavaScript. I’ll spare the details,
and the curious among you can
have a look at load.js for some
insight. To allow saving, the stats
server just proxies save requests
to the original server. The proxy
is required because the original
server does not support CORS,
meaning newer browsers will
reject the save attempt.
www. .com
Building the Stats Server

With the baseline game ready, it’s time to start thinking about the server-side, particularly how to handle the high
load that such a popular game will generate. High load is the reason the original stats page is updated only
infrequently. The challenge is to record and report game statistics for thousands of simultaneous players in real-
time, with very frequent updates (refreshing stats about once every second). With this in mind, I came up with the
following list of requirements while designing the server:

•  In-memory statistics aggregation
•  Horizontally scalable
•  Stateful-design, but distributed

Sounds simple, right? Do calculations in memory to make it fast, and scale horizontally to handle any load. A
panacea! But how can we sum and average hundreds of thousands of accounts per second? Well, we can cheat a
bit by doing the summing on the client-side, and only keep the result of the sums on the server. In other words,
client logic will be responsible for calculating the deltas between each status update, so the server just needs to
worry about adding those deltas into its internal state. Therefore, each status update is a snapshot in time, and the
server records only the sum over all snapshots.

To share state between servers, we’ll use the same idea; deltas between updates, and periodically publish to a
common PubNub channel to distribute changes to the internal state. This method of replication introduces its own
challenges, such as update latencies with multiple servers. This is acceptable because the statistics in this game
are quite volatile anyway. No one will notice any latencies.
www. .com
Server Implementation Details

For the server, I decided to use Bottle to handle the REST
interface, and gevent for non-blocking sockets. This will
give us a great deal of flexibility for the server.

After writing a few stubs for the REST interface, I started
on the distribution mechanism, which is just a PubNub
subscribe that handles messages from other servers.
Ideally, each server will periodically share the updates they
have received from clients. The server only needs to add
deltas from the user to its own internal delta for
distribution to other servers. It can’t distribute every client
request, because traffic would be much too high. And it
would defeat the purpose of horizontally scaling, anyway.
This is also where distribution latency comes in; a delta
from a client may take a few seconds to reach every
server.
www. .com
www. .com
Integrating PubNub

gevent makes the subscribe super easy; just the normal Pubnub.subscribe(), wrapped in a call to gevent.spawn(). There
are initially two such subscribes: a “control” subscription, which will recursively re-subscribe after handling each
message, and a “sync” subscription to initially synchronize with other servers.

The “control” subscription does three things:

1.  Respond to “sync” requests; providing the current game state to other servers
2.  Update config; allowing you to remotely reconfigure all servers
3.  Update game state; receive deltas from other servers

I’ll explain more about the config updating later. After Both subscriptions are established, a “sync” request is
published to any listening servers. The first “sync” response that comes within 5 minutes will cause the game state
to be updated with the provided values.

The server then goes to sleep until a user makes an “update” request. That will start a recursive timer (the interval is
configurable) which will send stat updates to clients, and stat delta updates to other servers. The timer recursion
automatically stops 5 minutes after the last user “update” request.

And that’s about all there really is to the server. You could also do some other fancy things like persisting the state
to disk periodically. It isn’t a lot of data to store, but these stats are also not critical, especially for the demo.
Running the Stats Server

Starting the server is easy as well, just start the main script with python, or run it directly in a shell (it has exec
permissions and a shebang). The script accepts three optional arguments for listening IP, listening port, and config
file. The config file is just JSON, in the same format as in config.py

Here’s an example:
$ python main.py 0.0.0.0 8999 ~/config.json	
www. .com
Hacking the Client

Back on the client-side, we just need a function to record the deltas, and another to send the “update” requests to
the server. I decided to use the localStorage API to record the update state between each request, allowing the
deltas to be calculated correctly even after restarting the browser.

As far as security goes, I will be ignoring the possibility of cheaters for the demo. Stats can also be skewed by
saves that have completed the game, because SPOILER ALERT the computer tab grants access to generating
candies and lollipops at an impossible rate, and changing pretty much every variable in strange ways. SPOILER
ALERT

Client requirements are as follows:

•  Turn a blind eye to cheaters (simplifies everything)
•  Periodically send “update” requests (once every 5 seconds is a good start)
•  Do not send “update” requests after the game has been completed
The update interval will be once every 5 seconds by default, which will be quick enough to affect the stats updates
that users end up seeing, and slow enough to handle a large number of simultaneous players with low server
resources; With 2,000 users, the server only needs to handle 400 requests per second. The gevent-based server will
easily handle that without a hiccup, even on commodity hardware. In fact, each server should handle about 1,000
concurrent connections. If more than 5,000 users are playing, just launch another stats server and put it behind
nginx (reverse proxy) as a load balancer. More on that later.
www. .com
// Save to PubNub CandyBox stats server periodically	
if ((this.nbrOfSecondsSinceLastMinInterval % 5) === 0) {	
stats.update();	
}	
The Hook

main.js is where the game loop runs. It’s implemented as a simple interval that fires once per second. This is the place
to add the stats updates. The code is very simple; just throttle a function call to once every 5 seconds:





The stats.update() function is where the magic happens. It records the interesting bits of game state, calculates the
delta, and sends the request to the stat server.


www. .com
$.each(currentUpdate, function (k, v) {	
if (typeof(v) !== "string")	
delta[k] = lastUpdate[k] - v;	
});	
Delta Calculation

The delta calculation is very easy (as you might imagine). I just keep a record of the last game stat after a successful
“update” request (and save this object to localStorage), and the delta is calculated with a small iterator:





Should be self-explanatory, but basically the difference between values in lastUpdate and values in currentUpdate are
recorded as the delta, with a safety net for the code key (not shown) which is a string value. The delta is then sent to the
stats server in an “update” request.

The server does its work, and periodically publishes a message for the stats page. The listener code is in stats.js and
you can see it does the percentage calculation client-side. It is otherwise incredibly basic.

www. .com
upstream stats_server {	
server localhost:8999;	
#server localhost:8998;	
#server localhost:8997;	
#server localhost:8996;	
	
keepalive 32;	
}	
	
server {	
listen 80;	
	
root /home/ubuntu/pn-candybox/public;	
index index.html;	
	
server_name candybox.pubnub.co;	
	
location /ping {	
proxy_pass http://stats_server;	
}	
	
Server Configuration

With the client and server ready to go, it’s time to start thinking about the operational side of the project; configuring
servers, DNS, an even dynamically scaling and remote-control reconfiguration.

I’m using nginx as a host for the client code and it also doubles as a front-end load balancer for the stats server. The
nginx config looks like this:
location /save {	
proxy_pass http://stats_server;	
}	
	
location /update {	
proxy_pass http://stats_server;	
}	
	
location / {	
try_files $uri $uri/ /index.html;	
}	
}	
	
	
	
	
	
	
	
	
	
www. .com
I did some load testing with ApacheBench and found that nginx with a single stats server can handle about 763
requests per second with 100 concurrent connections, or about 305 requests per second with 200 concurrent. All
tests were done on a t1.micro AWS instance (E5507 @ 2.27GHz, 589MB RAM) running Ubuntu 13.04 with no TCP
kernel tuning. This setup is good enough for our “2,000 simultaneous players” requirement.

Dynamically Scaling

With the server config in place, we can easily scale up by adding more upstream stats servers (commented in the
config above). Then reloading nginx. The stats servers will automatically synchronize with one another over PubNub.
We can also reconfigure the servers at runtime to tune the message publishing rates. I just have to open the
PubNub console and publish a specially constructed message to the “candybox_update” channel. Here’s an
example message that reconfigures the servers to publish only once every 5 seconds:







Publish that message, and all servers will instantly adjust their message publishing interval to 5 seconds. This is just
one example of what makes PubNub truly awesome. 
{	
"uuid" : "master",	
"action" : "config",	
"data" : {	
"update_interval" : 5	
}	
}	
www. .com
Wrapping Up

With all of that, we now have Candy Box sending
periodic updates to our stats server, and our stats
servers periodically sending updates to the stats page.
And it’s all done in a dynamically scalable way, with a
ridiculously small memory footprint, and low bandwidth
requirements.

All done! Now you should play the game,
check the stats, and fork me!
www. .com

More Related Content

What's hot

Kafka Reliability - When it absolutely, positively has to be there
Kafka Reliability - When it absolutely, positively has to be thereKafka Reliability - When it absolutely, positively has to be there
Kafka Reliability - When it absolutely, positively has to be thereGwen (Chen) Shapira
 
Build your own CDN with Varnish - Confoo 2022
Build your own CDN with Varnish - Confoo 2022Build your own CDN with Varnish - Confoo 2022
Build your own CDN with Varnish - Confoo 2022Thijs Feryn
 
Breaking Prometheus (Promcon Berlin '16)
Breaking Prometheus (Promcon Berlin '16)Breaking Prometheus (Promcon Berlin '16)
Breaking Prometheus (Promcon Berlin '16)Matthew Campbell
 
DevOps throughout time
DevOps throughout timeDevOps throughout time
DevOps throughout timeHany Fahim
 
Mасштабирование микросервисов на Go, Matt Heath (Hailo)
Mасштабирование микросервисов на Go, Matt Heath (Hailo)Mасштабирование микросервисов на Go, Matt Heath (Hailo)
Mасштабирование микросервисов на Go, Matt Heath (Hailo)Ontico
 
Load Balancing with Nginx
Load Balancing with NginxLoad Balancing with Nginx
Load Balancing with NginxMarian Marinov
 
ProstgreSQLFailoverConfiguration
ProstgreSQLFailoverConfigurationProstgreSQLFailoverConfiguration
ProstgreSQLFailoverConfigurationSuyog Shirgaonkar
 
NATS + Docker meetup talk Oct - 2016
NATS + Docker meetup talk Oct - 2016NATS + Docker meetup talk Oct - 2016
NATS + Docker meetup talk Oct - 2016wallyqs
 
Building an Impenetrable ZooKeeper - Kathleen Ting
Building an Impenetrable ZooKeeper - Kathleen TingBuilding an Impenetrable ZooKeeper - Kathleen Ting
Building an Impenetrable ZooKeeper - Kathleen Tingjaxconf
 
Load Balancing with Apache
Load Balancing with ApacheLoad Balancing with Apache
Load Balancing with ApacheBradley Holt
 
(PFC306) Performance Tuning Amazon EC2 Instances | AWS re:Invent 2014
(PFC306) Performance Tuning Amazon EC2 Instances | AWS re:Invent 2014(PFC306) Performance Tuning Amazon EC2 Instances | AWS re:Invent 2014
(PFC306) Performance Tuning Amazon EC2 Instances | AWS re:Invent 2014Amazon Web Services
 
Rails Caching Secrets from the Edge
Rails Caching Secrets from the EdgeRails Caching Secrets from the Edge
Rails Caching Secrets from the EdgeMichael May
 
The Zen of High Performance Messaging with NATS (Strange Loop 2016)
The Zen of High Performance Messaging with NATS (Strange Loop 2016)The Zen of High Performance Messaging with NATS (Strange Loop 2016)
The Zen of High Performance Messaging with NATS (Strange Loop 2016)wallyqs
 
Don't change the partition count for kafka topics!
Don't change the partition count for kafka topics!Don't change the partition count for kafka topics!
Don't change the partition count for kafka topics!Dainius Jocas
 

What's hot (20)

Kafka Reliability - When it absolutely, positively has to be there
Kafka Reliability - When it absolutely, positively has to be thereKafka Reliability - When it absolutely, positively has to be there
Kafka Reliability - When it absolutely, positively has to be there
 
GUC Tutorial Package (9.0)
GUC Tutorial Package (9.0)GUC Tutorial Package (9.0)
GUC Tutorial Package (9.0)
 
Build your own CDN with Varnish - Confoo 2022
Build your own CDN with Varnish - Confoo 2022Build your own CDN with Varnish - Confoo 2022
Build your own CDN with Varnish - Confoo 2022
 
Fail over fail_back
Fail over fail_backFail over fail_back
Fail over fail_back
 
Breaking Prometheus (Promcon Berlin '16)
Breaking Prometheus (Promcon Berlin '16)Breaking Prometheus (Promcon Berlin '16)
Breaking Prometheus (Promcon Berlin '16)
 
DevOps throughout time
DevOps throughout timeDevOps throughout time
DevOps throughout time
 
Shootout at the PAAS Corral
Shootout at the PAAS CorralShootout at the PAAS Corral
Shootout at the PAAS Corral
 
Mасштабирование микросервисов на Go, Matt Heath (Hailo)
Mасштабирование микросервисов на Go, Matt Heath (Hailo)Mасштабирование микросервисов на Go, Matt Heath (Hailo)
Mасштабирование микросервисов на Go, Matt Heath (Hailo)
 
Prometheus with Grafana - AddWeb Solution
Prometheus with Grafana - AddWeb SolutionPrometheus with Grafana - AddWeb Solution
Prometheus with Grafana - AddWeb Solution
 
Load Balancing with Nginx
Load Balancing with NginxLoad Balancing with Nginx
Load Balancing with Nginx
 
ProstgreSQLFailoverConfiguration
ProstgreSQLFailoverConfigurationProstgreSQLFailoverConfiguration
ProstgreSQLFailoverConfiguration
 
7 Ways To Crash Postgres
7 Ways To Crash Postgres7 Ways To Crash Postgres
7 Ways To Crash Postgres
 
NATS + Docker meetup talk Oct - 2016
NATS + Docker meetup talk Oct - 2016NATS + Docker meetup talk Oct - 2016
NATS + Docker meetup talk Oct - 2016
 
Building an Impenetrable ZooKeeper - Kathleen Ting
Building an Impenetrable ZooKeeper - Kathleen TingBuilding an Impenetrable ZooKeeper - Kathleen Ting
Building an Impenetrable ZooKeeper - Kathleen Ting
 
Load Balancing with Apache
Load Balancing with ApacheLoad Balancing with Apache
Load Balancing with Apache
 
(PFC306) Performance Tuning Amazon EC2 Instances | AWS re:Invent 2014
(PFC306) Performance Tuning Amazon EC2 Instances | AWS re:Invent 2014(PFC306) Performance Tuning Amazon EC2 Instances | AWS re:Invent 2014
(PFC306) Performance Tuning Amazon EC2 Instances | AWS re:Invent 2014
 
Rails Caching Secrets from the Edge
Rails Caching Secrets from the EdgeRails Caching Secrets from the Edge
Rails Caching Secrets from the Edge
 
The Zen of High Performance Messaging with NATS (Strange Loop 2016)
The Zen of High Performance Messaging with NATS (Strange Loop 2016)The Zen of High Performance Messaging with NATS (Strange Loop 2016)
The Zen of High Performance Messaging with NATS (Strange Loop 2016)
 
How to monitor NGINX
How to monitor NGINXHow to monitor NGINX
How to monitor NGINX
 
Don't change the partition count for kafka topics!
Don't change the partition count for kafka topics!Don't change the partition count for kafka topics!
Don't change the partition count for kafka topics!
 

Viewers also liked

Instaforex presentation en
Instaforex presentation enInstaforex presentation en
Instaforex presentation enDosen Forex
 
Streaming Geo Coordinates from MongoDB to your iPhone App with PubNub using W...
Streaming Geo Coordinates from MongoDB to your iPhone App with PubNub using W...Streaming Geo Coordinates from MongoDB to your iPhone App with PubNub using W...
Streaming Geo Coordinates from MongoDB to your iPhone App with PubNub using W...PubNub
 
Lisbeth Coivu, Vuodenajat
Lisbeth Coivu, VuodenajatLisbeth Coivu, Vuodenajat
Lisbeth Coivu, VuodenajatLisbeth Coivu
 
English presentation
English presentation English presentation
English presentation thenamekelly
 
TrackPad Destroyer
TrackPad DestroyerTrackPad Destroyer
TrackPad DestroyerPubNub
 
Rails monolith-to-microservices-design
Rails monolith-to-microservices-designRails monolith-to-microservices-design
Rails monolith-to-microservices-designPhilippe Lafoucrière
 

Viewers also liked (8)

Instaforex presentation en
Instaforex presentation enInstaforex presentation en
Instaforex presentation en
 
Streaming Geo Coordinates from MongoDB to your iPhone App with PubNub using W...
Streaming Geo Coordinates from MongoDB to your iPhone App with PubNub using W...Streaming Geo Coordinates from MongoDB to your iPhone App with PubNub using W...
Streaming Geo Coordinates from MongoDB to your iPhone App with PubNub using W...
 
Lisbeth Coivu, Vuodenajat
Lisbeth Coivu, VuodenajatLisbeth Coivu, Vuodenajat
Lisbeth Coivu, Vuodenajat
 
Lisbeth valojavesi
Lisbeth valojavesiLisbeth valojavesi
Lisbeth valojavesi
 
English presentation
English presentation English presentation
English presentation
 
Fai
FaiFai
Fai
 
TrackPad Destroyer
TrackPad DestroyerTrackPad Destroyer
TrackPad Destroyer
 
Rails monolith-to-microservices-design
Rails monolith-to-microservices-designRails monolith-to-microservices-design
Rails monolith-to-microservices-design
 

Similar to Real-Time Candy Box Stats

Realtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsRealtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsMario Gonzalez
 
EE4414 Multimedia Communication Systems II
EE4414 Multimedia Communication Systems IIEE4414 Multimedia Communication Systems II
EE4414 Multimedia Communication Systems IIFranZEast
 
FInal Project MGMT 404
FInal Project MGMT 404FInal Project MGMT 404
FInal Project MGMT 404Steven Quenzel
 
Building and Scaling a WebSockets Pubsub System
Building and Scaling a WebSockets Pubsub SystemBuilding and Scaling a WebSockets Pubsub System
Building and Scaling a WebSockets Pubsub SystemKapil Reddy
 
Comet from JavaOne 2008
Comet from JavaOne 2008Comet from JavaOne 2008
Comet from JavaOne 2008Joe Walker
 
Kafka used at scale to deliver real-time notifications
Kafka used at scale to deliver real-time notificationsKafka used at scale to deliver real-time notifications
Kafka used at scale to deliver real-time notificationsSérgio Nunes
 
IoT with OpenPicus Flyport
IoT with OpenPicus FlyportIoT with OpenPicus Flyport
IoT with OpenPicus FlyportIonela
 
SharePoint 2010 Virtualisation - SharePoint Saturday UK
SharePoint 2010 Virtualisation - SharePoint Saturday UKSharePoint 2010 Virtualisation - SharePoint Saturday UK
SharePoint 2010 Virtualisation - SharePoint Saturday UKMichael Noel
 
Reactive & Realtime Web Applications with TurboGears2
Reactive & Realtime Web Applications with TurboGears2Reactive & Realtime Web Applications with TurboGears2
Reactive & Realtime Web Applications with TurboGears2Alessandro Molina
 
SharePoint 2010 Virtualization
SharePoint 2010 VirtualizationSharePoint 2010 Virtualization
SharePoint 2010 VirtualizationMichael Noel
 
Game Networking for Online games
Game Networking for Online gamesGame Networking for Online games
Game Networking for Online gamesMinh Nghiem
 
KubeCon EU 2016 Keynote: Pushing Kubernetes Forward
KubeCon EU 2016 Keynote: Pushing Kubernetes ForwardKubeCon EU 2016 Keynote: Pushing Kubernetes Forward
KubeCon EU 2016 Keynote: Pushing Kubernetes ForwardKubeAcademy
 
Game server development in node.js in jsconf eu
Game server development in node.js in jsconf euGame server development in node.js in jsconf eu
Game server development in node.js in jsconf euXie ChengChao
 
Unite2014 Bunny Necropsy - Servers, Syncing Game State, Security and Optimiza...
Unite2014 Bunny Necropsy - Servers, Syncing Game State, Security and Optimiza...Unite2014 Bunny Necropsy - Servers, Syncing Game State, Security and Optimiza...
Unite2014 Bunny Necropsy - Servers, Syncing Game State, Security and Optimiza...David Geurts
 
Real-World Pulsar Architectural Patterns
Real-World Pulsar Architectural PatternsReal-World Pulsar Architectural Patterns
Real-World Pulsar Architectural PatternsDevin Bost
 
Top Java Performance Problems and Metrics To Check in Your Pipeline
Top Java Performance Problems and Metrics To Check in Your PipelineTop Java Performance Problems and Metrics To Check in Your Pipeline
Top Java Performance Problems and Metrics To Check in Your PipelineAndreas Grabner
 
Willing Webcam manual
Willing Webcam manualWilling Webcam manual
Willing Webcam manualwwwilling
 
Mantis: Netflix's Event Stream Processing System
Mantis: Netflix's Event Stream Processing SystemMantis: Netflix's Event Stream Processing System
Mantis: Netflix's Event Stream Processing SystemC4Media
 
Stress Testing at Twitter: a tale of New Year Eves
Stress Testing at Twitter: a tale of New Year EvesStress Testing at Twitter: a tale of New Year Eves
Stress Testing at Twitter: a tale of New Year EvesHerval Freire
 

Similar to Real-Time Candy Box Stats (20)

Realtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsRealtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_js
 
EE4414 Multimedia Communication Systems II
EE4414 Multimedia Communication Systems IIEE4414 Multimedia Communication Systems II
EE4414 Multimedia Communication Systems II
 
FInal Project MGMT 404
FInal Project MGMT 404FInal Project MGMT 404
FInal Project MGMT 404
 
Building and Scaling a WebSockets Pubsub System
Building and Scaling a WebSockets Pubsub SystemBuilding and Scaling a WebSockets Pubsub System
Building and Scaling a WebSockets Pubsub System
 
Comet from JavaOne 2008
Comet from JavaOne 2008Comet from JavaOne 2008
Comet from JavaOne 2008
 
Kafka used at scale to deliver real-time notifications
Kafka used at scale to deliver real-time notificationsKafka used at scale to deliver real-time notifications
Kafka used at scale to deliver real-time notifications
 
IoT with OpenPicus Flyport
IoT with OpenPicus FlyportIoT with OpenPicus Flyport
IoT with OpenPicus Flyport
 
SharePoint 2010 Virtualisation - SharePoint Saturday UK
SharePoint 2010 Virtualisation - SharePoint Saturday UKSharePoint 2010 Virtualisation - SharePoint Saturday UK
SharePoint 2010 Virtualisation - SharePoint Saturday UK
 
Reactive & Realtime Web Applications with TurboGears2
Reactive & Realtime Web Applications with TurboGears2Reactive & Realtime Web Applications with TurboGears2
Reactive & Realtime Web Applications with TurboGears2
 
SharePoint 2010 Virtualization
SharePoint 2010 VirtualizationSharePoint 2010 Virtualization
SharePoint 2010 Virtualization
 
Game Networking for Online games
Game Networking for Online gamesGame Networking for Online games
Game Networking for Online games
 
KubeCon EU 2016 Keynote: Pushing Kubernetes Forward
KubeCon EU 2016 Keynote: Pushing Kubernetes ForwardKubeCon EU 2016 Keynote: Pushing Kubernetes Forward
KubeCon EU 2016 Keynote: Pushing Kubernetes Forward
 
Game server development in node.js in jsconf eu
Game server development in node.js in jsconf euGame server development in node.js in jsconf eu
Game server development in node.js in jsconf eu
 
Unite2014 Bunny Necropsy - Servers, Syncing Game State, Security and Optimiza...
Unite2014 Bunny Necropsy - Servers, Syncing Game State, Security and Optimiza...Unite2014 Bunny Necropsy - Servers, Syncing Game State, Security and Optimiza...
Unite2014 Bunny Necropsy - Servers, Syncing Game State, Security and Optimiza...
 
Real-World Pulsar Architectural Patterns
Real-World Pulsar Architectural PatternsReal-World Pulsar Architectural Patterns
Real-World Pulsar Architectural Patterns
 
LoadTracer
LoadTracer LoadTracer
LoadTracer
 
Top Java Performance Problems and Metrics To Check in Your Pipeline
Top Java Performance Problems and Metrics To Check in Your PipelineTop Java Performance Problems and Metrics To Check in Your Pipeline
Top Java Performance Problems and Metrics To Check in Your Pipeline
 
Willing Webcam manual
Willing Webcam manualWilling Webcam manual
Willing Webcam manual
 
Mantis: Netflix's Event Stream Processing System
Mantis: Netflix's Event Stream Processing SystemMantis: Netflix's Event Stream Processing System
Mantis: Netflix's Event Stream Processing System
 
Stress Testing at Twitter: a tale of New Year Eves
Stress Testing at Twitter: a tale of New Year EvesStress Testing at Twitter: a tale of New Year Eves
Stress Testing at Twitter: a tale of New Year Eves
 

Recently uploaded

Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesSinan KOZAK
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024Scott Keck-Warren
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure servicePooja Nehwal
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraDeakin University
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions
 
Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Hyundai Motor Group
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...shyamraj55
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxnull - The Open Security Community
 

Recently uploaded (20)

Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
Transcript: #StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping Elbows
 
Unblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen FramesUnblocking The Main Thread Solving ANRs and Frozen Frames
Unblocking The Main Thread Solving ANRs and Frozen Frames
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024SQL Database Design For Developers at php[tek] 2024
SQL Database Design For Developers at php[tek] 2024
 
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure serviceWhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
WhatsApp 9892124323 ✓Call Girls In Kalyan ( Mumbai ) secure service
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning era
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food Manufacturing
 
Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2Next-generation AAM aircraft unveiled by Supernal, S-A2
Next-generation AAM aircraft unveiled by Supernal, S-A2
 
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptxVulnerability_Management_GRC_by Sohang Sengupta.pptx
Vulnerability_Management_GRC_by Sohang Sengupta.pptx
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
Automating Business Process via MuleSoft Composer | Bangalore MuleSoft Meetup...
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
 

Real-Time Candy Box Stats

  • 1. Real-Time Stats for Candy Box! by Jay Oster www. .com
  • 2. We like games here at PubNub, but not as much as we like real- time. Combine the two, and you’ve got pure mega-awesome. During the PubNub Hackathon, I took a popular text adventure game called Candy Box, and updated its stats page to provide a real-time overview of the global game statistics. The updated game can be played at candybox.pubnub.co, and the new real- time stats page is here. Full source code is available on github. In this article, we’ll guide you through how the game was modified, and how to build a very simple, yet hyper-scalable server infrastructure to serve real-time statistics. Today we’re using JavaScript on the client-side, and Python on the server- side. So let’s dig in! Since Candy Box is a very new game, there isn’t a public source code repository available. So I started this project by mirroring the original website, candies.aniwey.net, with wget. It’s also possible to load the URL into a browser and use File->Save Page As… to get a complete local copy of the game. www. .com
  • 3. Download the Game Code With all of the JavaScript downloaded, I had to make some minor adjustments in some places to get a few things working correctly. Loading and saving had to be modified; moving some of the original server-side logic right into JavaScript. I’ll spare the details, and the curious among you can have a look at load.js for some insight. To allow saving, the stats server just proxies save requests to the original server. The proxy is required because the original server does not support CORS, meaning newer browsers will reject the save attempt. www. .com
  • 4. Building the Stats Server With the baseline game ready, it’s time to start thinking about the server-side, particularly how to handle the high load that such a popular game will generate. High load is the reason the original stats page is updated only infrequently. The challenge is to record and report game statistics for thousands of simultaneous players in real- time, with very frequent updates (refreshing stats about once every second). With this in mind, I came up with the following list of requirements while designing the server: •  In-memory statistics aggregation •  Horizontally scalable •  Stateful-design, but distributed Sounds simple, right? Do calculations in memory to make it fast, and scale horizontally to handle any load. A panacea! But how can we sum and average hundreds of thousands of accounts per second? Well, we can cheat a bit by doing the summing on the client-side, and only keep the result of the sums on the server. In other words, client logic will be responsible for calculating the deltas between each status update, so the server just needs to worry about adding those deltas into its internal state. Therefore, each status update is a snapshot in time, and the server records only the sum over all snapshots. To share state between servers, we’ll use the same idea; deltas between updates, and periodically publish to a common PubNub channel to distribute changes to the internal state. This method of replication introduces its own challenges, such as update latencies with multiple servers. This is acceptable because the statistics in this game are quite volatile anyway. No one will notice any latencies. www. .com
  • 5. Server Implementation Details For the server, I decided to use Bottle to handle the REST interface, and gevent for non-blocking sockets. This will give us a great deal of flexibility for the server. After writing a few stubs for the REST interface, I started on the distribution mechanism, which is just a PubNub subscribe that handles messages from other servers. Ideally, each server will periodically share the updates they have received from clients. The server only needs to add deltas from the user to its own internal delta for distribution to other servers. It can’t distribute every client request, because traffic would be much too high. And it would defeat the purpose of horizontally scaling, anyway. This is also where distribution latency comes in; a delta from a client may take a few seconds to reach every server. www. .com
  • 6. www. .com Integrating PubNub gevent makes the subscribe super easy; just the normal Pubnub.subscribe(), wrapped in a call to gevent.spawn(). There are initially two such subscribes: a “control” subscription, which will recursively re-subscribe after handling each message, and a “sync” subscription to initially synchronize with other servers. The “control” subscription does three things: 1.  Respond to “sync” requests; providing the current game state to other servers 2.  Update config; allowing you to remotely reconfigure all servers 3.  Update game state; receive deltas from other servers I’ll explain more about the config updating later. After Both subscriptions are established, a “sync” request is published to any listening servers. The first “sync” response that comes within 5 minutes will cause the game state to be updated with the provided values. The server then goes to sleep until a user makes an “update” request. That will start a recursive timer (the interval is configurable) which will send stat updates to clients, and stat delta updates to other servers. The timer recursion automatically stops 5 minutes after the last user “update” request. And that’s about all there really is to the server. You could also do some other fancy things like persisting the state to disk periodically. It isn’t a lot of data to store, but these stats are also not critical, especially for the demo.
  • 7. Running the Stats Server Starting the server is easy as well, just start the main script with python, or run it directly in a shell (it has exec permissions and a shebang). The script accepts three optional arguments for listening IP, listening port, and config file. The config file is just JSON, in the same format as in config.py Here’s an example: $ python main.py 0.0.0.0 8999 ~/config.json www. .com
  • 8. Hacking the Client Back on the client-side, we just need a function to record the deltas, and another to send the “update” requests to the server. I decided to use the localStorage API to record the update state between each request, allowing the deltas to be calculated correctly even after restarting the browser. As far as security goes, I will be ignoring the possibility of cheaters for the demo. Stats can also be skewed by saves that have completed the game, because SPOILER ALERT the computer tab grants access to generating candies and lollipops at an impossible rate, and changing pretty much every variable in strange ways. SPOILER ALERT Client requirements are as follows: •  Turn a blind eye to cheaters (simplifies everything) •  Periodically send “update” requests (once every 5 seconds is a good start) •  Do not send “update” requests after the game has been completed The update interval will be once every 5 seconds by default, which will be quick enough to affect the stats updates that users end up seeing, and slow enough to handle a large number of simultaneous players with low server resources; With 2,000 users, the server only needs to handle 400 requests per second. The gevent-based server will easily handle that without a hiccup, even on commodity hardware. In fact, each server should handle about 1,000 concurrent connections. If more than 5,000 users are playing, just launch another stats server and put it behind nginx (reverse proxy) as a load balancer. More on that later. www. .com
  • 9. // Save to PubNub CandyBox stats server periodically if ((this.nbrOfSecondsSinceLastMinInterval % 5) === 0) { stats.update(); } The Hook main.js is where the game loop runs. It’s implemented as a simple interval that fires once per second. This is the place to add the stats updates. The code is very simple; just throttle a function call to once every 5 seconds: The stats.update() function is where the magic happens. It records the interesting bits of game state, calculates the delta, and sends the request to the stat server. www. .com
  • 10. $.each(currentUpdate, function (k, v) { if (typeof(v) !== "string") delta[k] = lastUpdate[k] - v; }); Delta Calculation The delta calculation is very easy (as you might imagine). I just keep a record of the last game stat after a successful “update” request (and save this object to localStorage), and the delta is calculated with a small iterator: Should be self-explanatory, but basically the difference between values in lastUpdate and values in currentUpdate are recorded as the delta, with a safety net for the code key (not shown) which is a string value. The delta is then sent to the stats server in an “update” request. The server does its work, and periodically publishes a message for the stats page. The listener code is in stats.js and you can see it does the percentage calculation client-side. It is otherwise incredibly basic. www. .com
  • 11. upstream stats_server { server localhost:8999; #server localhost:8998; #server localhost:8997; #server localhost:8996; keepalive 32; } server { listen 80; root /home/ubuntu/pn-candybox/public; index index.html; server_name candybox.pubnub.co; location /ping { proxy_pass http://stats_server; } Server Configuration With the client and server ready to go, it’s time to start thinking about the operational side of the project; configuring servers, DNS, an even dynamically scaling and remote-control reconfiguration. I’m using nginx as a host for the client code and it also doubles as a front-end load balancer for the stats server. The nginx config looks like this: location /save { proxy_pass http://stats_server; } location /update { proxy_pass http://stats_server; } location / { try_files $uri $uri/ /index.html; } } www. .com
  • 12. I did some load testing with ApacheBench and found that nginx with a single stats server can handle about 763 requests per second with 100 concurrent connections, or about 305 requests per second with 200 concurrent. All tests were done on a t1.micro AWS instance (E5507 @ 2.27GHz, 589MB RAM) running Ubuntu 13.04 with no TCP kernel tuning. This setup is good enough for our “2,000 simultaneous players” requirement. Dynamically Scaling With the server config in place, we can easily scale up by adding more upstream stats servers (commented in the config above). Then reloading nginx. The stats servers will automatically synchronize with one another over PubNub. We can also reconfigure the servers at runtime to tune the message publishing rates. I just have to open the PubNub console and publish a specially constructed message to the “candybox_update” channel. Here’s an example message that reconfigures the servers to publish only once every 5 seconds: Publish that message, and all servers will instantly adjust their message publishing interval to 5 seconds. This is just one example of what makes PubNub truly awesome. { "uuid" : "master", "action" : "config", "data" : { "update_interval" : 5 } } www. .com
  • 13. Wrapping Up With all of that, we now have Candy Box sending periodic updates to our stats server, and our stats servers periodically sending updates to the stats page. And it’s all done in a dynamically scalable way, with a ridiculously small memory footprint, and low bandwidth requirements. All done! Now you should play the game, check the stats, and fork me! www. .com