20
Dec
09

Hibernate SequenceGenerator with allocationSize=1 leads to huge contention

This post is a kind of warn for Hibernate users.

I sincerely don’t know which was the reason but accidentally someone annotated every entity on a software with:

@SequenceGenerator(allocationSize=1)

Doing a brainstorm to find the reason we can speculate:

  1. Minimise sequence gaps due to server reboots (unlikely)
  2. Misunderstanding of what the parameter really does. Probably the developer thought it had to do with the increment by parameter of the underlying database sequence (more likely)

Whichever the reason the truth is that whenever you annotate an entity with @SequenceGenerator Hibernate (at least I know up to 3.3.1.GA) delegates sequence generation to org.hibernate.id.SequenceHiLoGenerator which in turn generate method is as follows:

public synchronized Serializable generate(SessionImplementor session, Object obj)
	throws HibernateException {
		if (maxLo < 1) {
			//keep the behavior consistent even for boundary usages
			long val = ( (Number) super.generate(session, obj) ).longValue();
			if (val == 0) val = ( (Number) super.generate(session, obj) ).longValue();
			return IdentifierGeneratorFactory.createNumber( val, returnClass );
		}
		if ( lo>maxLo ) {
			long hival = ( (Number) super.generate(session, obj) ).longValue();
			lo = (hival == 0) ? 1 : 0;
			hi = hival * ( maxLo+1 );
			if ( log.isDebugEnabled() )
				log.debug("new hi value: " + hival);
		}

		return IdentifierGeneratorFactory.createNumber( hi + lo++, returnClass );
	}

If you compare the generate sequence above with the one from org.hibernate.id.SequenceGenerator below you’ll notice that this one in turn is not synchronized:

public Serializable generate(SessionImplementor session, Object obj)
	throws HibernateException {

		try {

			PreparedStatement st = session.getBatcher().prepareSelectStatement(sql);
			try {
				ResultSet rs = st.executeQuery();
				try {
					rs.next();
					Serializable result = IdentifierGeneratorFactory.get(
							rs, identifierType
						);
					if ( log.isDebugEnabled() ) {
						log.debug("Sequence identifier generated: " + result);
					}
					return result;
				}
				finally {
					rs.close();
				}
			}
			finally {
				session.getBatcher().closeStatement(st);
			}

		}
		catch (SQLException sqle) {
			throw JDBCExceptionHelper.convert(
					session.getFactory().getSQLExceptionConverter(),
					sqle,
					"could not get next sequence value",
					sql
				);
		}

	}

The synchronized keyword on the previous one isn’t any surprise since SequenceHiLoGenerator does some of its sequence generation in memory and it may be accessed concurrently by multiple threads.

But the fact is that SequenceHiLoGenerator wasn’t designed to have an allocationSize=1 (strangely enough it has a separate flow for this situation).

Reproducing this scenario seems to be very easy, sincerely I have not tried to do a syntetic reproduction but I guess that spawning a few threads with a simple entity configured with allocationSize=1 and having all the threads call a Session.persist might do the trick. All you will need to do is have a thread dump after a few minutes (3 or 5) of execution and import it into a dump analyser (IBM Support Assistant if you are running on an IBM JVM) and you’ll notice that a great deal of threads will be waiting on a lock (before entering the generate method on AbstractSaveEventListener.saveWithGeneratedId).

But what would be possible alternatives for this?

If you want to have the entities IDs reflecting the value of the database sequence use the following annotation:


@GenericGenerator(strategy="sequence")

Another possibility is removing the allocationSize (which leads to the default value of 50) or configuring it with a greater value. But, in this case you’ll need to update the database sequence so that you don’t end up with a gap on your IDs since SequenceHiLoGenerator employs the following formula for sequence generation:

DBSequence*allocationSize<= IDs < (DBSequence+1)*allocationSize

And upon startup it already increments sequence by one acquiring a new slot of IDs.

So you’ll have to restart your sequence with the following value:


select round(max(id)/allocationSize) from table;

Advertisements

6 Responses to “Hibernate SequenceGenerator with allocationSize=1 leads to huge contention”


  1. 1 Gustavo
    July 13, 2010 at 12:02 am

    Thanks a lot for this tip. My ids grow up by 50, and I didn’t know why. Now I know, and I Fix it.

    • July 13, 2010 at 11:04 am

      Hi Gustavo,

      But how did you fix it? If you use the HiLo implementation you’ll fall on the performance problem I described on the post. Please take care to use the regular Sequence implementation (that happens not to be the default one and so needs to be specified using Hibernate proprietary annotations not the JPA one).

      regards,
      Rafael Ribeiro

  2. 3 Luiz Rolim
    March 3, 2011 at 3:40 pm

    Hi rafael, thanks for sharing this.

    I know its an old post, but i hope u can help me:

    I didn´t understand your point about the allocationSize stuff.

    Debugging here it goes to the
    public synchronized Serializable generate(SessionImplementor session, Object obj) method of SequenceHiLoGenerator anyways, whether allocationSize parameter is set or not.

    So Is there any real diff?

    PS: I want to set allocationSize=1 so that hibernate doesn´t grow my id up by 50 as Gustavo noticed.

    Tried quickly yours @GenericGenerator tip, but it didn´t use the sequence as expected.

  3. 5 MadPuff
    November 13, 2012 at 1:32 am

    It works with this @org.hibernate.annotations.GenericGenerator annotation, you should have both:

    @javax.persistence.SequenceGenerator(name = “Question_id_sequence”, sequenceName = “S_QUESTION”)

    @org.hibernate.annotations.GenericGenerator(name=”Question_id_sequence”, strategy = “sequence”, parameters = { @Parameter(name=”sequence”, value=”S_QUESTION”) } )

    as it is said at:

    http://stackoverflow.com/questions/5346147/hibernate-oracle-sequence-produces-large-gap/5346701#5346701

    • November 13, 2012 at 1:53 pm

      but the gap should not be a problem at all for the majority of software out there… and it increases performance as it reduces contention (insertion does not need to wait till database responds)…


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


ClustrMaps

Blog Stats

  • 353,192 hits since aug'08

%d bloggers like this: