/*- * Copyright (C) 2002, 2017, Oracle and/or its affiliates. All rights reserved. * * This file was distributed by Oracle as part of a version of Oracle Berkeley * DB Java Edition made available at: * * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html * * Please see the LICENSE file included in the top-level directory of the * appropriate version of Oracle Berkeley DB Java Edition for a copy of the * license and additional information. */ import java.io.File; import java.math.BigInteger; import java.security.SecureRandom; import java.util.Arrays; import com.sleepycat.je.Cursor; import com.sleepycat.je.Database; import com.sleepycat.je.DatabaseConfig; import com.sleepycat.je.DatabaseEntry; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.LockConflictException; import com.sleepycat.je.Environment; import com.sleepycat.je.EnvironmentConfig; import com.sleepycat.je.LockMode; import com.sleepycat.je.OperationStatus; /** * A large database with random key distribution has lots of IN waste, * especially if records are small; this creates a worst-case scenario for the * cleaner and also possibly for the evictor. Simulate such an application and * measure how well the cleaner and evictor keep up. * * Some commonly used command lines for running this program are: * * # Init new DB, causes duplicates to be created and deleted [#15588] * java BigDW -h HOME -init -dupdel * * Each transaction does the following in "grow" mode. In "no grow" mode, it * does one less insert, keeping the total number of keys constant. * * 2 inserts, 1 delete, 1 update, 10 reads * * The delete and update operations include a read to find the record. * */ public class BigDW implements Runnable { private String homeDir = "tmp"; private Environment env; private Database refDB; private Database testDB; private boolean done; private int nDeadlocks; private boolean init; private boolean verbose; private boolean dupDel; private int nTransactions; private int nMaxTransactions = 20000; private int nThreads = 4; private int subDir = 0; private int keySize = 10; private int dataSize = 10; private int nReadsPerWrite = 1; private int maxRetries = 100; private float totalSecs; private float throughput; private SecureRandom random = new SecureRandom(); private long startTime; private long time; private long mainCacheSize = 20000000; public static void main(String args[]) { try { new BigDW().run(args); System.exit(0); } catch (Throwable e) { e.printStackTrace(System.out); System.exit(1); } } /* Output command-line input arguments to log. */ private void printArgs(String[] args) { System.out.print("\nCommand line arguments:"); for (String arg : args) { System.out.print(' '); System.out.print(arg); } System.out.println(); } private void usage(String error) { if (error != null) { System.err.println(error); } System.err.println ("java " + getClass().getName() + '\n' + " [-h ] [-v] [-init] [-dupdel]\n" + " [-txns ]\n"); System.exit(1); } private void run(String args[]) throws Exception { try { if (args.length == 0) { throw new IllegalArgumentException(); } /* Parse command-line input arguments. */ for (int i = 0; i < args.length; i += 1) { String arg = args[i]; boolean moreArgs = i < args.length - 1; if (arg.equals("-v")) { verbose = true; } else if (arg.equals("-dupdel")) { dupDel = true; } else if (arg.equals("-h") && moreArgs) { homeDir = args[++i]; } else if (arg.equals("-init")) { init = true; } else if (arg.equals("-txns") && moreArgs) { nMaxTransactions = Integer.parseInt(args[++i]); } else if (arg.equals("-threads") && moreArgs) { nThreads = Integer.parseInt(args[++i]); } else if (arg.equals("-subDir") && moreArgs) { subDir = Integer.parseInt(args[++i]); } else { usage("Unknown arg: " + arg); } } printArgs(args); } catch (IllegalArgumentException e) { usage("IllegalArguments! "); e.printStackTrace(); System.exit(1); } openEnv(); startTime = System.currentTimeMillis(); Thread[] threads = new Thread[nThreads]; for (int i = 0; i < nThreads; i += 1) { threads[i] = new Thread(this); threads[i].start(); Thread.sleep(1000); /* Stagger threads. */ } for (int i = 0; i < nThreads; i += 1) { if (threads[i] != null) { threads[i].join(); } } time = System.currentTimeMillis(); closeEnv(); totalSecs = (float) (time - startTime) / 1000; throughput = (float) nTransactions / totalSecs; if (verbose) { System.out.println("\nTotal seconds: " + totalSecs + " txn/sec: " + throughput); } } private void openEnv() throws Exception { EnvironmentConfig envConfig = new EnvironmentConfig(); envConfig.setTransactional(true); envConfig.setAllowCreate(init); envConfig.setCacheSize(mainCacheSize); if (subDir > 0) { envConfig.setConfigParam (EnvironmentConfig.LOG_N_DATA_DIRECTORIES, subDir + ""); Utils.createSubDirs(new File(homeDir), subDir); } env = new Environment(new File(homeDir), envConfig); DatabaseConfig dbConfig = new DatabaseConfig(); dbConfig.setAllowCreate(init); dbConfig.setExclusiveCreate(init); dbConfig.setSortedDuplicates(dupDel); refDB = env.openDatabase(null, "BigDWRef", dbConfig); dbConfig.setDeferredWrite(true); testDB = env.openDatabase(null, "BigDWTest", dbConfig); compare(); } private void closeEnv() throws Exception { refDB.close(); testDB.sync(); testDB.close(); env.close(); } public void run() { DatabaseEntry data = new DatabaseEntry(); DatabaseEntry key = new DatabaseEntry(); byte[] lastInsertKey = null; while (!done) { /* JE-only begin */ try { /* Perform the transaction. */ for (int retry = 0;; retry += 1) { Cursor refCursor = refDB.openCursor(null, null); Cursor testCursor = testDB.openCursor(null, null); try { if (init) { key.setData(lastInsertKey); insert(refCursor, testCursor, key, data); lastInsertKey = copyData(key); } /* Insert */ key.setData(lastInsertKey); insert(refCursor, testCursor, key, data); lastInsertKey = copyData(key); /* Dup-key insert. */ byte[] dupDataBA = copyData(data); for (int i = 0; i < 5; i++) { dupDataBA[0]++; DatabaseEntry dupData = new DatabaseEntry(dupDataBA); OperationStatus status1 = refCursor.put(key, dupData); @SuppressWarnings("unused") boolean insertDone1 = checkInsertStatus(status1); if (status1 != OperationStatus.SUCCESS) { throw new RuntimeException("insert1 " + status1); } OperationStatus status2 = testCursor.put(key, dupData); if (status2 != OperationStatus.SUCCESS) { throw new RuntimeException("insert2 " + status2); } @SuppressWarnings("unused") boolean insertDone2 = checkInsertStatus(status2); } /* Delete */ getRandom(refCursor, "BigDWRef", testCursor, "BigDWTest", key, data, LockMode.RMW); DatabaseEntry dummy1 = new DatabaseEntry(); DatabaseEntry dummy2 = new DatabaseEntry(); while (refCursor.delete() == OperationStatus.SUCCESS && refCursor.getNextDup (dummy1, dummy2, null) == OperationStatus.SUCCESS) { } while (testCursor.delete() == OperationStatus.SUCCESS && refCursor.getNextDup (dummy1, dummy2, null) == OperationStatus.SUCCESS) { } /* Read */ for (int i = 0; i < nReadsPerWrite; i += 1) { getRandom(refCursor, "BigDWRef", testCursor, "BigDWTest", key, data, LockMode.RMW); } refCursor.close(); testCursor.close(); nTransactions += 1; if (nMaxTransactions != 0 && nTransactions >= nMaxTransactions) { done = true; } break; } catch (LockConflictException e) { refCursor.close(); testCursor.close(); if (retry >= maxRetries) { throw e; } /* Break deadlock cycle with a small sleep. */ Thread.sleep(5); nDeadlocks += 1; } } /* for */ } catch (Throwable e) { e.printStackTrace(); System.exit(1); } } } private void checkStatus(OperationStatus status) throws Exception { if (status != OperationStatus.SUCCESS) { throw new Exception("problemStatus = " + status); } } private void compare() throws Exception { DatabaseEntry refKey = new DatabaseEntry(); DatabaseEntry refData = new DatabaseEntry(); DatabaseEntry testKey = new DatabaseEntry(); DatabaseEntry testData = new DatabaseEntry(); Cursor refCursor = refDB.openCursor(null, null); Cursor testCursor = testDB.openCursor(null, null); System.out.println("Compare starts"); try { while (refCursor.getNext(refKey, refData, LockMode.DEFAULT) == OperationStatus.SUCCESS) { checkStatus(testCursor.getNext(testKey, testData, LockMode.DEFAULT)); if (!Arrays.equals(refKey.getData(), testKey.getData())) { throw new Exception("Keys don't match"); } if (!Arrays.equals(refData.getData(), testData.getData())) { throw new Exception("Data don't match"); } } if (testCursor.getNext(testKey, testData, LockMode.DEFAULT) != OperationStatus.NOTFOUND) { throw new Exception("testCursor has extra data"); } } finally { refCursor.close(); testCursor.close(); } System.out.println("Compare ends"); } private void insert(Cursor c1, Cursor c2, DatabaseEntry key, DatabaseEntry data) throws DatabaseException { makeData(data); boolean insertDone1 = false; while (!insertDone1) { makeInsertKey(key); OperationStatus status1 = c1.putNoOverwrite(key, data); insertDone1 = checkInsertStatus(status1); OperationStatus status2 = c2.putNoOverwrite(key, data); boolean insertDone2 = checkInsertStatus(status2); assert insertDone1 == insertDone2 : "status1=" + status1 + " status2=" + status2; } } private boolean checkInsertStatus(OperationStatus status) { if (status == OperationStatus.KEYEXIST) { System.out.println("****** Duplicate random key."); return false; // try again. } else { if (status != OperationStatus.SUCCESS) { System.out.println ("Unexpected return value from insert(): " + status); } return true; // end one way or another } } private void getRandom(Cursor c1, String db1, Cursor c2, String db2, DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { makeRandomKey(key); getRandomWork(c1, db1, key, data, lockMode); getRandomWork(c2, db2, key, data, lockMode); } private void getRandomWork(Cursor c, String dbName, DatabaseEntry key, DatabaseEntry data, LockMode lockMode) throws DatabaseException { OperationStatus status = c.getSearchKeyRange(key, data, lockMode); if (status == OperationStatus.NOTFOUND) { status = c.getLast(key, data, lockMode); if (status != OperationStatus.SUCCESS) { System.out.println ("Unexpected return value from " + dbName + ".getRandomWork(): " + status); } } } private void makeInsertKey(DatabaseEntry key) { if (key.getData() != null) { BigInteger num = new BigInteger(copyData(key)); num = num.add(BigInteger.ONE); key.setData(num.toByteArray()); } else { makeRandomKey(key); } } private void makeRandomKey(DatabaseEntry key) { byte[] bytes = new byte[keySize]; random.nextBytes(bytes); key.setData(bytes); } private void makeData(DatabaseEntry data) { byte[] bytes = new byte[dataSize]; for (int i = 0; i < bytes.length; i += 1) { bytes[i] = (byte) i; } data.setData(bytes); } private byte[] copyData(DatabaseEntry data) { byte[] buf = new byte[data.getSize()]; System.arraycopy(data.getData(), data.getOffset(), buf, 0, buf.length); return buf; } }