package org.gbif.occurrence.persistence; import org.gbif.occurrence.common.config.OccHBaseConfiguration; import org.gbif.occurrence.persistence.api.KeyLookupResult; import org.gbif.occurrence.persistence.guice.ThreadLocalLockProvider; import org.gbif.occurrence.persistence.keygen.HBaseLockingKeyService; import org.gbif.occurrence.persistence.keygen.KeyPersistenceService; import org.gbif.occurrence.persistence.keygen.ZkLockingKeyService; import java.io.IOException; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import com.google.common.base.Stopwatch; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.RetryNTimes; import org.apache.curator.test.TestingServer; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.util.Bytes; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.assertEquals; @Ignore("Extremely expensive, many threaded test.") public class KeyPersistenceServiceTest { private static final String A = "a"; private static final String B = "b"; private static final String C = "c"; private static final String D = "d"; private static final String E = "e"; private static final OccHBaseConfiguration CFG = new OccHBaseConfiguration(); static { CFG.setEnvironment("test"); } private static final byte[] LOOKUP_TABLE = Bytes.toBytes(CFG.lookupTable); private static final String CF_NAME = "o"; private static final byte[] CF = Bytes.toBytes(CF_NAME); private static final byte[] COUNTER_TABLE = Bytes.toBytes(CFG.counterTable); private static final String COUNTER_CF_NAME = "o"; private static final byte[] COUNTER_CF = Bytes.toBytes(COUNTER_CF_NAME); private static final byte[] OCCURRENCE_TABLE = Bytes.toBytes(CFG.occTable); private static Connection connection = null; private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static TestingServer ZOOKEEPER_SERVER; private static CuratorFramework CURATOR; private static ThreadLocalLockProvider ZOO_LOCK_PROVIDER; @BeforeClass public static void beforeClass() throws Exception { TEST_UTIL.startMiniCluster(1); TEST_UTIL.createTable(LOOKUP_TABLE, CF); TEST_UTIL.createTable(COUNTER_TABLE, COUNTER_CF); TEST_UTIL.createTable(OCCURRENCE_TABLE, CF); ZOOKEEPER_SERVER = new TestingServer(); CURATOR = CuratorFrameworkFactory.builder() .namespace("hbasePersistence") .connectString(ZOOKEEPER_SERVER.getConnectString()) .retryPolicy(new RetryNTimes(1, 1000)).build(); CURATOR.start(); ZOO_LOCK_PROVIDER = new ThreadLocalLockProvider(CURATOR); connection = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); } @Before public void before() throws IOException { TEST_UTIL.truncateTable(LOOKUP_TABLE); TEST_UTIL.truncateTable(COUNTER_TABLE); TEST_UTIL.truncateTable(OCCURRENCE_TABLE); } @AfterClass public static void afterClass() throws Exception { CURATOR.close(); ZOOKEEPER_SERVER.stop(); TEST_UTIL.shutdownMiniCluster(); connection.close(); } @Ignore @Test public void testZkLockingKeyService() { ZkLockingKeyService service = new ZkLockingKeyService(CFG, connection, ZOO_LOCK_PROVIDER); testContention(service); } @Ignore @Test public void testHBaseLockingKeyService() { HBaseLockingKeyService service = new HBaseLockingKeyService(CFG, connection); testContention(service); } private void testContention(KeyPersistenceService kps) { String scope = UUID.randomUUID().toString(); // the constants A,B,C,D,E are meant to all refer to the same occurrence, used to introduce contention // generate 1M sets of uniqueStrings List<Set<String>> testSets = Lists.newArrayList(); int testSize = 1000000; int abc = 0; int bcd = 0; int cde = 0; int non = 0; for (int i = 0; i < testSize; i++) { Set<String> uniqueStrings = Sets.newHashSet(); if (i % 7 == 0) { cde++; uniqueStrings.add(C); uniqueStrings.add(D); uniqueStrings.add(E); } else if (i % 5 == 0) { bcd++; uniqueStrings.add(B); uniqueStrings.add(C); uniqueStrings.add(D); } else if (i % 3 == 0) { abc++; uniqueStrings.add(A); uniqueStrings.add(B); uniqueStrings.add(C); } else { non++; uniqueStrings.add(String.valueOf(i)); } testSets.add(uniqueStrings); } // 1M produces abc [228571] bcd [171428] cde [142858] non [457143] so expect 457144 ids to be generated // System.out.println( // "got abc [" + abc + "] bcd [" + bcd + "] cde [" + cde + "] non [" + non + "] sum [" + (abc + bcd + cde + non) // + "]"); // call generateKey for each of them, using X threads to do it concurrently int threadCount = 100; int submitCount = 0; ExecutorService threadPool = Executors.newFixedThreadPool(threadCount); Stopwatch stopwatch = new Stopwatch().start(); for (Set<String> uniques : testSets) { threadPool.execute(new KeyGenRunnable(kps, uniques, scope, ++submitCount)); } threadPool.shutdown(); try { threadPool.awaitTermination(30, TimeUnit.MINUTES); } catch (InterruptedException e) { // oh well } stopwatch.stop(); System.out.println("1M keygen with [" + threadCount + "] threads took [" + stopwatch + "]"); // expect Y new ids (test that nextId would = Y + 1) Set<String> finalStrings = Sets.newHashSet(); finalStrings.add(UUID.randomUUID().toString()); KeyLookupResult nextResult = kps.generateKey(finalStrings, scope); assertEquals(457145, nextResult.getKey()); } private static class KeyGenRunnable implements Runnable { private final KeyPersistenceService kps; private final Set<String> uniqueStrings; private final String scope; private final int id; private KeyGenRunnable(KeyPersistenceService kps, Set<String> uniqueStrings, String scope, int id) { this.kps = kps; this.uniqueStrings = uniqueStrings; this.scope = scope; this.id = id; } @Override public void run() { kps.generateKey(uniqueStrings, scope); if (id % 10000 == 0) { System.out.println("Finished [" + id + "]"); } } } }