package org.infinispan.query.distributed; import java.util.List; import java.util.Scanner; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.apache.lucene.search.MatchAllDocsQuery; import org.infinispan.Cache; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.cache.Index; import org.infinispan.context.Flag; import org.infinispan.query.CacheQuery; import org.infinispan.query.MassIndexer; import org.infinispan.query.Search; import org.infinispan.query.SearchManager; import org.infinispan.query.impl.massindex.IndexUpdater; import org.infinispan.test.MultipleCacheManagersTest; import org.infinispan.test.fwk.TestResourceTracker; /** * Long running test for the async MassIndexer, specially regarding cancellation. Supposed to be run as a main class. * @author gustavonalle * @since 7.1 */ public class AsyncMassIndexPerfTest extends MultipleCacheManagersTest { /** * Number of entries to write */ private static final int OBJECT_COUNT = 1000000; /** * Number of threads to do the initial load */ private static final int WRITING_THREADS = 5; /** * If should SKIP_INDEX during initial load */ private static final boolean DISABLE_INDEX_WHEN_INSERTING = true; private static final boolean TX_ENABLED = false; private static final String MERGE_FACTOR = "30"; private static final CacheMode CACHE_MODE = CacheMode.DIST_SYNC; private static final IndexManager INDEX_MANAGER = IndexManager.INFINISPAN; private static final Provider DIRECTORY_PROVIDER = Provider.INFINISPAN; /** * Hibernate search backend used. Either sync or async (commit every 1s by default) */ private static final WorkerMode WORKER_MODE = WorkerMode.sync; /** * For status report during insertion */ private static final int PRINT_EACH = 10; private Cache<Integer, Transaction> cache1, cache2; private MassIndexer massIndexer; @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder cacheCfg = getDefaultClusteredCacheConfig(CACHE_MODE, TX_ENABLED); cacheCfg.clustering().remoteTimeout(120000) .indexing().index(Index.LOCAL) .addIndexedEntity(Transaction.class) .addProperty("default.directory_provider", DIRECTORY_PROVIDER.toString()) .addProperty("default.indexmanager", INDEX_MANAGER.toString()) .addProperty("default.indexwriter.merge_factor", MERGE_FACTOR) .addProperty("hibernate.search.default.worker.execution", WORKER_MODE.toString()) .addProperty("error_handler", "org.infinispan.query.helper.StaticTestingErrorHandler") .addProperty("lucene_version", "LUCENE_CURRENT"); List<Cache<Integer, Transaction>> caches = createClusteredCaches(2, cacheCfg); cache1 = caches.get(0); cache2 = caches.get(1); massIndexer = Search.getSearchManager(cache1).getMassIndexer(); } private void writeData() throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(WRITING_THREADS, getTestThreadFactory("Worker")); final AtomicInteger counter = new AtomicInteger(0); for (int i = 0; i < OBJECT_COUNT; i++) { executorService.submit(() -> { int key = counter.incrementAndGet(); Cache insertCache; if (DISABLE_INDEX_WHEN_INSERTING) { insertCache = cache1.getAdvancedCache().withFlags(Flag.SKIP_INDEXING); } else { insertCache = cache1; } insertCache.put(key, new Transaction(key * 100, "0eab" + key)); if (key != 0 && key % PRINT_EACH == 0) { System.out.printf("\rInserted %d", key); } }); } executorService.shutdown(); executorService.awaitTermination(3, TimeUnit.MINUTES); if (!DISABLE_INDEX_WHEN_INSERTING) { waitForIndexSize(OBJECT_COUNT); } System.out.println(); } /** * Waits until the index reaches a certain size. Useful for async backend */ private void waitForIndexSize(final int expected) { eventually(() -> { int idxCount = countIndex(); System.out.printf("\rWaiting for indexing completion (%d): %d indexed so far", expected, +idxCount); return idxCount == expected; }); System.out.println("\nIndexing done."); } public static void main(String[] args) throws Throwable { AsyncMassIndexPerfTest test = new AsyncMassIndexPerfTest(); TestResourceTracker.testThreadStarted(test); test.createBeforeClass(); test.createBeforeMethod(); test.populate(); } public void populate() throws Exception { StopTimer stopTimer = new StopTimer(); writeData(); stopTimer.stop(); System.out.printf("\rData inserted in %d seconds.", stopTimer.getElapsedIn(TimeUnit.SECONDS)); info(); new Thread(new EventLoop(massIndexer)).start(); } private void info() { System.out.println("\rr: Run MassIndexer\nc: Cancel MassIndexer\ni: Put new entry\ns: Current index size\np: Purge indexes\nf: flush\nh: This menu\nx: Exit"); } private enum Provider { RAM("ram"), FILESYSTEM("filesystem"), INFINISPAN("infinispan"); private final String cfg; Provider(String cfg) { this.cfg = cfg; } @Override public String toString() { return cfg; } } private enum WorkerMode { async, sync } private enum IndexManager { NRT("near-real-time"), INFINISPAN("org.infinispan.query.indexmanager.InfinispanIndexManager"), ELASTIC_SEARCH("elasticsearch"), DIRECTORY("directory-based"); private final String cfg; IndexManager(String cfg) { this.cfg = cfg; } @Override public String toString() { return cfg; } } protected int countIndex() { SearchManager searchManager = Search.getSearchManager(cache1); CacheQuery<?> q = searchManager.getQuery(new MatchAllDocsQuery(), Transaction.class); return q.getResultSize(); } protected void clearIndex() { SearchManager searchManager = Search.getSearchManager(cache1); searchManager.purge(Transaction.class); } class EventLoop implements Runnable { private MassIndexer massIndexer; private CompletableFuture<Void> future; private AtomicInteger nexIndex = new AtomicInteger(OBJECT_COUNT); public EventLoop(MassIndexer massIndexer) { this.massIndexer = massIndexer; } void startMassIndexer() { System.out.println("Running MassIndexer"); final StopTimer stopTimer = new StopTimer(); future = massIndexer.startAsync(); future.whenComplete((v, t) -> { stopTimer.stop(); if (t != null) { System.out.println("Error executing massindexer"); t.printStackTrace(); } System.out.printf("\nMass indexer run in %d seconds", stopTimer.getElapsedIn(TimeUnit.SECONDS)); System.out.println(); waitForIndexSize(nexIndex.get()); System.out.println("Mass Indexing complete."); }); } @Override public void run() { Scanner scanner = new Scanner(System.in); while (!Thread.interrupted()) { String next = scanner.next(); if ("c".equals(next)) { if (future == null) { System.out.println("\rMassIndexer not started"); continue; } else { future.cancel(true); System.out.println("Mass Indexer cancelled"); } } if ("r".equals(next)) { startMassIndexer(); } if ("f".equals(next)) { flushIndex(); System.out.println("Index flushed."); } if ("s".equals(next)) { System.out.printf("Index size is %d\n", countIndex()); } if ("p".equals(next)) { clearIndex(); System.out.println("Index cleared."); } if ("i".equals(next)) { int nextIndex = nexIndex.incrementAndGet(); cache2.put(nextIndex, new Transaction(nextIndex, "0" + nextIndex)); System.out.println("New entry inserted"); } if ("h".equals(next)) { info(); } if ("x".equals(next)) { System.exit(0); } } } } private void flushIndex() { new IndexUpdater(cache1).flush(Transaction.class); } class StopTimer { private long start; private long elapsed; public StopTimer() { start = currentTime(); } private long currentTime() { return System.currentTimeMillis(); } public void reset() { start = currentTime(); } public void stop() { elapsed = currentTime() - start; } public long getElapsedIn(TimeUnit unit) { return unit.convert(elapsed, TimeUnit.MILLISECONDS); } } }