Skip to content

Commit

Permalink
HSEARCH-3827 Copy a generic base for backend performance tests from S…
Browse files Browse the repository at this point in the history
…earch 5
  • Loading branch information
yrodiere authored and fax4ever committed Feb 17, 2020
1 parent 155123f commit a218356
Show file tree
Hide file tree
Showing 23 changed files with 1,268 additions and 3 deletions.
41 changes: 41 additions & 0 deletions integrationtest/performance/backend/base/pom.xml
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Hibernate Search, full-text search for your domain model
~
~ License: GNU Lesser General Public License (LGPL), version 2.1 or later
~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-integrationtest-performance</artifactId>
<version>6.0.0-SNAPSHOT</version>
<relativePath>../..</relativePath>
</parent>
<artifactId>hibernate-search-integrationtest-performance-backend-base</artifactId>

<name>Hibernate Search Integration Tests - Performance - Backend base</name>
<description>Base for backend performance tests</description>

<dependencies>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-engine</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-util-internal-integrationtest-mapper-stub</artifactId>
</dependency>

<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
</dependencies>

</project>
@@ -0,0 +1,39 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.integrationtest.performance.backend.base;

import org.hibernate.search.integrationtest.performance.backend.base.testsupport.index.AbstractBackendHolder;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.index.IndexInitializer;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.index.PerThreadIndexPartition;

import org.openjdk.jmh.annotations.CompilerControl;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.ThreadParams;

@State(Scope.Thread)
public abstract class AbstractBackendBenchmarks {

private IndexInitializer indexInitializer;
private PerThreadIndexPartition indexPartition;

protected void doSetupTrial(AbstractBackendHolder backendHolder, IndexInitializer indexInitializer,
ThreadParams threadParams) {
this.indexInitializer = indexInitializer;
this.indexPartition = new PerThreadIndexPartition( backendHolder, indexInitializer, threadParams );
}

@CompilerControl(CompilerControl.Mode.INLINE)
protected final IndexInitializer getIndexInitializer() {
return indexInitializer;
}

@CompilerControl(CompilerControl.Mode.INLINE)
protected final PerThreadIndexPartition getIndexPartition() {
return indexPartition;
}
}
@@ -0,0 +1,85 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.integrationtest.performance.backend.base;

import java.util.concurrent.CompletableFuture;

import org.hibernate.search.engine.backend.document.DocumentElement;
import org.hibernate.search.engine.backend.work.execution.DocumentCommitStrategy;
import org.hibernate.search.engine.backend.work.execution.spi.IndexIndexer;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.dataset.Dataset;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.dataset.DatasetHolder;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.index.AbstractBackendHolder;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.index.MappedIndex;
import org.hibernate.search.util.common.impl.Futures;
import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubBackendSessionContext;
import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMapperUtils;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;

/**
* Abstract class for JMH benchmarks related to mass indexing,
* i.e. document adds with few commits.
* <p>
* This benchmark generates and executes batches of documents to add to the index from a single thread,
* guaranteeing not to conflict with any other thread in the same trial.
*/
@Fork(1)
@State(Scope.Thread)
public abstract class AbstractMassIndexingBenchmarks extends AbstractBackendBenchmarks {

@Param({ "NONE" })
private DocumentCommitStrategy commitStrategy;

/**
* Equivalent to the MassIndexer's "batchSizeToLoadObjects".
*/
@Param({ "10" })
private int batchSize;

private MappedIndex index;
private IndexIndexer<? extends DocumentElement> indexer;
private Dataset dataset;

private long currentDocumentIdInThread = 0L;

@Setup(Level.Iteration)
public void prepareIteration(DatasetHolder datasetHolder) {
index = getIndexPartition().getIndex();
StubBackendSessionContext sessionContext = new StubBackendSessionContext();
indexer = index.getIndexManager()
.createIndexer( sessionContext, commitStrategy );
dataset = datasetHolder.getDataset();
}

@Benchmark
@Threads(3 * AbstractBackendHolder.INDEX_COUNT)
public void writeBatch(WriteCounters counters) throws InterruptedException {
CompletableFuture<?>[] futures = new CompletableFuture<?>[batchSize];

for ( int i = 0; i < batchSize; ++i ) {
long documentId = getIndexPartition().toDocumentId( currentDocumentIdInThread++ );
futures[i] = indexer.add(
StubMapperUtils.referenceProvider( String.valueOf( documentId ) ),
document -> dataset.populate( index, document, documentId, 0L )
);
}

// Do not return until works are *actually* executed
Futures.unwrappedExceptionGet( CompletableFuture.allOf( futures ) );

counters.write += batchSize;
}

}
@@ -0,0 +1,197 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.integrationtest.performance.backend.base;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;

import org.hibernate.search.engine.backend.common.DocumentReference;
import org.hibernate.search.engine.backend.document.DocumentElement;
import org.hibernate.search.engine.backend.work.execution.DocumentCommitStrategy;
import org.hibernate.search.engine.backend.work.execution.DocumentRefreshStrategy;
import org.hibernate.search.engine.backend.work.execution.spi.IndexIndexingPlan;
import org.hibernate.search.engine.search.query.SearchResult;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.dataset.Dataset;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.dataset.DatasetHolder;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.index.AbstractBackendHolder;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.index.MappedIndex;
import org.hibernate.search.integrationtest.performance.backend.base.testsupport.index.PerThreadIndexPartition;
import org.hibernate.search.util.common.impl.Futures;
import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubBackendSessionContext;
import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMapperUtils;
import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMappingIndexManager;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Group;
import org.openjdk.jmh.annotations.GroupThreads;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.infra.Blackhole;

