Skip to main content

Memcaching the right stuff

I am hosting PewPew's scores on the Google App Engine. I chose the GAE because it was free for small websites that stay under a free quota. Unfortunately for me, they recently lowered the free quota, and I started having to pay the minimum fee of $9/month. It's not much, but I want PewPew to keep on working forever, and $9 per month forever amounts to an infinite amount of money, which I don't plan on having.

There were two areas where I was over the free quota: the database was too large (the free quota is 1GB), and I was using to much CPU for the database requests.

Reducing the database size

The database was taking more than 1GB because I never deleted any of the submitted scores/replay. I wrote an handler that erased the lowest scores. Erasing ~400k scores cost me approximately 4$.
If you have a database with tens of millions of entries, doing operations on them can become expensive!
The handler could only erase 1000 entries at a time before timing out, so I "automatized" its call.
Now, I only have ~100k entries left, taking 792 MB. Out of those, 491 MB are used by the replays, which amounts to 23.5 days of non-stop action.

Reducing the CPU usage

I thought optimizing the CPU usage was just going to be a matter of caching the queries. Turns out, there's a right way and a wrong way to do that.

Initially, the scores for the 8 game modes were generated this way:
    for level in levels:
      query_str = get_query(level)
      scores = db.GqlQuery(query_str)
      for entry in scores:
        #write the score, player_name

And to cache the results, I did this :
    for level in levels:
      query_str = get_query(level)
      scores = memcache.get(query_str)
      if scores is None:
        scores = db.GqlQuery(query_str)
        memcache.set(query_str, scores, 60 * 60 * 24)
      for entry in scores:
        #write the score, player_name


But doing this did not improve my CPU usage, and profiling my app showed that I was still doing a lot of database call, even though none appear in my code. I assume I was not caching the entries, only references to the results. Reading the entries still required doing some database calls:



The correct way to do things is to manually put in the cache the strings and numbers you need to store, not references to the entries in your database.
Once this was done, I got the expected 8 memcache calls and divided the response time by 40:


Now the players can get the scores instantly, and I am back to paying $0/forever.

Comments

  1. Interresting read. I'm glad you are thinking about PewPew this way, I'd love playing it in some years still.

    ReplyDelete
  2. Great! I don't bye a lot of Apps, but I'm glad I bought yours. Graphics, control, campaign... everything works really great. Keep with the good work :)

    ReplyDelete

Post a Comment

Popular posts from this blog

PewPew Live's look in a nutshell

Occasionally someone will asked how I obtained the PPL look. In a nutshell: Draw everything with lines, including the text and the various icons. It's a lot of work, but besides looking unique it creates a consistent appearance which is a thing that a lot of indie games struggle with. The lines are screen-space projected lines with miter joins. Draw the lines with additive rendering. This means that if a red and green line overlap, the overlap will be yellow. There are a few things not drawn with additive rendering (like the background of buttons to improve readability), but they are exceptions. Add bloom. There's lots of different bloom implementations. Nowadays I use a bloom that is similarly to the one in  blender's eevee . If you see banding, use dithering. Optional: Add even more post-processing like (very slight) chromatic aberration, lens dirt, scan lines, curved monitor, and vignette. No post-processing, just lines Bloom! Ignore the missing bloom at the top All the...

A general state rollback technique for C++

I wanted to write this post for a while. It describes a C++ technique to implement rollback in the context of multiplayer games that I feel is quite interesting and useful. The tl;dr is: don't bother serializing individual objects, just rollback all the memory. Rollback-based multiplayer I've been working on a multiplayer version of PewPew, and for reasons that are outside of the scope of this post, I chose to implement multiplayer with deterministic lockstep and rollback. The basic idea behind rollback-based multiplayer is that the inputs of players are replicated to all the players. Whenever a player receives the inputs of another player, the state of the game is rolled back to the point where the input happened and fast-forwarded back to the present so that the state shown to a player takes into account the inputs of the other players. Because history is being re-computed, some events get undone. For example, it's possible a player saw themselves taking a bonus, but aft...

Ridiculously cheap depth of field effect for lines

I'm working on PewPew's sequel, for which I've revamped the graphics. Instead of drawing lines directly using OpenGL, each individual line segment is made up of two triangles whose vertexes are computed with shaders. Getting lines in 3D space to be properly displayed on a 2D screen is not trivial. In PewPew's sequel I use the screen-space projected lines, a technique very well described in the  Drawing Lines is Hard  post. The upside of drawing the lines yourself is that you are fully in control, which allows you to implement nice things such as joints, perspective, and even simulate depth of field. https://en.wikipedia.org/wiki/Depth_of_field Usually depth of field (DoF) in video games is implemented using a post-processing step that blurs the pixels with an intensity that is a function of the depth of the pixels. When we are rendering lines, we can approximate DoF directly when rendering the lines by having the vertex shader increase the width of lines and r...