4
votes

I love Objectify's "just use ofy()" convenience to get an Objectify instance but I'm running into a use case where I could use some advice.

My use case for the datastore is such that in one part of my process I will be writing entities in a long running process. Hundreds of thousands of entities. They will be well scattered across time / entity-groups (so datastore contention isn't really an issue for me). During this long running process I will not have the need to read a datastore entity more than exactly once.

I know I can disable "second level" cache by using Objectify.cache(false) to create an instance that will not use memecache. That's great.

My concern is over the session cache. I did just a little peeking into the Objectify code and it seems that in WriteEngine.java when we do a "save()" for an entity we encounter:

                // Also stuff this in the session
                session.addValue(key, obj);

So objectify is holding onto my items in memory? I'd like to turn off saving entities in any sort of cache of possible.

2
(1) Zeehad, If you read this: I rolled back your change because, this question is very specific to objectify and related to caching in the session so the memcached tag is not really appropriate. (2) This sounds like a question for @Stickfigure, hopefully he'll see question. You may want to create a new issue in the objectify github repository github.com/objectify/objectify/issues . (3) If you were to ask me, I'd simply use the low-level datastore api for this kind of job.konqi
Thanks for the comment @konqi. I have another part of my application which is read-heavy.. and in that scenario objectify is a blessing. I'm sure it's possible but I'm leary of code low-level datastore API to write entities and objectify to read them. Maybe I should get over my fear... or post a new question: "what are the rules of the road for writing entities to be ready by objectify?"Robert
At least that would be an easy question to answer: Simply use the same types for properties in Objectify and the low level api. The datastore uses protocol buffers and Objectify "only" does object relational mapping. The actual data transformation to protobuf is handed down to the low level api by objectify.konqi

2 Answers

2
votes

So to be clear... @stickfigure 's answer is the definitive answer for standard Objectify. I'm hoping that he can look over this code and comment. And I'm hoping that Objectify might have more caching options in the future... even perhaps at the entity level! Until then, below is a hack that I"m using that perhaps someone else will find useful.

Those if you who have been using Objectify for a while might remember this service model idea. You use this OfyService by including

import static com.industryopenings.seeker.shared.OfyService.ofyw;

at the top of your class. Then you just call ofyw() where you would normally call ofy(). Please note... mixing of ofyr() and ofyw() not my intent with this code. Mixing could have potentially strange results. Particularly in transactions.

This OfyService does two things...

  1. Ensures instances of Objectify created with ofyw() have cache(false) specification instead of the default cache(true)
  2. Periodically calls the clear() method for you after OFYW_USE_COUNT_THRESHOLD calls to ofyw()

Bear in mind we don't really have much ability to inspect the objectify session... there is no object size or object count logic here. It assumes you periodically call ofyw() (without holding onto the Objectify instance) and just clears session periodically for you.

import com.googlecode.objectify.*;
import com.googlecode.objectify.impl.ObjectifyImpl;

public final class OfyService {
    public final static int OFYW_USE_COUNT_THRESHOLD = 10;
    public static int currentReferenceCount = 0;
    private static ObjectifyFactory defaultFactory = new ObjectifyFactory();
    private static ObjectifyFactory noCacheFactory = new NoCacheFactory();

    // This factory always returns a no-cache instance.
    // BUT: end users can be foolish and turn it back on if they want... having potentially ripple effects downstream
    // Best practice if you're going to use this OfyService is to never call the cache(boolean) method
    private static class NoCacheFactory extends ObjectifyFactory {
        @Override
        public Objectify begin() {
            return new ObjectifyImpl<>(this).cache(false);
        }
    }

    // Note!  We probably need to register our classes in both factories
    static {
        defaultFactory.register(Doc.class);
        defaultFactory.register(SiteLogEntry.class);
        defaultFactory.register(SiteLogRunSummary.class);

        noCacheFactory.register(Doc.class);
        noCacheFactory.register(SiteLogEntry.class);
        noCacheFactory.register(SiteLogRunSummary.class);
    }

    // ofyr to get an Objectify... with the "r" to denote read / caching enabled.
    public static Objectify ofyr() {
        ObjectifyService.setFactory(defaultFactory);
        return ObjectifyService.ofy();
    }

    // ofyr to get an Objectify... with the "w" to denote writing / no caching enabled.
    public static Objectify ofyw(){
        // This will ensure we're not using second level cache (memecache)
        ObjectifyService.setFactory(noCacheFactory); 

        // In lieu of a true solution we're simply going to clean house every X times ofyw() is called
        currentReferenceCount++;
        Objectify o = ObjectifyService.ofy();
        if(currentReferenceCount > OFYW_USE_COUNT_THRESHOLD){
            o.clear();
            currentReferenceCount = 0;
        }
        return o;
    }
}

Hope this helps someone else.

1
votes

Unfortunately, right now the only way to do this is to call ofy().clear() periodically. I see you have added an issue to the github tracker, which is good.