views:

652

answers:

1

To give an example of the kind of request that I can't figure out what else to do for:

The application is a bowling score/stat tracker. When someone enters their scores in advanced mode, a number of stats are calculated, as well as their score. The data is modeled as:

Game - members like name, user, reference to the bowling alley, score Frame - pinfalls for each ball, boolean lists for which pins were knocked down on each ball, information about the path of the ball (stance, target, where it actually went), the score as of that frame, etc GameStats - stores calculated statistics for the entire game, to be merged with other game stats as needed for statistics display across groups of games.

An example of this information in practice can be found here.

When a game is complete, and a frame is updated, I have to update the game, the frame, every frame after it and possibly some before it (to make sure their scores are correct), and the stats. This operation always flags the CPU monitor. Even if the game isn't complete, and statistics don't need to be calculated, the scores and such need to be updated to show the real-time progress to the user, and so these also get flagged. The average CPU time for this handler is over 7000 mcycles, and it doesn't even display a view. Most people bowl 3 to 4 games per series - if they are entering their scores realtime, at the lanes, that's about 1 request every 2 to 4 minutes, but if they write it all down and enter it later, there are 30-40 of these requests being made in a row.


As requested, the data model for the important classes:

class Stats(db.Model):
  version = db.IntegerProperty(default=1)
  first_balls=db.IntegerProperty(default=0)
  pocket_tracked=db.IntegerProperty(default=0)
  pocket=db.IntegerProperty(default=0)
  strike=db.IntegerProperty(default=0)
  carry=db.IntegerProperty(default=0)
  double=db.IntegerProperty(default=0)
  double_tries=db.IntegerProperty(default=0)
  target_hit=db.IntegerProperty(default=0)
  target_missed_left=db.IntegerProperty(default=0)
  target_missed_right=db.IntegerProperty(default=0)
  target_missed=db.FloatProperty(default=0.0)
  first_count=db.IntegerProperty(default=0)
  first_count_miss=db.IntegerProperty(default=0)

  second_balls=db.IntegerProperty(default=0)
  spare=db.IntegerProperty(default=0)
  single=db.IntegerProperty(default=0)
  single_made=db.IntegerProperty(default=0)
  multi=db.IntegerProperty(default=0)
  multi_made=db.IntegerProperty(default=0)
  split=db.IntegerProperty(default=0)
  split_made=db.IntegerProperty(default=0)

class Game(db.Model):
  version = db.IntegerProperty(default=3)
  user = db.UserProperty(required=True)
  series = db.ReferenceProperty(Series)
  score = db.IntegerProperty()
  game_number = db.IntegerProperty()
  pair = db.StringProperty()
  notes = db.TextProperty()
  simple_entry_mode = db.BooleanProperty(default=False)
  stats = db.ReferenceProperty(Stats)
  complete = db.BooleanProperty(default=False)

class Frame(db.Model):
  version = db.IntegerProperty(default=1)
  user = db.UserProperty()
  game = db.ReferenceProperty(Game, required=True)
  frame_number = db.IntegerProperty(required=True)
  first_count = db.IntegerProperty(required=True)
  second_count = db.IntegerProperty()
  total_count = db.IntegerProperty()
  score = db.IntegerProperty()
  ball = db.ReferenceProperty(Ball)
  stance = db.FloatProperty()
  target = db.FloatProperty()
  actual = db.FloatProperty()
  slide = db.FloatProperty()
  breakpoint = db.FloatProperty()
  pocket = db.BooleanProperty()
  pocket_type = db.StringProperty()
  notes = db.TextProperty()
  first_pinfall = db.ListProperty(bool)
  second_pinfall = db.ListProperty(bool)
  split = db.BooleanProperty(default=False)
+1  A: 

A few suggestions:

  • You could store the stats for frames as part of the same entity as the game, rather than having a separate entity for each, by storing it as a list of bitfields (stored in integers) for the pins standing at the end of each half-frame, for example. Let me know if you want more details on how this would be implemented.
  • Failing that, you can calculate some of the more interrelated stats on fetch. For example, calculating the score-so-far ought to be simple if you have the whole game loaded at once, which means you can avoid having to update multiple frames on every request.
  • We can be of more help if you show us your data model. :)
Nick Johnson
I had thought about using the bitfield idea for pinfalls, but that is not very queryable, at least not in GAE. For instance, I want to grab all the frames where I left some form of the washout (headpin, 2 pin, and 10 pin are standing, but 3, 5, and 9 aren't).
Chris Marasti-Georg
Do you want to grab all the frames from one game that meet that criteria, or all frames from all games? If the former, a linear search would be easier.
Nick Johnson
Either way, I think you definitely need some way to integrate frames into the main entity, and reduce the total number of fields - remove those that are trivially calculatable from others. There's a fairly high per-field overhead when encoding entities for the datastore.
Nick Johnson
I would want to find frames from all games (for a user), or a certain subset of games. In the Frame object, only 4 properties are possibly calculable from others, and they aren't necessarily trivial (e.g. is_split). I thought GAE encouraged storing lots of data?
Chris Marasti-Georg
It does, but in the form of lots of entities, organised so that you have to touch as few as possible per request. My suggested strategy would be to store only anything you need indexed, and anything you need to reconstructy the full dataset, and anything significantly expensive to calculate.
Nick Johnson
The reason I ended up adding the stats class was that when I had a league with 16 weeks, each of those with 4 games, calculating the stats for all of that was causing to use too much CPU. Hmmm... I guess I may just need to wait for paid quota increases? Or look at other hosting?
Chris Marasti-Georg
Or break it up into smaller requests - don't try and handle processing 64 games in a single request.
Nick Johnson
Any tips on how to accomplish that?
Chris Marasti-Georg
If your app is AJAX-y, it can submit a smaller set of games in each request - for example, by submitting them as the user enters them.
Nick Johnson