2
\$\begingroup\$

I'm creating a gradebook. Many students earn a grade in many standards each. I'm hoping to get help analyzing the code below for efficient use of asynchronous result fetching. ofy is an Objectify object, which is basically just a convenience wrapper over the async fetching capabilities of AppEngine.

public List<StandardScoreDAO> getStandardScores(
        ArrayList<Long> studentIdLongs, ArrayList<Long> standardIdLongs)
{
    ObjectifyService.register(StandardScoreDAO.class);
    Objectify ofy = ObjectifyService.begin();

    List<Iterator<StandardScoreDAO>> results = new ArrayList<Iterator<StandardScoreDAO>>();

    for (Long studentId : studentIdLongs)
    {
        for (Long standardId : standardIdLongs)
        {
            results.add(ofy.query(StandardScoreDAO.class)
                    .filter("measuredUserId =", studentId)
                    .filter("standardId =", standardId)
                    .iterator());
        }
    }

    ArrayList<StandardScoreDAO> ret = new ArrayList<StandardScoreDAO>();

    for (Iterator<StandardScoreDAO> i : results)
    {
        while (i.hasNext())
        {
            ret.add(i.next());
        }
    }

    return ret;
}

As you can see, if there are 100 studentIds and 100 standardIds, this is 10,000 queries. AppEngine will only run 10 in parallel. Aiee! I don't know if it's at all realistic to expect this to finish in 30 seconds (testing soon), but I wonder if anyone sees a much better way to organize this.

\$\endgroup\$
2
\$\begingroup\$

With Objectify, you have the option to use the "in" operator in a query. Moreover, it becomes clear from the second block of nested loops that you are only looking for the intersection of the two sets of measuredUserIds and standardIds. So, a single statement should do the trick:

public List<StandardScoreDAO> getStandardScores(
        ArrayList<Long> studentIdLongs, ArrayList<Long> standardIdLongs) {
    ObjectifyService.register(StandardScoreDAO.class);
    Objectify ofy = ObjectifyService.begin();

    Iterator<StandardScoreDAO> i = ofy.query(StandardScoreDAO.class)
                    .filter("measuredUserId in", studentIdLongs)
                    .filter("standardId in", standardIdLongs)
                    .iterator();

    List<StandardScoreDAO> ret = new LinkedList<StandardScoreDAO>();

    while (i.hasNext()) {
        ret.add(i.next());
    }

    return ret;
}

If you access a list merely by iteration (and not by random access or by index), and do not know its size on creation, LinkedList might give you a performance benefit.

\$\endgroup\$
  • \$\begingroup\$ Are Objectify's "in" operators immune from the 30-sub-query limit? With JDO, for example, I couldn't use studentIdLongs.contains(:measuredUserId) && standardIdLongs.contains(:standardId) because it generates many more than 30 sub-queries. Thanks for the tip on LinkedList - that makes sense! \$\endgroup\$ – Riley Apr 13 '11 at 12:55

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.