package org.javersion.store.jdbc; import com.google.common.cache.CacheBuilder; import org.javersion.object.ObjectVersion; import org.javersion.object.ObjectVersionGraph; import org.javersion.path.PropertyPath; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.support.TransactionTemplate; import javax.annotation.Resource; import java.io.*; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import static java.lang.System.currentTimeMillis; import static java.util.concurrent.Executors.newFixedThreadPool; import static org.javersion.path.PropertyPath.ROOT; import static org.javersion.store.jdbc.ExecutorType.ASYNC; import static org.javersion.store.jdbc.GraphOptions.keepHeadsAndNewest; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = PersistenceTestConfiguration.class) public class LoadTest { private static final String[] VALUES = {"Lorem","ipsum","dolor","sit","amet,","consectetuer","adipiscing","elit.","Sed","posuere","interdum","sem.", "Quisque","ligula","eros","ullamcorper","quis,","lacinia","quis","facilisis","sed","sapien.","Mauris","varius","diam","vitae","arcu.","Sed", "arcu","lectus","auctor","vitae,","consectetuer","et","venenatis","eget","velit.","Sed","augue","orci,","lacinia","eu","tincidunt","et", "eleifend","nec","lacus.","Donec","ultricies","nisl","ut","felis,","suspendisse","potenti.","Lorem","ipsum","ligula","ut","hendrerit", "mollis,","ipsum","erat","vehicula","risus,","eu","suscipit","sem","libero","nec","erat.","Aliquam","erat","volutpat.","Sed","congue", "augue","vitae","neque.","Nulla","consectetuer","porttitor","pede.","Fusce","purus","morbi","tortor","magna","condimentum","vel,","placerat", "id","blandit","sit","amet","tortor"}; private int nextValue = 0; private final int docCount = 200; private final int docVersionCount = 200; private final int propCount = 20; private final int keepNewest = 20; private final int compactThreshold = 100; private final Executor testExecutor = newFixedThreadPool(Runtime.getRuntime().availableProcessors()); private final GraphOptions<String, String> graphOptions = keepHeadsAndNewest(keepNewest, compactThreshold); private Writer out; private String storeName; private VersionStore<String, String> store; private GuavaGraphCache<String, String> cache; @Resource DocumentStoreOptions<String, String, JDocumentVersion<String>> documentStoreOptions; @Resource EntityStoreOptions<String, String, JEntityVersion<String>> entityStoreOptions; @Resource TransactionTemplate transactionTemplate; @Before public void initWriter() throws FileNotFoundException, UnsupportedEncodingException { out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("results.csv", false), "utf-8")); } @After public void closeWriter() throws IOException { out.flush(); out.close(); } @Test @Ignore public void document_store_performance() throws Exception { List<String> docIds = generateDocIds(docCount); store = new DocumentVersionStoreJdbc<>(documentStoreOptions.toBuilder() .graphOptions(graphOptions) .publisherType(ASYNC) .optimizerType(ASYNC) .build()); run(docIds); } @Test @Ignore public void entity_store_performance() throws Exception { List<String> docIds = generateDocIds(docCount); store = new EntityVersionStoreJdbc<>(entityStoreOptions.toBuilder() .graphOptions(graphOptions) .publisherType(ASYNC) .optimizerType(ASYNC) .build()); run(docIds); } private void run(List<String> docIds) throws IOException, InterruptedException { storeName = store.getClass().getSimpleName(); cache = new GuavaGraphCache<>(store, CacheBuilder.<String, ObjectVersionGraph<Void>>newBuilder() .maximumSize(docIds.size()) .refreshAfterWrite(1, TimeUnit.NANOSECONDS), graphOptions); print( "#Store", "Load Size", "Optimized Size", "Cached Size", "Load Time", "Optimized(" + keepNewest + "/" + compactThreshold + ") Time", "Cached Time", "Append Time" ); // Insert first versions for (String docId : docIds) { tick(docId, false); } // runByDocId(docIds); runByVersion(docIds); } private void runByDocId(List<String> docIds) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(docIds.size()); for (String docId : docIds) { testExecutor.execute(() -> { for (int round = 0; round < docVersionCount; round++) { try { tick(docId, true); } finally { countDownLatch.countDown(); } } }); } countDownLatch.await(); } private void runByVersion(List<String> docIds) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(docIds.size() * docVersionCount); for (int round = 0; round < docVersionCount; round++) { for (String docId : docIds) { testExecutor.execute(() -> { try { tick(docId, true); } finally { countDownLatch.countDown(); } }); } } countDownLatch.await(); } private void tick(String docId, boolean printResult) { ObjectVersionGraph<String> versionGraph; // Load full long ts = currentTimeMillis(); versionGraph = store.getFullGraph(docId); final long loadSize = versionGraph.size(); final long loadTime = currentTimeMillis() - ts; final ObjectVersionGraph<String> baseGraph = versionGraph; // Load optimized ts = currentTimeMillis(); versionGraph = store.getOptimizedGraph(docId); final long optimizedSize = versionGraph.size(); final long optimizedTime = currentTimeMillis() - ts; // Load cached ts = currentTimeMillis(); versionGraph = cache.load(docId); final long cachedSize = versionGraph.size(); final long cachedTime = currentTimeMillis() - ts; ObjectVersion.Builder<String> builder = ObjectVersion.<String>builder() .changeset(generateProperties(propCount)); if (!baseGraph.isEmpty()) { builder.parents(baseGraph.getHeadRevisions()); } // Add version ts = currentTimeMillis(); transactionTemplate.execute(status -> { store.updateBatch(docId) .addVersion(docId, baseGraph.commit(builder.build()).getTip()) .execute(); return null; }); final long appendTime = currentTimeMillis() - ts; if (printResult) { print( storeName, loadSize, optimizedSize, cachedSize, loadTime, optimizedTime, cachedTime, appendTime ); } } private synchronized void print(Object... values) { StringBuilder sb = new StringBuilder(64); for (Object value : values) { if (sb.length() > 0) { sb.append(','); } sb.append(value); } sb.append('\n'); try { out.append(sb); } catch (IOException e) { throw new RuntimeException(e); } } private Map<PropertyPath, Object> generateProperties(int count) { Map<PropertyPath, Object> properties = new HashMap<>(); PropertyPath.Property list = ROOT.property("list"); for (int i=1; i <= count; i++) { properties.put(list.index(i), VALUES[nextValue++ % VALUES.length]); } return properties; } private List<String> generateDocIds(int count) { // select '"' || id || '",' from entity limit 100; // return Arrays.asList( // ).subList(0, count); List<String> ids = new ArrayList<>(count); for (int i=0; i < count; i++) { ids.add(UUID.randomUUID().toString()); } return ids; } }