14 Replies Latest reply on Sep 28, 2009 4:58 AM by adamw

    Invalidation-only cache

    adamw

      Hello,

      here's my scenario: I want to keep some objects in cache, which are calculated basing on some persistent state (DB lets say). Rarely, the persistent state changes, and the objects have to be re-calculated (lazily). So, on a change, I want to invalidate all items. Additionally, this happens in a cluster, so the invalidation must be cluster-wide, although the change notification is received only in one node. The calculated objects aren't serializable so each node must calculate them independently.

      I was trying to solve this with a cache in the INVALIDATION_SYNC mode and a cache loader. The cache loader is responsible for calculating the objects (I also get the laziness here) in each node. When a change happens, I simple call remove(), which invalidates entries in all nodes (because of the mode). When a get() is later called, the objects are read using the cache loader.

      Now this works fine as along as I don't use transactions. When I start to use transactions, the get() call after a remove() call (in the same TX) doesn't call the cache loader, but returns null. This is quite logical and ok in the normal cases, but not in mine. Do you have any other suggestions on how to achieve the above functionality with transactions?

      Thanks,
      Adam

        • 1. Re: Invalidation-only cache
          mircea.markus

          can't you do the get call outside a transaction? You can use TransactionManager.suspend/resume if you need to do some more work in tx after refreshing the cache.

          • 2. Re: Invalidation-only cache
            adamw

            Hello,

            but if I call get() in the TX that did the changes, I want to get a re-calculated value basing on the changed persistent store. Other threads should get of course the old value.

            Adam

            • 3. Re: Invalidation-only cache
              mircea.markus

              even within a TX, I would expect that if you ask for a node that is not in the memory the cache store still to be queried and data fetched from there. Just for me to make sure I get it right, you have something like:

              tx.start()
              cache.remove(fqn); //this will clear data from both memory AND cache store
              cache.get(fqn);// this should look into both memory AND cache store
              tx.commit();

              So, IMO, disregarding weather you do the second call within a tx or not, it should still query the cache store. Actually I'm a bit confused that when the sequence runs outside of a tx the cache store returns a non null value, give the fact that you specifically performed a remove on the same node.

              • 4. Re: Invalidation-only cache
                adamw

                Hello,

                Well, if I call cache.remove(fqn) in a tx, then any subsequent calls return null, without hitting the cache loader. Only after the tx is commited, the cache loader is queried. (In the no-tx scenario it behaves as if the tx was commited immediately after the operation.) That's what my experiments show ;)

                This behaviour (I think) is consistent with this bug:
                https://jira.jboss.org/jira/browse/JBCACHE-352

                Adam

                • 5. Re: Invalidation-only cache
                  mircea.markus

                  ah I see. Out of ideas then :(

                  • 6. Re: Invalidation-only cache
                    adamw

                    Heh, I didn't expect that my use case will be so uncommon. It seems a relatively simple idea to store some computed values and invalidate them when the underlying data changes. But well ... :)

                    Adam

                    • 7. Re: Invalidation-only cache
                      mircea.markus

                       

                      Heh, I didn't expect that my use case will be so uncommon. It seems a relatively simple idea to store some computed values and invalidate them when the underlying data changes. But well ... :)

                      Well apparently this is the first time one asks for this functionality :)
                      One thing you can try: what about replace the cache.remove call, with a cache.put(newValue), and fetch the new value outside the transaction.


                      • 8. Re: Invalidation-only cache
                        adamw

                        Hello,

                        yes, I'm doing that now for a limited number of keys (eagerly putting them in the cache instead of removing the whole node), although I'm doing it inside the TX (I want the new values to be calculated basing on the data modified in the TX), however I can't do that for all keys, as that would take too much time. The whole point of using a cache loader is to be able to compute those values lazily.

                        Adam

                        • 9. Re: Invalidation-only cache
                          manik

                          I think you are using the cache loader in an incorrect way. CLs are designed to be a persistent (and overflow) extension to memory and should never be sued to store a different form of data. And should also never change externally outside the cache system.

                          If you want to cache stuff from a data source which contains a different form of data, or that can be updated externally, I recommend looking at how Hibernate implements a 2nd level cache as this pattern is similar. Basically your code would look up the cache. If it is not in the cache, look in the data store, retrieve and cache before returning. And periodically (or based on some other trigger) invalidate what's in the cache.

                          • 10. Re: Invalidation-only cache
                            adamw

                            Hello,
                            well, from what I've seen in CacheHelper Hibernate is simply calling get() and put(). However in my case this doesn't work, as I run the cache in INVALIDATION_SYNC mode. This means that when I call put() on node1, an invalidation message is sent to other nodes. Then, when node 2 wants to retrieve the object, it sees that there's no value, so it calculates the value and calls put(), which invalidates the node on node1, and so on. In fact, what I would need are three operations: invalidation of a node in a cluster, local-get and local-put (without invalidating).

                            Using cacheloader (well, I admin, hacking around it ;) ) allowed me to get a local-put. Maybe I'll see what the CL are doing behind the scenes :)

                            Adam

                            • 11. Re: Invalidation-only cache
                              manik

                              Have a look at Hibernate, again. :-) They too use INVALIDATION, and there is a way to deal with this - it's called putForExternalRead().

                              As for the CL approach, that will not work, this scenario is not what the CL is designed for.

                              • 12. Re: Invalidation-only cache
                                adamw

                                Ah yes, didn't notice it, thanks a lot! :)

                                Adam

                                • 13. Re: Invalidation-only cache
                                  adamw

                                  One more question though - do I understand right from the javadoc that putForExternalRead is non-transactional? Meaning that if a change something in a TX in the persistent store, and then call putForExternalRead with the changes, other transactions will see the change, making it a dirty read?

                                  Although I'll probably won't be able to use it anyway, as I'm not able to deploy cache > 2 on AS 4.2 ;) (Hibernate stops finding the datasource in jndi ...)

                                  Adam

                                  • 14. Re: Invalidation-only cache
                                    adamw

                                    Another idea, using JBC 1.4.1; for put operations, which shouldn't cause invalidation, maybe I could use an Option with cache mode == LOCAL. For the remove operation, I could use INVALIDATION_SYNC, which should propagate. Do you see any obstacles here? :)

                                    Adam