[hibernate-dev] Re: Hibernate Search: massive batch indexing
Emmanuel Bernard
emmanuel at hibernate.org
Mon Jun 9 14:04:52 EDT 2008
On Jun 7, 2008, at 20:14, Sanne Grinovero wrote:
> thanks for your insights :-)
> I'll try explain myself better inline:
>
> 2008/6/7 Emmanuel Bernard <emmanuel at hibernate.org>:
> This sounds very promising.
> I don't quite understand why you talk about loading lazy objects
> though?
> On of the recommendations is to load the object and all it's related
> objects before indexing. No lazy triggering should happen.
> eg "from User u left join fetch u.address a left join fetch a.country"
> if Address and Country are embedded in the User index.
>
> I am talking about the lazy object loading because it is not always
> possible to
> load the complete object graph eagerly because of the cartesian
> problem;
> the "hints" I mention in point A is mainly (but not limited to) the
> left join
> fetch instruction needed to load the root entity.
> However if I put all needed collections in the fetch join I kill the
> DB
> performance and am flooded by data; I have made many experiments to
> find the "gold balance" between eager and lazy and know for sure it
> is much
> faster keeping most stuff out of the initial "fetch join"
> My current "rule of thumb" is to load no more than two additional
> collections,
> the rest goes lazy.
> Also we should keep in mind the eager/lazy/subselect strategies
> going to be chosen for the entities will probably be selected for
> "normal" business operations finetuning and not for indexing
> performance;
> I had to fight somehow with other devs needing some setting for
> other usecases in a different way than what I needed to bring indexing
> timings down.
I understand. You could use Hibernate.initialize and batch-size
upfront to help in this area *before* passing it to Hibernate Search.
>
>
>
> I think the E limitation is fine, we can position this API as
> offline indexing of the data. That's fair enough for a start. I
> don't like your block approach unless you couple it with JMS. I am
> uncomfortable in keeping work to be done for a few hours in a VM
> without persistent mechanism.
>
> I am glad to hear it's fine to position it as "offline" API, as a
> start.
> Do you think we should enforce or check it somehow?
Let's add a modal box "Are you sure?" ;)
I don't think you can really enforce that (especially on a cluster).
>
> For later improvements the batching IndexWriter could be "borrowed" by
> the committing transactions to synchronously write their data away,
> we just need to avoid the need of and IndexReader for deletions;
> I've been searching for a solution in my other post... if that could
> be fixed
> and a single IndexWriter per index could be available you could
> have batch indexing and normal operation available together.
I will answer on the second post.
>
>
> "this pool is usually the slowest as it has to initialize many lazy
> fields,
> so there are more threads here."
> I don't quite understand why this happens.
>
> I suppose I should show you an ER diagram of our model; in our case
> but I believe
> in most cases people will search for an object basing his "fulltext"
> idea on many different
> fields which are external to the main entity: intersecting e.g.
> author nickname with historic period,
> considering book series, categories and collections, or by a special
> code in one of
> 30 other legacy library encoding schemes.
> The use case actually shows that very few fields are read from the
> root entity, but most
> are derived from linked many-to-many entities, sometimes going to a
> second or third level
> of linked information. I don't think this is just my case, IMHO it
> is very likely most
> real world applications will have a similar problem, we have to
> encode in the root
> object many helper fields to make most external links searchable; I
> believe this is part of the
> "dealing with the mismatch between the index structure and the
> domain model"
> which is Search's slogan (pasted from homepage).
>
> So what is the impact of your code on the current code base? Do you
> need to change a lot of things? How fast do you think you could have
> a beta in the codebase?
>
> I still have not completely understood the locks around the indexes;
> I believe the impact on current code is not so huge, I should need
> to know
> how I should "freeze" other activity on the indexes: Indexing could
> just start but other threads will be waiting a long time; should other
> methods check and throw an exception when mass indexing is busy?
Let's not envision an exception for the moment.
The locks must be acquired in a specific order, aside from that, this
should be straightforward
>
> Is it ok for one method to spawn 40 threads?
It's OK if it's there is only one call per VM doing that. If every
client does that, then that's not good :)
>
> How should the "management / progress monitor API" look like?
Maybe like the Hibernate Statistics. It depends on what the API should
do
>
> I didn't look at similarity and sharding, is it ok for a first beta
> to avoid this features? I don't think it should be difficult to
> figure out, but would like
> to show working code prototypes asap to have early feedback.
no problem
>
> I think that if the answers to above questions don't complicate my
> current code the effort to integrate it is less than a week of work;
> unfortunately this translates
> in 4-6 weeks of time as I have other jobs and deadlines, maybe less
> with some luck.
> How should this be managed? a branch? one commit when done?
If you don't disrupt the rest of the features, then you cand apply
them in trunk, if you are afraid, then do a branch. But branches are
pain to merge back in SVN.
>
>
>
> Let's spin a different thread for the "in transaction" pool, I am
> not entirely convinced it actually will speed up things.
> Yes I agree there probably is not a huge advantage, if any; the main
> reason would be to have "normal operation" available
> even during mass reindexing, performance improvements would be limited
> to special cases such as a single thread committing several
> entities: the "several" would benefit from batch behavior.
> The other thread I had already started is linked to this: IMHO we
> should improve the deletion of entities first.
>
> On Jun 6, 2008, at 18:51, Sanne Grinovero wrote:
>
> Hello list,
>
> I've finally finished some performance test about stuff I wanted to
> double-check
> before writing stupid ideas to this list, so I feel I can at last
> propose
> some code to (re)building the index for Hibernate Search.
>
> The present API of Hibernate Search provides a nice and safe
> transactional "index(entity)",
> but even when trying several optimizations it doesn't reach the speed
> of an unsafe (out of transaction) indexer we use in our current
> production environment.
> Also reading the forum it appears that much people are having
> difficulties in using
> the current API, even with a good example in the reference
> documentation
> some difficulties arise with Seam's transactions and with huge data
> sets.
> (I'm NOT saying something is broken, just that you need a lot of
> expertise
> to get it going)
>
> SCENARIO
> =======
>
> * Developers change an entity and want to test the effect on the index
> structure,
> thay want do to search experiments with the new fields.
> * A production system is up(down)graded to a new(old) release,
> involving index changes.
> (the system is "down for maintance" but the speed is crucial)
> * Existing index is corrupted/lost. (Again, speed to recover is
> critical)
> * A Database backup is restored, or data is changed by other jobs.
> * Some crazy developer like me prefers to disable H.Search's event
> listeners for some reason.
> (I wouldn't generally recommend it, but have met other people who
> have a reasonable
> argument to do this. Also in our case it is a feature as new entered
> books will be
> available for loans only from the next day :D)
> * A Lucene update breaks the index format (not so irrationale as they
> just did on trunk).
>
> PERFORMANCE
> =======
>
> In simple use cases, such as less than 1000 entities and not too much
> relationships,
> the exising API outperforms my prototype, as I have some costly setup.
> In more massive tests the setup costs are easily recovered by a much
> faster indexing speed;
> I have many data I could send, I'll just show some and keep the
> details simple:
>
> entity "Operator": standard complexity, involves loading of +4 objs, 7
> field affect index
> entity "User": moderate complexity, involves loading of +- 20 objs, 12
> affect index data
> entity "Modern": high complexity, loading of 44 entities, many are
> "manyToMany", 25 affect index data
>
> On my laptop (dual core, local MySQL db):
> type Operator User Modern
> number 560 100.000 100.000
> time-current 0,23 secs 45'' 270.3''
> time-new 0,43 secs 30'' 190''
>
> On a staging server (4 core Xeon with lots of ram and dedicated DB
> server):
> type Operator User Modern
> number 560 200.000 4.000.000
> time-current 0,09 secs 130'' 5h20'
> time-new 0,25 secs 22'' 19'
>
> [benchmark disclaimer:
> These timings are meant to be relative to each other for my particular
> code version, I'm not an expert of Java benchmarking at all.
> Also unfortunately I can't really access the same hardware for each
> tests.
> I used all possible tweaks I am aware of in Hibernate Search, actually
> enabling new needed params to make the test as fair as possible.]
>
> Examining the numbers:
> with current recommended H.Search strategy I can index 560 simple
> entities
> in 0,23 seconds; quite fast and newbe users will be impressed.
> At the other extreme, we index 4 million complex items, but I need
> more
> than 5 hours to do that; this is more like real use case and it could
> scare several developers.
> Unfortunately I don't have a complete copy of the DB on my laptop,
> but looking at the numbers it looks like my laptop could finish
> in 3 hours, nearly double the speed of our more-than-twice fast
> server.
> (yes I've had several memory leaks :-) but they're solved now)
> The real advantage is the round-trip to database: without multiple
> threading each lazy loaded collection somehow annotated to be indexed
> massively slows down the whole process; If you look at both DB an AS
> servers, they have very low resource usage confirming this, while my
> laptop
> stays at 70% cpu (and killing my harddrive) because he has data
> available
> locally, producing a constant feed of strings to my index.
> When using the new prototype (about 20 threads in 4 different pools)
> I get the 5hours down to less than 20minutes; Also I can start the
> indexing of all 7 indexable types in parallel and it will stay
> around 20minutes.
> The "User" entity is not as complex as Modern (less lazy loaded data)
> but confirms the same numbers.
>
> ISSUES
> =======
> About the current version I've ready.
> It is not a complete substitute of the current one and is far from
> perfect;
> currently these limitations apply but could be easily solved:
> (others I am not aware of not listed :-)
>
> A) I need to "read" some hints for each entity; I tinkered with a new
> annotation,
> configuration properties should work but are likely to be quite
> verbose (HQL);
> basically I need some hints about fetch strategies appopriate
> for batch indexing, which are often different than normal use cases.
>
> B) Hibernate Search's indexing of related entities was not available
> when I designed it.
> I think this change will probably not affect my code, but I still
> need to
> verify the functionality of IndexEmbedded.
>
> C) It is finetuned for our entities and DB, many variables are
> configurable but
> some stuff should be made more flexible.
>
> D) Also index sharding didn't exist at the time, I'll need to change
> some stuff
> to send the entities to the correct index and acquire the
> appropriate locks.
>
> The next limitations is not easy to solve, I have some ideas but no
> one I liked.
>
> E) It is not completely safe to use it during other data modification;
> It's not a problem in our
> current production but needs much warning in case other people
> wants to use it.
> The best solution I could think of is to lock the current workqueue
> of H.Search,
> so to block execution of work objects in the queue and resume the
> execution of
> this work objects after batch indexing is complete.
> If some entity disappears (removed from DB but a reference is in
> the queue) it
> can easily be skipped, if I index "old version" of some other data
> it will be
> fixed when scheduled updates from H.S. eventlisteners are resumed;
> (and the same for new entities).
> It would be nice to share the same database transaction during the
> whole process,
> but as I use several threads and many separate sessions I think
> this is not possible
> (this is the best place to ask I think;-)
>
> GOING PRACTICAL
> ===============
> if (cheater) goto :top
>
> A nice evictAll(class) exists, I would like to add indexAll(class).
> It would be nice to provide non-blocking versions, maybe overloading:
> indexAll(Class clazz, boolean block)
> or provide a Future as return object, so people could wait for one
> or more indexAll requests if they want to.
> There are many parameters to tweak the indexing process, so I'm
> not sure if we should put them in the properties, or have a
> parameters-
> wrapper object indexAll(Class class, Properties prop), or
> something like makeIndexer(Class class) returning a complex object
> with several setters for finetuning and start() and awaitTermination()
> methods.
>
> the easy part
> --------------
> This part is easy to do as I have it working well, it is a pattern
> involving several executors; the size of each threadPool and of the
> linking queues between them gives the good balance to achieve the
> high throughput.
> First the entities are counted and divided in blocks, these ranges
> are fed to
> N scrollables opened in N threads, each thread begins iterating on the
> list of entities and feeds detached entities to the next Pool using
> BlockingQueues.
> In the next pool the entities are re-attached using Lock.none,
> readonly, etc..
> (and many others you may want to tell me) and we get and appropriate
> DocumentBuilder from the SearchFactory to transform it into a Lucene
> Document;
> this pool is usually the slowest as it has to initialize many lazy
> fields,
> so there are more threads here.
> Produced documents go to a smaller pool (best I found was for 2-3
> threads)
> were data is concurrently written to the IndexWriter.
> There's an additional thread for resource monitoring to produce some
> hints
> about queue sizing and idle threads, to do some finetune and to see
> instant
> speed reports in logs when enabled.
> For shutdown I use the "poison pill" pattern, and I usually get rid
> of all
> threads and executors when I'm finished.
> It needs some adaption to take into account of latest Search features
> such as similarity, but is mostly beta-ready.
>
> the difficult part
> -------------------
> Integrating it with the current locking scheme is not really
> difficult,
> also because the goal is to minimize downtime so I think some downtime
> should be acceptable.
> It would be very nice however integrate this pattern as the default
> writer for indexes, even "in transaction"; I think it could be
> possible
> even in synchronous mode to split the work of a single transaction
> across
> the executors and wait for all the work be done at commit.
> You probably don't want to see the "lots of threads" meant for batch
> indexing,
> but the pools scale quite well to adapt themselves to the load,
> and it's easy (as in clean and maintainable code) to enforce
> resource limits.
> When integrating at this level the system wouldn't need to stop
> regular
> Search activity.
>
> any questions? If someone wanted to reproduce my benchmarks I'll
> be glad to send my current code and DB.
>
> kind regards,
> Sanne
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/hibernate-dev/attachments/20080609/2ced728f/attachment.html
More information about the hibernate-dev
mailing list