/**
* Abstract class for JMH benchmarks related to on-the-fly indexing,
* which is primarily used when doing CRUD operations on the database
* in the ORM integration.
* <p>
* This benchmark generates and executes worksets on the index from a single thread,
* guaranteeing not to conflict with any other thread in the same trial.
* <p>
* Internally, this object keeps tab of documents currently present in the index
* and generates worksets accordingly.
*/
@Fork(1)
@State(Scope.Thread)
public abstract class AbstractOnTheFlyIndexingBenchmarks extends AbstractBackendBenchmarks {

@Param({ "NONE" })
private DocumentRefreshStrategy refreshStrategy;

/**
* The number of works of each type (add/update/delete)
* to put in each workset.
*/
@Param({ "3" })
private int worksPerTypePerWorkset;

/*
* We just want a sequence of numbers that spreads uniformly over a large interval,
* but we don't need cryptographically secure randomness,
* and we want the sequence to be the same from one test run to another.
* That's why we simply use {@link Random} and that's why we set the seed to
* a hard-coded value.
* Also, we use one random generator per thread to avoid contention.
*/
private final Random idShufflingRandom = new Random( 3210140441369L );

private Dataset dataset;

private long invocationCount;

private List<Long> idsToAdd;
private List<Long> idsToUpdate;
private List<Long> idsToDelete;

@Setup(Level.Iteration)
public void prepareIteration(DatasetHolder datasetHolder) {
this.dataset = datasetHolder.getDataset();
this.invocationCount = 0L;

int threadIdIntervalSize = 3 * worksPerTypePerWorkset;

// Initialize the ID lists
idsToAdd = new ArrayList<>();
idsToUpdate = new ArrayList<>();
idsToDelete = new ArrayList<>();
List<Long> shuffledIds = createShuffledIndexList( threadIdIntervalSize );
int offset = 0;
for ( int i = 0; i < worksPerTypePerWorkset; ++i ) {
idsToAdd.add( shuffledIds.get( i ) );
}
offset += worksPerTypePerWorkset;
for ( int i = 0; i < worksPerTypePerWorkset; ++i ) {
idsToDelete.add( shuffledIds.get( offset + i ) );
}
offset += worksPerTypePerWorkset;
for ( int i = 0; i < worksPerTypePerWorkset; ++i ) {
idsToUpdate.add( shuffledIds.get( offset + i ) );
}

getIndexInitializer().addToIndex(
getIndexPartition().getIndex(),
Stream.concat( idsToUpdate.stream(), idsToDelete.stream() )
.mapToLong( getIndexPartition()::toDocumentId )
);
}

@Benchmark
@Threads(3 * AbstractBackendHolder.INDEX_COUNT)
public void workset(WriteCounters counters) {
StubBackendSessionContext sessionContext = new StubBackendSessionContext();
PerThreadIndexPartition partition = getIndexPartition();
MappedIndex index = partition.getIndex();
IndexIndexingPlan<? extends DocumentElement> indexingPlan = index.getIndexManager()
.createIndexingPlan( sessionContext, getCommitStrategyParam(), refreshStrategy );

for ( Long documentIdInThread : idsToAdd ) {
long documentId = partition.toDocumentId( documentIdInThread );
indexingPlan.add(
StubMapperUtils.referenceProvider( String.valueOf( documentId ) ),
document -> dataset.populate( index, document, documentId, invocationCount )
);
}
for ( Long documentIdInThread : idsToUpdate ) {
long documentId = partition.toDocumentId( documentIdInThread );
indexingPlan.update(
StubMapperUtils.referenceProvider( String.valueOf( documentId ) ),
document -> dataset.populate( index, document, documentId, invocationCount )
);
}
for ( Long documentIdInThread : idsToDelete ) {
long documentId = partition.toDocumentId( documentIdInThread );
indexingPlan.delete(
StubMapperUtils.referenceProvider( String.valueOf( documentId ) )
);
}

// Do not return until works are *actually* executed
Futures.unwrappedExceptionJoin( indexingPlan.execute() );

counters.write += 3 * worksPerTypePerWorkset;

++invocationCount;

List<Long> nextToDelete = idsToUpdate;
idsToUpdate = idsToAdd;
idsToAdd = idsToDelete;
idsToDelete = nextToDelete;
}

@Benchmark
@GroupThreads(2 * AbstractBackendHolder.INDEX_COUNT)
@Group("concurrentReadWrite")
public void concurrentWorkset(WriteCounters counters) {
workset( counters );
}

@Benchmark
@GroupThreads(2 * AbstractBackendHolder.INDEX_COUNT)
@Group("concurrentReadWrite")
public void concurrentQuery(QueryParams params, Blackhole blackhole) {
PerThreadIndexPartition partition = getIndexPartition();
StubMappingIndexManager indexManager = partition.getIndex().getIndexManager();

SearchResult<DocumentReference> results = indexManager.createScope().query()
.where( f -> f.matchAll() )
.sort( f -> f.field( MappedIndex.SHORT_TEXT_FIELD_NAME ) )
.fetch( params.getQueryMaxResults() );

blackhole.consume( results.getTotalHitCount() );
for ( DocumentReference hit : results.getHits() ) {
blackhole.consume( hit );
}
}

protected abstract DocumentCommitStrategy getCommitStrategyParam();

private List<Long> createShuffledIndexList(int size) {
List<Long> result = new ArrayList<>( size );
for ( int i = 0; i < size; ++i ) {
result.add( (long) i );
}
Collections.shuffle( result, idShufflingRandom );
return result;
}

}
@@ -0,0 +1,23 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.integrationtest.performance.backend.base;

import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;

@State(Scope.Thread)
public class QueryParams {

@Param({ "100" })
private int maxResults;

public int getQueryMaxResults() {
return maxResults;
}

}

0 comments on commit a218356

Please sign in to comment.