So, in the continuation of the
previous article I will try to talk about real-world situations about the problems that arose while working in real projects.
Migration scripts
Perhaps one of the most frequent problems when working with a cache in my application is the need to roll in migration scripts on a working server. After all, if these scripts are not run through the session server of a working server, then the cache of this factory will not know about the changes that are made to the database. Therefore, we get the problem of data incompatibility. There are several ways to solve this problem:
- Server restart is the easiest and usually the most unacceptable way;
- Clearing the cache through certain mechanisms is perhaps the most optimal method for simplicity and reliability. This method can be put up, for example in JMX, on a web page or another interface and called when necessary. The flexibility of the method is that it is written once, and is used as much as you like, anywhere. In case if your cache provider is EHCache and the provider class is SingletonEhCacheProvider, then your code may look like this:
public String dumpKeys() { String regions[] = CacheManager.getInstance().getCacheNames(); StringBuilder allkeys = new StringBuilder(); String newLine = System.getProperty("line.separator"); for (String region : regions) { Ehcache cache = CacheManager.getInstance().getEhcache(region); allkeys.append(toSomeReadableString(cache.getKeys())); allkeys.append(newLine); } return allkeys.toString(); }
Naturally, this code must be executed in the same process as the Hibernate, whose statistics you want to track. Read more here . The same can be achieved using the session factory.
- Run migration scripts using the session server of the working server. This is similar to the second method, with the only difference that we do not clear the cache, but skip all the migration scripts through the existing factory. Thus, all necessary caches are updated themselves. This method is rational to use if the cache is large and cheaper to update than to create a new one;
Query cache
Query cache is probably the most inefficient of all listed in the previous article. There are several reasons for this:
- First of all (what I forgot to mention in the first article), the key to the data of this cache is not only the request parameters, but also the request itself. This is especially important when there are many requests and they are large.
- Query cache is often reset. That is, if at least one of the tables that participate in the query has been modified, then the cache will be flushed and the query is executed using a new one.
Therefore, it should be used very carefully. And remember - there is no sense to cache everything. Cache only those requests that can really speed up your application and those requests for which the cache will be very rarely reset.
A typical example of a bad place for a query cache is a sample of the amount of something on a resource with a high rate of updates / additions of entities. Let's say you need to display the statistics of the entities created in the application by their statuses and some other parameters, like this:
Criteria criteria = getSession().createCriteria(Plan.class); criteria.setProjection(Projections.projectionList() .add(Projections.groupProperty("status")) .add(Projections.rowCount()) ); criteria.setCacheable(true);
Each time a new plan is inserted into the table or an existing one is changed, the cache will be reset. Not only is the cache constantly reset, but there are also fixed costs for monitoring the status of the tables and maintaining the cache itself. This can significantly hit performance. Actually, it once happened to my application.
Deleting cached objects
I can not remember all the specific circumstances of the problems that happened when working in the cache. But one of them is particularly well remember - this is the removal of objects that are in the cached collection. It is known that objects and their dependencies are cached separately. Therefore, if we have the following class:
@Entity @Table(name = "shared_doc") @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) public class SharedDoc{ @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) private Set<User> users; }
And one of the users has been deleted, then we execute its removal:
getSession().delete(user)
In this case, the deleted entry remains in the cache of the users collection. We receive not consistent data. The same is true for all sorts of cascade deletions. When objects deleted in a cascade are left in the cache. One of the obvious solutions is to delete objects also from the collection cache in which it can be located. That is, in this example, the removal should look like this:
SharedDoc doc = (SharedDoc) session.load(SharedDoc.class, 1L); doc.getUsers().remove(user); session.delete(user);
This works great when the user is only in one collection cache - users. But the task is very complicated when there are many such collections and they can be scattered in different entities. This kind of problem can also lead to an ObjectNotFoundException when attempting something with objects that are left in the cache.
')
Competitive transactions
Sometimes the cache may not behave as you would expect in the case of competitive transactions. Consider the typical case:
Session session1 = getSession(); Session session2 = getSession()); Transaction t = session1.beginTransaction(); Plan plan = (Plan) session1.load(Plan.class, 1L); System.out.println (plan.getName()); plan.setName(newName); t.commit(); t = session2.beginTransaction(); plan = (Plan) session2.load(Plan.class, 1L); System.out.println (plan.getName()); tx2.commit(); session1.close(); session2.close();
It would seem that in the second call to the plan it would have to be obtained from the second level cache - but this is not so. Because the session2 object was created before changing the plan object. And while referring to the second-level cache, Hibernate checks the session creation time and the time of the last object change. This behavior should be taken into account in your applications, as it can create additional load in places where you do not expect it.
Here, perhaps, all of what I was able to recall from the problems when working with Hibernate Cache. Unfortunately, I did not work with the distributed cache and I have nothing to say on this topic. If you have encountered other problems not described in the article, comment, I will add.