This discussion is archived
1 2 Previous Next 15 Replies Latest reply: Aug 19, 2013 8:49 PM by user5243669 RSS

10.1.3 cache invalidation capabilities does not work

212206 Newbie
Currently Being Moderated
I am dealing with a situation where ,overnight, a series of batch processes update database tables, leaving my Toplink cache with potentially hundreds of stale objects.

To solve this issue, I intend to use the new 10.1.3 (object invalidation, class invalidation, time invalidation ...etc) capabilities of cache invalidation but I note that this invalidation work only for execution of new query on invalidated objects.
Access to an invalidated object by graph object navigation return stale object !

I have write a small example with TopLink demonstration project (employee-project)

package examples.sessions.twotier.examples.cache;

import examples.sessions.twotier.Example;
import examples.sessions.twotier.model.Address;
import examples.sessions.twotier.model.Employee;

import oracle.toplink.expressions.Expression;
import oracle.toplink.expressions.ExpressionBuilder;

public class PhoneCache extends Example {

Employee employee;
Address address;

public void run() {
log("\t find one employee ...: ");
findEmployee();
log("\t Résult of findEmployee() : " + employee);


log("\t 1st execution of address = employee.getAddress() ... ");
address = employee.getAddress();
log("\t Result of 1st execution of address = employee.getAddress() : " + address);

log("\t invalidate address ... : ");
getSession().getIdentityMapAccessor().invalidateObject(address);

log("\t execute address = employee.getAddress() ... ");
address = employee.getAddress();
log("\t result of address = employee.getAddress() : " +
address);

log("\t explicitly invalidate address by query findAddressById(address.getId()) ...: ");
findAddressById(address.getId());
log("\t result of explicit invalidation address by query findAddressById(address.getId()) : " +
address);

}

private void findEmployee() {
employee = (Employee)getSession().readObject(Employee.class);
}

private void findAddressById(long adrId) {
ExpressionBuilder builder = new ExpressionBuilder();
Expression exp = (builder.get("id").equal(adrId));
address = (Address)getSession().readObject(Address.class, exp);
}


public static void main(String[] args) {
new PhoneCache().runExample();
}
}



When i execute this example i get this result :

[TopLink - Précis] : 2006.05.01 06:56:54.248--DatabaseSessionImpl(841)--Connection(878)--Thread(Thread[main,5,main])--SELECT t0.EMP_ID, t1.EMP_ID, t0.GENDER, t1.SALARY, t0.F_NAME, t0.L_NAME, t0.MANAGER_ID, t0.ADDR_ID, t0.END_DATE, t0.START_DATE, t0.END_TIME, t0.START_TIME, t0.VERSION FROM EMPLOYEE t0, SALARY t1 WHERE (t1.EMP_ID = t0.EMP_ID)
     Résult of findEmployee() : Employee: Jill Austin
     1st execution of address = employee.getAddress() ...
[TopLink - Précis] : 2006.05.01 06:56:54.589--DatabaseSessionImpl(841)--Connection(878)--Thread(Thread[main,5,main])--SELECT ADDRESS_ID, P_CODE, COUNTRY, PROVINCE, CITY, STREET FROM ADDRESS WHERE (ADDRESS_ID = 8)
     Result of 1st execution of address = employee.getAddress() : Address: 1111 Mooseland Rd., Calgary, AB, Canada
     invalidate address ... :
     execute address = employee.getAddress() ...
     result of address = employee.getAddress() : Address: 1111 Mooseland Rd., Calgary, AB, Canada
     explicitly invalidate address by query findAddressById(address.getId()) ...:
[TopLink - Précis] : 2006.05.01 06:56:54.599--DatabaseSessionImpl(841)--Connection(878)--Thread(Thread[main,5,main])--SELECT ADDRESS_ID, P_CODE, COUNTRY, PROVINCE, CITY, STREET FROM ADDRESS WHERE (ADDRESS_ID = 8)
     result of explicit invalidation address by query findAddressById(address.getId()) : Address: 1111 Mooseland Rd., Calgary, AB, Canada




Navigation to invalidated object ( employee.getAddress() ) return invalidated Object !

Object is realy invalidated by explicit new query on it (findAddressById()). But in real application triggered relation (employee.getAddress()) remain in memory.

It is an TopLink bug or bad programming issue in my approch ?


