Chapter 6. Limitations

6.1. Incomplete Implementation of Hibernate API

In order to speed-up the initial release of Hibernate Shards, some parts of the Hibernate API that we rarely use were left unimplemented. Of course things that we rarely used are probably critical for some applications, so if we've left you out in the cold we apologize. We're committed to getting the rest of the API implemented quickly. For details on which methods were not implemented, please see the Javadoc for ShardedSessionImpl, ShardedCriteriaImpl, and ShardedQueryImpl.

6.2. Cross-Shard Object Graphs

Hibernate Shards does not currently support cross-shard object graphs.

In other words, it is illegal to create an association between objects A and B when A and B live on different shards. The workaround is to define a property on A which uniquely identifies an object of type B, and to use that property to load object B (remember what life was like before Hibernate? Yeah, just like that.)

For example:

                --need domain for examples--
            

In some applications your model may be constructed in such a way that it is difficult to make this kind of mistake, but in some applications it may be easier. The scary thing here is that if you make this mistake, Hibernate will consider the "bad" object in the list to be a new object and, assuming you have cascades enabled for this relationship, it will create a new version of this object on a different shard. This is trouble. In order to help prevent this sort of thing from happening we have an interceptor called CrossShardRelationshipDetectingInterceptor that checks for cross-shard relationships on every object that is created or saved.

Unfortunately there is a cost associated with using the CrossShardRelationshipDetectingInterceptor. In order to determine the shard on which an associated object resides we need to fetch the object from the database, so if you have lazy-loaded associations the interceptor will resolve those associations as part of its checks. This is potentially quite expensive, and may not be suitable for a production system. With this in mind, we've made it easy to configure whether or not this check is performed via the "hibernate.shard.enable_cross_shard_relationship_checks" property we referenced in the chapter on configuration. If this property is set to "true" a CrossShardRelationshipDetectingInterceptor will be registered with every ShardedSession that is established. Don't worry, you can still register your own interceptor as well. Our expectation is that most applications will have this check enabled in their dev and qa environments and disabled in their staging, load and performance, and production environments.

6.3. Distributed Transactions

Hibernate Shards does not provide support for distributed transactions within a non-managed environment. If your application requires distributed transactions you need to plug in a transaction management implementation that supports distributed transactions.

6.4. Stateful Interceptors

We've done our best to make sure that, by and large, Hibernate Core code runs just fine when using Hibernate Shards. There are, unfortunately, exceptions, and one of those exceptions is when your application needs to use an org.hibernate.Interceptor that maintains state.

Stateful interceptors need special handling because, under the hood, we're instantiating one org.hibernate.SessionImpl per shard. If we want an Interceptor associated with the Session, we need to pass in whatever Interceptor was provided when the ShardedSession was created. If that Interceptor is stateful, the Interceptor state for one Session will be visible in all Sessions. When you consider the sorts of things that are typically done in stateful Interceptors (auditing for example), you can see how this can pose a problem.

Our solution is to require users to provide a StatefulInterceptorFactory when they establish their Session objects (which are really ShardedSessions). If the provided Interceptor implements this interface, Hibernate Shards will ensure that a fresh instance of the type of Interceptor returned by StatefulInterceptorFactory.newInstance() will be passed to each Session that is established under the hood. Here's an example:

public class MyStatefulInterceptorFactory extends BaseStatefulInterceptorFactory {
    public Interceptor newInstance() {
        return new MyInterceptor();
    }
}

Many Interceptor implementations require a reference to the Session with which they're associated. In the case of a stateful Interceptor, you want your Interceptor to have a reference to the real (shard-specific) Session, not the shard-aware Session. In order to facilitate this, you have the choice of having the type of Interceptor that is constructed by the StatefulInterceptorFactory implement the RequiresSession interface. If the Interceptor constructed by the StatefulInterceptorFactory implements this interface, Hibernate Shards will provide the Interceptor with a reference to the real (shard-specific) Session once the factory constructs it. This way your Interceptor can safely and accurately interact with a specific shard. Here's an example:

public class MyStatefulInterceptor implements Interceptor, RequiresSession {
    private Session session;

    public void setSession(Session session) {
        this.session = session;
    }

    ... // Interceptor interface impl
}

Due to the basic nature of the problem we don't expect this to change anytime soon.

6.5. Objects With Ids That Are Base Types

With Hibernate your model objects can use whatever they want as their ids so long as the id can be represented by a Serializable (or autoboxed into a Serializable). With Hibernate Shards you are slightly more constrained because we don't support base types.

So this is no good:

public class WeatherReport {
    private int weatherReportId;  // trouble

    public int getWeatherReportId() {
        return weatherReportId;
    }

    public void setWeatherReportId(int id) {
        weatherReportId = id;
    }
}

But this is just lovely:

public class WeatherReport {
    private Integer weatherReportId;  // goodness

    public Integer getWeatherReportId() {
        return weatherReportId;
    }

    public void setWeatherReportId(Integer id) {
        weatherReportId = id;
    }
}

Do we have a good reason for this limitation? Not really. It's the result of an implementation choice that has leaked out and made everyone's lives a tiny bit worse. If you simply must use Hibernate Shards and you simply must model your ids with base types, don't call Session.saveOrUpdate. We aim to address this leak soon and let you get back to modeling whatever way you like (although for the record, we prefer object ids because they make it easy to determine whether or not an object has had an id assigned).

6.6. Replicated Data

Even though this is a framework for horizontal partitioning, there is almost always read-only (or at least slow changing) data that lives on every shard. If you're just reading these entities we don't have a problem, but if you want to associate these entities with sharded entities we run into trouble. Suppose you have a Country table on every shard with the exact same data, and suppose WeatherReport has a Country member. How do we guarantee that the Country you associate with that WeatherReport is associated with the same shard as the WeatherReport? If we get it wrong we'll end up with a cross-shard relationship, and that's bad.

We have a number of ideas about how to make this easy to deal with but we have not yet implemented any of them. In the short term, we think your best bet is to either not create object relationships between sharded entities and replicated entities. In other words, just model the relationship like you would if you weren't using an OR Mapping tool. We know this is clunky and annoying. We'll take care of it soon.