Query Restrictions
So you may have noticed that there is are some pretty tight restrictions imposed on Queries, you can't index more than two properties and you can't use more than one inequality filter (that's any of <, <=, >=, > !=). They're there for good reason, Flox is build on the Google App Engine and going outside these constraints would cause prices to skyrocket. So how do you get around this problem? Easy! You select everything and then filter in flash right?
var pid:String = Player.current.id;
var q:Query = new Query("Match");
// Select all the matches that this player is a part of
q.where("ownerId == ? || opponentId == ?",pid,pid);
q.find(function onComplete(broad:Array):void {
// Reduce the results by removing all the matches that have been seen
var results:Array = broad.filter(function(m:Match,i:int,a:Array):Boolean {
return ((m.ownerId == pid && m.ownerHasSeen == false) ||
(m.opponentId == pid && m.opponentHasSeen == false));
}
}, function onError(error:String,httpStatus:int):void {
// Do something with the error
});
- If you have more results than the limit (50 by default) then you may be dropping credible results in place of results that have already been seen. You can get around this by paginating, but it means you need to load every page before doing the filter pass.
- This whole process has two very critical performance implications. The first is that the filter pass can be a big sticking point depending on how complex the query and the second is that if you are serialising a number of results just to toss them back out, you create A LOT of garbage, something that can absolutely kill AS3 performance.
Despite the above issues, this example is actually exactly what we used as a first pass for Proleague. Perhaps you are like we were and you know that this feels yuck, but what else can you do?
Combining Properties
Using the example from above, lets start by thinking of the ideal query we'd like to make. We always want matches that haven't been seen by the owner, so something like the following would work.
"(ownerId == ? AND ownerHasSeen == false) OR
(opponentId == ? AND opponentHasSeen == false)"
"ownerId_ownerHasSeen == ? OR opponentId_opponentHasSeen == ?"
"abcdefg123-1" // Player id + true for the player having seen the match
"abcdefg123-0" // Player id + false for the player having not seen the match
Implementation
Because this is an optimisation specific to the Match entities I feel that this is a great time for some static helper functions.
public class Match {
// Combines the id and hasSeen properties into a single string
public static function mergeIdAndHasSeen(id:String,hasSeen:Boolean):String {
return id + "-" + (hasSeen)?"1":"0";
}
// Separates the id from the combined string
public static function unmergeId(mergedString:String):String {
var i:int = mergedString.indexOf("-");
return mergedString.substring(0,i);
}
// Separates 'has seen' from the combined string
public static function unmergeHasSeen(mergedString:String):Boolean {
return mergedString.charAt(mergedString.length-1) == "1";
}
// Constructor + properties omitted
}
Hopefully this is pretty self explanatory but basically we have three functions, one for combining the two properties and another two for separating them back out again. Using these helper functions it should be really easy to tweak our code to make use of them. First we need to add the combined properties to the Match class.
public class Match {
// Static helper functions omitted
public function get ownerId_ownerHasSeen():String {
return Match.mergeIdAndHasSeen(this.ownerId,this.ownerHasSeen);
}
public function set ownerId_ownerHasSeen(value:String):void {
this.ownerId = Match.unmergeId(value);
this.ownerHasSeen = Match.unmergeHasSeen(value);
}
public function get opponentId_opponentHasSeen():String {
return Match.mergeIdAndHasSeen(this.opponentId,this.opponentHasSeen);
}
public function set opponentId_opponentHasSeen(value:String):void {
this.opponentId = Match.unmergeId(value);
this.opponentHasSeen = Match.unmergeHasSeen(value);
}
// Constructor + other properties omitted
}
By converting the properties inside the getters and setters, we ensure that the wrong value will never be returned from these functions. Because our helper methods are static we can use the same merge/unmerge code when building our query.
var pid:String = Player.current.id;
var p:String = Match.mergeIdAndHasSeen(pid,false);
var q:Query = new Query("Match");
q.where("ownerId_ownerHasSeen == ? || opponentId_opponentHasSeen == ?",p,p);
So that's it! The optimisation is well worth your trouble, even if you aren't hitting the index limit, reducing two indexes down to one can save you a tonne on operations too, so make sure to do it! The implementation ends up clean enough that you need not worry about loose bits of code, everything ends up nice and neat.
Hope this helps someone! Next time we'll discuss some entity refresh headaches and also the very cool email login system that Flox uses. If you have a request/question/comment, sing out! We'd love to hear from you.
Casino Roll
ReplyDeleteJoin Casino worrione Roll casino-roll.com Online https://septcasino.com/review/merit-casino/ Casino Roll 2021 https://octcasino.com/