Regards
  • 1. Re: 10.1.3 cache invalidation capabilities does not work
    JamesSutherland Pro
    Currently Being Moderated
    You are correct that it is possible to get access to objects that have been invalidated if you access them through a relationship from an object that is valid.

    If the related objects both are invalidated at the same time, then this will not be an issue, as the first object would have been invalidated and refreshed and the related objects would then be detected to be invalid and refreshed as well. So this depends on the invalidation used, and the complexity of the relationships between objects that use invalidation and those that do not. If this is a major issue for you then you may wish to avoid having relationships between objects that do not use invalidation, and those that do.

    If you wish to ensure that an object is valid at any time, you could perform a readObject() on the object to ensure this.

    Note that invalidation is just a strategy used to avoid stale data, as always optimistic locking should still be used to avoid write conflicts.

    TopLink also provided other strategies to avoid stale data including refreshing and isolated caching.
  • 2. Re: 10.1.3 cache invalidation capabilities does not work
    212206 Newbie
    Currently Being Moderated
    We have the same problem when using new 10.1.3 bulKupdate API : UpdateAllQuery.

    We think that TopLink cannot detect that the "invalid" reference memory Object.


    If it is the case then we let us reflect to modify the implementation of our
    classes as follows:

    1- Adding new transcient (not persistant) attribute "isValid (true/false)" on
    each implementation of model classes.

    2- Modify inter-objects navigation methods (objectA.getObjectB() method).
    -- Example : Person to PhoneNumber navigation
    Class Person {
    String name;
    PhoneNumber phoneNumber;
    ...
    isValid boolean;

    public PhoneNumber getPhoneNumber(){
    if (!this.phoneNumber.isValid)
    refresh(this.phoneNumber);
    return this.phoneNumber
    }


    To do this implementation we need to collect invalidation evenement to
    modify "isValid" attribute : set isValid=false when object is invalidated.

    We need to know:

    1- if this approach is valid

    2- how to track invalidation coming from :
    * TopLink invalidation "Cache coordination"
    ** programmatic invalidation in the same application by the means of the
    APIs : invalidateObject, TimeToLiveCacheInvalidationPolicy,
    DailyCacheInvalidationPolicy


    Regards
  • 3. Re: 10.1.3 cache invalidation capabilities does not work
    JamesSutherland Pro
    Currently Being Moderated
    There is an API isValid() on the Session IdentityMapAccessor that will return if an object is valid or not. If an object is using an invalidation policy it can become invalid at any time based on its read-time, so it would not be possible to create an invalidation event.

    Note that checking isValid() on every getPhoneNumber() access method could be a performance issue depending on how often the method is called.
  • 4. Re: 10.1.3 cache invalidation capabilities does not work
    212206 Newbie
    Currently Being Moderated
    Thank you,


    I do not think of being the only customer to want to use new APIs : UpdateAllQuery, DailyCacheInvalidationPolicy or invalidateObject.

    You know very well that one obtains the objects by request : findEmmplyeeById() but more especially by navigation in the graph of objects: Employee.getManager().

    How would you make to prevent that Employee.getManager() brings back stale Employee invalidated by updateAllQuery(or DailyCacheInvalidationPolicy or invalidateObject)?

    Thank you for your invaluable assistance
  • 5. Re: 10.1.3 cache invalidation capabilities does not work
    504035 Explorer
    Currently Being Moderated
    What mean "is possible" in "You are correct that it is possible to get access to objects that have been invalidated if you access them through a relationship from an object that is valid."?

    Is it controllable by settings on the query?

    If I have in client cache (not uow) with:
    Person valid
    Name valid
    Address invalid

    Then I do a query by id on Person:
    - via client session, address will be updated?
    - via uow, address will be updated?

    If the answer is no to both:
    - why the client cache has a flag that mark the object invalidated?
    - which use case could have good reason to return the invalidated object?
    - how costly is to TopLink code to verify the flag?
  • 6. Re: 10.1.3 cache invalidation capabilities does not work
    cdelahun Pro
    Currently Being Moderated
    Hello,

    Assuming Person -> address is 1:1. If Person is obtained from the cache (and not refreshed), it is returned as is - it will reference the address even if address is set to be invalid.

    A feature could be entered to have toplink check the invalid flag but to what extent - if every time you do a cache access, does the entire object tree need to be traversed to check for invalidation? If so, this defeats the performance benifit of having a cache - No matter how performant a validation check is, just the tree traversal itself takes time, not including the posibility that data actually needs to be refreshed.

    If you really need to ensure data is always valid, there are a number of options available to you. Off the top, you don't need to actually map the relationship - you can have your getAddress or getManager method query for it each and every time. A reference is just a way of caching the relationship between objects in the object itself. If you don't want it cached, don't map it.

    Another option is to do an invalidation check and refresh if neccessary as James suggested. I do not know the exact performance implications since it depends on your application. There are probably a number of other solutions someone else might come up with.

    Best Regards,
    Chris
  • 7. Re: 10.1.3 cache invalidation capabilities does not work
    504035 Explorer
    Currently Being Moderated
    You said: "does the entire object tree need to be traversed to check for invalidation? If so, this defeats the performance benefit of having a cache"

    Yes, TopLink already traverse the entire object tree when reading through unit of work. So, looking for the invalidation flag cost very little and should be the default.

    Lot of TopLink developers are misguided and don't always read though the unit of work and it's pretty bad, because they don't get thread safe code, don't have transaction isolation, and cannot see new and updated objects in the result of queries.

    That's many bugs by design. TopLink behavior should be to be consistent, thread-safe, and don't return stale data when already know that the data is stale.

    Options could be provided to break consistency for performance reason, but in a persistent framework where calling another process "Database" to execute expensive task "query" I doubt that any Java code trying to do the right thing can be significantly slow.
  • 8. Re: 10.1.3 cache invalidation capabilities does not work
    cdelahun Pro
    Currently Being Moderated
    Hello,

    Sorry for the long delay. In essence, in an Employee->Manager relationship, you are requesting that on employee.getManager(), TopLink always query for the manager object - since the query will hit the cache if the manager there and is valid. While possible if TopLink indirection is used, this should be done as a feature as it would defeat the purpose of mapping the relationship in the employee object.

    If you want to ensure it is always valid, I'd suggest mapping foreign keys using direct to field mappings - ie do not map the relationship, instead using a query to return the manager in the getManager() method.

    The other alternative James already mentioned, which is to invalidate key points in your object tree instead of just individual objects - that way reading in any object from the tree will cause the stale parts to refresh as well.

    Hope this helps,
    Chris
  • 9. Re: 10.1.3 cache invalidation capabilities does not work
    504035 Explorer
    Currently Being Moderated
    I'm not asking for on the fly TopLink behavior be triggered outside TopLink API transparently when doing something like Employee->Manager. What I want is that the invalid objects in session cache are NEVER returned from called TopLink APIs but refreshed first if the user calling the API doesn't explicitly ask for an optimization introducing stale data.

    The most important scenario for us, and it's obviously a no brainer, when session object are migrated/cloned to UOW, the session object should be refreshed before its migration.

    You said: "invalidate key points in your object tree instead of just individual objects - that way reading in any object from the tree will cause the stale parts to refresh as well"

    If the root is pointing to shared data, like Employee -> Organization. That means you have thousands of Employees sharing the same Organization instance. So what do you do when Organization is modified? Invalid all Employees even if not pointing to the same Organization? Or try to do clever in-memory query to just invalidate hundreds?

    Wow! Quite an optimization to invalidate hundreds and thousands of object + the cost of this manual babysittings. I can find thousand of case like that in my application.

    Nobody in our development team understand all the layers. The possible path of execution is unlimited.

    We need simple rules, like: "TopLink will not return object marked invalid in TopLink cache".

    Is it too much to ask?

    Quote: "Works by design", "Default to do the right thing", "Pseudo optimization"
  • 10. Re: 10.1.3 cache invalidation capabilities does not work
    cdelahun Pro
    Currently Being Moderated
    Hello,

    I think we are both in agreement - this would be a great feature. The point I tried to make previously is that you are not accessing TopLink api's and getting stale data, you are accessing your object's api and getting its reference to a stale object. Employee to Manager like I said, will always cause the potential for stale data. For instance, you read employee and your app has a link to it. Manager becomes invalidated, after which your thread accesses Employee's manager. Unless you are saying TopLink should intercept all get methods on your objects and instead of returning it always query to see if its has been invalidated, you will get stale data - much in the same way as Employee could have been invalidated just after you read it.

    TopLink valueholders could be used to do that, as getManager() would need to get the manager object from the valueholder if indirection is used - which could check if it was invalid. This is why I believe this would be an interesting feature for TopLink to implement. But you can also do this yourself through your object's getManager method - just query for the manager object rather than blindly returning Employee's link to it. This would have exactly the same effect - if its valid it gets returned, if not it gets queried/refreshed from the database.

    TopLink api's, through invalidation, will give you refreshed data on the objects you directly request. It is a great feature than can help reduce the potential for stale data. But, the only way to ensure you never get stale data referenced would be to not map those references and instead always query for them.
  • 11. Re: 10.1.3 cache invalidation capabilities does not work
    504035 Explorer
    Currently Being Moderated
    Your solution when using indirection are interesting. However, our application is not using indirection.
    you are not accessing TopLink api's and getting stale data, you are accessing your object's api and getting its reference to a stale object
    I think your point is reasonable if my application would be reading session objects. Even do, my application will probably want to "ask" TopLink when it queries to have only valid objects returned, the root and all children valid. However, if my application would be using session object and cache some reference in application custom map I would be expecting that the root and the children may be stale.

    But my application is only dealing with UOW objects. When I query via UOW, I'm expecting that only valid objects will be returned (including only valid children) and if some objects are invalid, they will be refreshed before been returned. So this is a TopLink API, and right now, there is no way to ask TopLink to not return me children that it already knows that are invalid. Moving an object from session cache to UOW already involve walking all the tree and doing 2 copy (clone,backup) of the session object, so checking a flag in the CacheKey is not something expensive.

    The punch is this: objects migrated in UOW are not overriden, so if I query Employee that is valid but the manager is invalid, then the invalid manager is migrated to the UOW. Then even if after my code query for manager directly in the same UOW, the old dirty manager will still be there!

    To resume:
    - We need the right default, the right default is not speed, but correctness
    - When reading via UOW, correctness doesn't cost much
    - There is a difference between having the wrong default and having no API available to do the right thing
  • 12. Re: 10.1.3 cache invalidation capabilities does not work
    DougClarke Employee ACE
    Currently Being Moderated
    Sebastien,

    Your assertion that the UnitOfWork should not create a working copy for an object from the shared cache which is marked invalid seems reasonable to me. We are working through the different options for what TopLink should do when registering a graph and an invalid shared object is encountered. While refreshing each object as registered seems reasonable we have concerns that this may not be efficient in all scenarios.

    I believe we can work around this issue with a DescriptorEventListener.postClone event listener. I'll investigate the possibility of using this temporarily while development reviews longer term solution options.

    Doug
  • 13. Re: 10.1.3 cache invalidation capabilities does not work
    DougClarke Employee ACE
    Currently Being Moderated
    Sebastien,

    I built a small utility class that functions as a descriptor-event-listener and detects when an object has been registered that is invalid in the shared cache. After I register objects in a UnitOfWork I can then retrieve this set and handle them in an application specific manor.

    It was pointed out to me that always doing a refresh when this scenario is found may not be correct for all applications.

    To use this you would need to add this event listener to all descriptors of interest.
        public class CollectInvalidHandler extends DescriptorEventAdapter {
            private static final String INVALID_OBJECTS = "INVALID-OBJECTS";
    
            public void postClone(DescriptorEvent event) {
                Session sharedSession = 
                    event.getSession().getRootSession(event.getQuery());
                if (!sharedSession.getIdentityMapAccessor().isValid(event.getOriginalObject())) {
                    addInvalidObject((UnitOfWork)event.getSession(), 
                                     event.getSource());
                }
            }
    
            /**
             * Add to a list of invalid objects cached in the UnitOfWork's properties.
             * 
             * This method assumes that only one thread is registering objects with
             * the UnitOfWork and therefore no concurrency protection is required.
             */
            private void addInvalidObject(UnitOfWork uow, Object object) {
                getInvalid(uow).add(object);
            }
    
            /**
             * Retrieve the list of invalid objects found during registration. If 
             * the set doe snot exist create and register it with the UnitOfWork
             */
            public static Set getInvalid(UnitOfWork uow) {
                Set invalidObjects = (Set)uow.getProperty(INVALID_OBJECTS);
    
                if (invalidObjects == null) {
                    invalidObjects = new HashSet();
                    uow.setProperty(INVALID_OBJECTS, invalidObjects);
                }
    
                return invalidObjects;
            }
            
            public static void refreshInvalid(UnitOfWork uow) {
                for (Object invalidObject: CollectInvalidHandler.getInvalid(uow)) {
                    uow.refreshObject(invalidObject);
                }
            }
        }
    A simple test case that uses this looks like:
            Employee emp = 
                BasicEmployeeHelper.findEmployeeWithAddressAndPhone(this);
    
            session.getIdentityMapAccessor().invalidateObject(emp.getAddress());
    
            assertFalse(session.getIdentityMapAccessor().isValid(emp.getAddress()));
    
            UnitOfWork uow = session.acquireUnitOfWork();
            Employee empWC = (Employee)uow.registerObject(emp);
            empWC.getAddress();
            
            assertEquals(1, CollectInvalidHandler.getInvalid(uow).size());
            CollectInvalidHandler.refreshInvalid(uow);
            ...
    This should at least let you detect and handle the situation in your application. We continue to work on a more complete and transparent solution but need to evaluate the various scenarios and come up with an easy to use configuration for this new capability.

    Doug
  • 14. Re: 10.1.3 cache invalidation capabilities does not work
    504035 Explorer
    Currently Being Moderated
    Thanks for your workaround.

    My understanding of the suggestion is that after TopLink migrate an invalid object from ClientSession to UnitOfWork, which involve doing two copies [clone&backup], we call a TopLink API against UnitOfWork object which will update the ClientSession object and also the UnitOfWork’s clone&backup.

    The workaround is significantly slower than doing the validity check before the migration but that may end-up to be faster than our current workaround, which is to customize CacheInvalidationPolicy to tell TopLink that the object is invalid when one of its children is invalid.

    We would have to intercept all the calls we make against TopLink APIs to do the refresh.

    The workaround may require special handling for invalid objects in ClientSession that end-up to have been deleted.
1 2 Previous Next