首页 > Berkeley DB JE, Chao Huang > Berkeley DB Java版性能测试

Berkeley DB Java版性能测试

2009年9月21日 chaohuang

初衷

最近有很多朋友问到关于BDB等相关的一些性能测试数据,我想性能测试的结果受很多因素的影响,比如:你的程序设计,多线程/并发,测试数据集,测试平台等等。

一个简单的性能测试程序

在我之前的blog: 在Berkeley DB Java版中实现SQL查询,我提到了一下自己写的一个单线程的例子程序在9秒内读取了100万条记录,22秒内插入100万的记录。

我想,我可以在此和大家分享一下我的程序。当然,程序是我花了半天的时间开发的,仅供参考,不代表官方申明。

数据库记录格式 – DPLEntity.java

import com.sleepycat.persist.model.Entity;
import com.sleepycat.persist.model.KeyField;
import com.sleepycat.persist.model.Persistent;
import com.sleepycat.persist.model.PrimaryKey;

/**
 * 仅供参考,不作为官方申明!
 *
 * @author chao
 *
 */
public class DPLEntity {
}

@Persistent
class CompositeKey {

    @KeyField(1)
    int f0 = 0;
    @KeyField(2)
    String f1 = "The quick brown fox jumps over the lazy dog.";

    CompositeKey() { } // for bindings

    CompositeKey(int f0) {
        this.f0 = f0;
    }

    CompositeKey(int f0, String f1) {
        this.f0 = f0;
        this.f1 = f1;
    }

    @Override
    public String toString() {
        return "CompositeKey: (" + f0 + "," + f1 + ")";
    }
}

@Entity
class BasicEntity {

    @PrimaryKey
    CompositeKey key;

    protected long id = 0;
    protected String one = "one";
    protected double two = .2d;
    protected String three = "three";
    Address address = new Address();

    BasicEntity() { }

    BasicEntity(int i) {
        this.key = new CompositeKey(i);
    }

    public void modify() {
        id++;
        one += "1";
        two = id;
        three += "3";
        address = new Address("Shenzhen", "Guangdong, China", 500001);
    }

    @Override
    public String toString() {
        return "BasicEntity: (" + key + "," + id + "," + one + "," +
            two + "," + three + ", " + address + ")";
    }
}

@Persistent
class Address {

    private String city = "Boston";
    private String state = "Massachusetts";
    private int zip = 10001;

    Address() { }

    Address(String city, String state, int zip) {
        this.city = city;
        this.state = state;
        this.zip = zip;
    }

    @Override
    public String toString() {
        return "Address: (" + city + "," + state + "," + zip + ")";
    }
}

多线程 – MyThread.java

import com.sleepycat.je.CursorConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.Transaction;
import com.sleepycat.persist.EntityCursor;
import com.sleepycat.persist.EntityStore;
import com.sleepycat.persist.PrimaryIndex;

/**
 * 仅供参考,不作为官方申明!
 *
 * @author chao
 *
 */
public class MyThread extends Thread {

    /* Global settings */
    protected static final int EXIT_SUCCESS = 0;
    protected static final int EXIT_FAILURE = 1;

    /* Thread id */
    protected int id;
    /* Start time */
    private long start;
    /* End time */
    private long end;
    /* Num ops required */
    private int numRequiredOps;
    /* Num txns completed */
    private int numTxns;
    /* Num operations executed */
    private int numExecutedOps;
    /* Num deadlocks seen, results in retries */
    private int numDeadlocks;
    /* Entitystore */
    private EntityStore store;
    /* Primary index */
    private PrimaryIndex primaryIndex;
    /* Runtime configurations */
    private MyExample.TestConfig testConfig;

    private int keyNum;

    public MyThread(int id,
                    EntityStore store,
                    PrimaryIndex primaryIndex,
                    MyExample.TestConfig testConfig,
                    int numRequiredOps) {
        this.id = id;
        this.store = store;
        this.primaryIndex = primaryIndex;
        this.testConfig = testConfig;
        this.numRequiredOps = numRequiredOps;
    }

    public void run() {

        System.out.println("Access method thread: " + id + " started.");

        /* Allow the other threads to start. */
        yield();

        /* Record our start time. */
        start = System.currentTimeMillis();

        numTxns = 0;
        int orderedKey = 0;

        try {
            numExecutedOps = 0;
            boolean done = false;

            orderedKey = id;
            while (!done) {
                Object txn = null;
                try {
                    /* Begin the transaction. */
                    if (true) {
                        txn = beginTransaction();
                        numTxns++;
                    }

                    /* Perform the operations. */
                    boolean execOk = true;
                    int itemsPerTxn = testConfig.itemsPerTxn;
                    for (int j = 0; j < itemsPerTxn; j++) {
                        selectKey(orderedKey);

                        if (testConfig.operationType.equalsIgnoreCase("READ")) {
                            /* Read */
                            databaseGet(txn);
                        } else if (testConfig.operationType.
                                equalsIgnoreCase("DELETE")) {
                            /* Delete */
                            databaseDelete(txn);
                        } else if (testConfig.operationType.
                                equalsIgnoreCase("INSERT")) {
                            /* Insert */
                            databasePutNoOverwrite(txn);
                        } else if (testConfig.operationType.
                                equalsIgnoreCase("UPDATE")) {
                            /* Update */
                            databaseUpdate(txn);
                        } else {
                            /* Cursor scan with dirty read. */
                            scan();
                        }

                        if (execOk) {
                            numExecutedOps++;
                            orderedKey += testConfig.numThreads;
                        }

                        /* See if we're done. */
                        if (numExecutedOps >= numRequiredOps) {
                            done = true;
                        }

                        if (done) {
                            break;
                        }
                    }

                    /* Commit the transaction. */
                    if (txn != null) {
                        commitTxn(txn);
                    }

                    if (done) {
                        break;
                    }
                } catch (Exception e) {
                    /* Deal with deadlock and other errors. */
                    if (txn != null) {
                        try {
                            abortTxn(txn);
                        } catch (Exception e2) {
                            System.err.println("abort: " + e2);
                            System.err.println("original exception: " + e);
                            System.exit(EXIT_FAILURE);
                        }
                    }
                    exitIfNotDeadlock(e);
                    /* else will retry on next iteration */
                    numDeadlocks++;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }

        /* Record our end time. */
        end = System.currentTimeMillis();

        System.out.println("Access method thread " + id +
                " exiting cleanly. Executed " + numRequiredOps + " " +
                testConfig.operationType + " ops in " +
                (end - start) + " ms.");
    }

    /**
     * Select the key. It may be chosen in one of four ways:
     *  - random value
     *  - value within a range
     *  - value tied to the current iteration
     *  - a single, present value.
     *
     */
    private void selectKey(int iterationVal) {
        int val = iterationVal;

        assignKey(val);
    }

    private void assignKey(int val) {
        keyNum = val;
    }

    private Object beginTransaction()
        throws Exception {

        try {
            Transaction txn =
                store.getEnvironment().beginTransaction(null, null);
            return txn;
        } catch (DatabaseException DE) {
            System.out.println("Caught " + DE + " during beginTransaction");
            return null;
        }
    }

    private void abortTxn(Object txn)
        throws Exception {

        Transaction t = (Transaction) txn;

        try {
            t.abort();
        } catch (DatabaseException DE) {
            System.out.println("Caught " + DE + " during abort");
        }
    }

    private void commitTxn(Object txn)
        throws Exception {

        Transaction t = (Transaction) txn;

        try {
            if (testConfig.syncCommit) {
                t.commitSync();
            } else {
                t.commitNoSync();
            }
        } catch (DatabaseException DE) {
            System.out.println("Caught " + DE + " during abort");
        }
    }

    private void exitIfNotDeadlock(Exception e) {

        System.err.println("unexpected exception: " + e);
        e.printStackTrace();
        System.exit(EXIT_FAILURE);
    }

    private void databasePutNoOverwrite(Object txn)
        throws Exception {

        BasicEntity entity = new BasicEntity(keyNum);
        primaryIndex.putNoReturn((Transaction) txn, entity);
    }

    private void databaseGet(Object txn) throws Exception  {

        BasicEntity e = primaryIndex.get((Transaction) txn,
                                         new CompositeKey(keyNum),
                                         LockMode.DEFAULT);
        // System.out.println("Read entity = " + e);
    }

    private void databaseUpdate(Object txn) throws Exception {

        Transaction t = (Transaction) txn;
        BasicEntity entity = primaryIndex.get(t, new CompositeKey(keyNum),
                                              LockMode.DEFAULT);
        entity.modify();
        primaryIndex.putNoReturn((Transaction) txn, entity);
    }

    private int scan() throws Exception {

        int numRecords = 0;
        CursorConfig curConf = new CursorConfig();
        if (true) {
            curConf.setReadUncommitted(true);
            System.out.println("setting dirty read");
        }

        Object txn = null;
        if (true) {
            txn = beginTransaction();
        }

        EntityCursor cursor =
            primaryIndex.entities((Transaction) txn, curConf);
        try {
            for (BasicEntity e : cursor) {
                numRecords++;
            }
        } finally {
            cursor.close();
        }

        if (true) {
            commitTxn(txn);
        }

        System.out.println("scan records=" + numRecords);
        return numRecords;
    }

    private void databaseDelete(Object txn)
        throws Exception {

        primaryIndex.delete((Transaction) txn, new CompositeKey(keyNum));
    }
}

我的测试 – MyExample.java

import java.io.File;

import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.JEVersion;
import com.sleepycat.persist.*;

/**
 * 仅供参考,不作为官方申明!
 *
 * @author chao
 *
 */
public class MyExample {

    /* Global settings */
    protected static final int EXIT_SUCCESS = 0;
    protected static final int EXIT_FAILURE = 1;
    /* Variables */
    private com.sleepycat.je.Environment env;
    private Database db;
    private EntityStore store;
    private PrimaryIndex primaryIndex;
    TestConfig testConfig;

    public MyExample(String args[]) {
        testConfig = new TestConfig(args);
    }

    public static void main(String[] args) {
        MyExample example = new MyExample(args);
        example.execute();
    }

    /**
     * Print the usage.
     */
    public static void usage(String msg) {
        String usageStr;
        if (msg != null) {
            System.err.println(msg);
        }
        usageStr = "Usage: java MyExamplen"
                + " [-h ] [-preload] [-dirtyRead]"
                + " [-useTxns] [-syncCommit]"
                + " [-deferredWrite]n"
                + " [-numThreads ]n"
                + " [-itemsPerTxn ]n"
                + " [-cacheSize ]n"
                + " [-logFileSize ]n"
                + " [-numOperations ]n"
                + " [-operationType ]";
        System.err.println(usageStr);
    }

    /* Set up the test environment. */
    public void setup() throws Exception {
        /* Create a new, transactional database environment. */
        EnvironmentConfig envConfig = new EnvironmentConfig();
        envConfig.setAllowCreate(true);
        envConfig.setTransactional(testConfig.useTxns);
        envConfig.setTxnNoSync(!testConfig.syncCommit);
        envConfig.setTxnWriteNoSync(!testConfig.syncCommit);
        envConfig.setCacheSize(testConfig.cacheSize);
        envConfig.setConfigParam("je.log.fileMax",
                String.valueOf(testConfig.logFileSize));

        env = new Environment(new File(testConfig.envHome), envConfig);

        /* Open the entity store. */
        StoreConfig storeConfig = new StoreConfig();
        storeConfig.setAllowCreate(true);
        storeConfig.setTransactional(testConfig.useTxns);
        storeConfig.setDeferredWrite(testConfig.deferredWrite);

        store = new EntityStore(env, "thead", storeConfig);
        primaryIndex = store.getPrimaryIndex(CompositeKey.class, BasicEntity.class);

        /* Preload the database contents into cache. */
        if (testConfig.preload)
            preload();
    }

    /*
     * If the "-preload" flag is set, do a scan to bring the database into
     * memory first.
     */
    public void preload() throws Exception {

        EntityCursor entities = primaryIndex.entities();
        try {
            for (BasicEntity e : entities) {
            }
        } finally {
            entities.close();
        }
    }

    /* The execution method. */
    public void execute() {
        try {
            setup();

            /*
             * Fork off the threads to perform the transactions.
             */
            MyThread[] threads = new MyThread[testConfig.numThreads];
            for (int i = 0; i < testConfig.numThreads; i++) {
                threads[i] = createThread(i, store, primaryIndex, testConfig,
                        (testConfig.numOperations / testConfig.numThreads));
            }

            /* Start all threads here. */
            for (int i = 0; i < testConfig.numThreads; i++) {
                threads[i].start();
            }
            /* Wait for them to finish. */
            for (int i = 0; i < testConfig.numThreads; i++) {
                try {
                    threads[i].join();
                } catch (InterruptedException IE) {
                    System.err
                            .println("caught unexpected InterruptedException");
                    System.exit(EXIT_FAILURE);
                }
            }

            /* Close the database and environment. */
            close();
        } catch (Exception e) {
            System.err.println("TestJEDB: " + e);
            e.printStackTrace();
            System.exit(EXIT_FAILURE);
        }
    }

    public MyThread createThread(int threadId,
                                 EntityStore store,
                                 PrimaryIndex pIndex,
                                 TestConfig testConfig,
                                 int numOps) {

        return new MyThread(threadId, store, pIndex, testConfig, numOps);
    }

    public void close() throws Exception {

        try {
            if (store != null) {
                store.close();
            }

            if (env != null) {
                env.close();
            }
        } catch (DatabaseException DE) {
            System.err.println("Caught " + DE + " while closing env and store");
        }
    }

    /**
     * Parses and contains all test properties.
     */
    static class TestConfig {
        /* Use dirty reads. */
        static boolean dirtyRead = false;
        /* Preload records into memory by doing a scan before the test. */
        static boolean preload = false;
        /* Use the deferred write mode to speed up. */
        static boolean deferredWrite = false;
        /* Use synchronous commit. */
        static boolean syncCommit = false;
        /* Use transactions. */
        static boolean useTxns = false;
        /* Number of threads to use. */
        static int numThreads = 1;
        /* Number of items accessed per txn. */
        static int itemsPerTxn = 50;
        /* Cache size, default is 100M. */
        static long cacheSize = (200 << 20);
        /* JE's Log file size, default is 10M. */
        static long logFileSize = (10 << 20);
        /* Environment home. */
        static String envHome = "./tmp";

        /* Operations per test phase. */
        // static int numOperations = (1 << 20); // default to 1 million.
        static int numOperations = 1000000; // default to 1 million.
        /* Operation types include: INSERT, READ, SCAN, DELETE and UPDATE */
        static String operationType = "READ";
        /* Save command-line input arguments. */
        static StringBuffer inputArgs = new StringBuffer();

        private TestConfig(String args[]) {

            if (args.length < 2) {
                usage(null);
                System.exit(EXIT_FAILURE);
            }

            try {
                /* Parse command-line input arguments. */
                for (int i = 0; i < args.length; i++) {
                    String arg = args[i];
                    boolean moreArgs = i < args.length - 1;
                    if (arg.equals("-h") && moreArgs) {
                        envHome = args[++i];
                    } else if (arg.equals("-dirtyRead")) {
                        dirtyRead = true;
                    } else if (arg.equals("-preload")) {
                        preload = true;
                    } else if (arg.equals("-deferredWrite")) {
                        deferredWrite = true;
                    } else if (arg.equals("-syncCommit")) {
                        syncCommit = true;
                    } else if (arg.equals("-useTxns")) {
                        useTxns = true;
                    } else if (arg.equals("-numThreads") && moreArgs) {
                        numThreads = Integer.parseInt(args[++i]);
                    } else if (arg.equals("-itemsPerTxn") && moreArgs) {
                        itemsPerTxn = Integer.parseInt(args[++i]);
                    } else if (arg.equals("-cacheSize") && moreArgs) {
                        cacheSize = Long.parseLong(args[++i]);
                    } else if (arg.equals("-logFileSize") && moreArgs) {
                        logFileSize = Long.parseLong(args[++i]);
                    } else if (arg.equals("-numOperations") && moreArgs) {
                        numOperations = Integer.parseInt(args[++i]);
                    } else if (arg.equals("-operationType") && moreArgs) {
                        operationType = args[++i];
                    } else if (arg.equals("-help")) {
                        usage(null);
                        System.exit(EXIT_SUCCESS);
                    } else {
                        usage("Unknown arg: " + arg);
                        System.exit(EXIT_FAILURE);
                    }
                }

                /* Save command-line input arguments. */
                for (String s : args) {
                    inputArgs.append(" " + s);
                }
                inputArgs.append(" je.version=" + JEVersion.CURRENT_VERSION);
                System.out.println("nCommand-line input arguments:n "
                        + inputArgs);
                System.out.println("Test configurations:nt" + this + "n");
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(EXIT_FAILURE);
            }
        }

        @Override
        public String toString() {
            return "TestConfig={ " +
                   " dirtyRead=" + dirtyRead +
                   " preload=" + preload +
                   " deferredWrite=" + deferredWrite +
                   " syncCommit=" + syncCommit +
                   " useTxns=" + useTxns +
                   " numberThreads=" + numThreads +
                   " numOperations=" + numOperations +
                   " operationType=" + operationType +                   " itemsPerTxn=" + itemsPerTxn +
                   " logFileSize=" + logFileSize +
                   " cacheSize=" + cacheSize +
                   " envHome=" + envHome + " }";
        }
    }
}

我的测试结果
* $ java -Xmx192M -cp je.jar:. MyExample -h ./tmp -operationType INSERT
Executed 1048576 INSERT ops in 22734 ms.

* $ java -Xmx192M -cp je.jar:. MyExample -h ./tmp -operationType READ # (an out of cache read test)
Executed 1048576 READ ops in 26025 ms.

* $ java -Xmx1024M -cp je.jar:. MyExample -h ./tmp -preload -operationType READ -cacheSize 512000000 # (a in cache read test)
Executed 1048576 READ ops in 9130 ms.

重要备注:
1. 如果在上面的例子程序中,将entity增加多个Secondary Index,并用多个线程进行读写操作,会出现死锁并且是不能避免的。原因如下:
Be aware that if you are using secondary databases (indexes), then locking order is different for reading and writing. For this reason, if you are writing a concurrent application and you are using secondary databases, you should expect deadlocks.

2. 你可以参考JE关于Locks,Deadlocks那个章节的官方文档,从而来针对性的解决或者处理你程序中的死锁异常。链接地址为:http://www.oracle.com/technology/documentation/berkeley-db/je/TransactionGettingStarted/blocking_deadlocks.html。

3. 至于如何来针对性的解决或者处理死锁异常,请参考SUN的工程师(Jeff)的博客。链接地址:http://forums.oracle.com/forums/thread.jspa?threadID=992240&tstart=0。

4. 如果您还有进一步的问题,欢迎反馈到JE的官方论坛:http://forums.oracle.com/forums/forum.jspa?forumID=273&start=0。

  1. xinguozhong
    2009年9月22日10:52 | #1

    非常感谢黄超先生写的例子,赞一个

  2. xinguozhong
    2009年9月22日17:32 | #2

    这个例子中我运行了,性能确实比我用java调用c版本的bdb有提高。发现几个问题:
    1.本例中应该没有启用replication,如果启用replication是不是要自己些binding编解码类?
    2.我看见生成的数据库文件应该是和log混在一起了,但是envconfig中没有找到设置数据库文件大小的代码,每个数据库文件size是9.5兆这个对效率可能有影响。
    3.发现如果setTxnSync(True)的话效率急剧降低到3k每秒。

  3. chaohuang
    2009年9月22日18:30 | #3

    @xinguozhong
    1. 你确定需要使用集群(replication)吗?你是打算用几台机器做集群?
    2. JE的数据和log是放在同一个文件中的(*.jdb),这一点和C版本的BDB不同。每个jdb文件大小默认是10M,一般来说使用10M这个大小是最优的。否则,当cleaner 线程对每个jdb文件进行垃圾回收的时候,较大的文件意味着长的延迟。
    3. 请问你改动了些什么?用什么版本的JE?我设置为setTxnSync(True),插入1百万的记录耗时30767 ms。如下:
    $ java -Xmx192M -cp je.jar:. MyExample -h db -numThreads 1 -numOperations 1000000 -operationType INSERT

    Command-line input arguments:
    -h db -numThreads 1 -numOperations 1000000 -operationType INSERT je.version=4.0.15
    Access method thread: 0 started.
    Access method thread 0 exiting cleanly. Executed 1000000 INSERT ops in 30767 ms.
    - java version “1.6.0_14″,使用的是我的Linux台式机。

  4. xinguozhong
    2009年9月23日09:32 | #4

    这个blog说实话真的太慢了。。。。。

  5. xinguozhong
    2009年9月23日09:37 | #5

    1.集群还不确定,我们项目也没那么大,可是以后可能会用,以后能不能方便的引入集群,我比较关心。
    2.好的明白了。
    3. 代码中我看到了是强制commitNoSync提交。
    private void commitTxn(Object txn)
    throws Exception {

    Transaction t = (Transaction) txn;

    try {
    if (false) {
    t.commitSync();
    } else {
    t.commitNoSync();
    }
    } catch (DatabaseException DE) {
    System.out.println(”Caught ” + DE + ” during abort”);
    }
    }
    在setup()函数中我看到了envConfig.setTxnNoSync(!testConfig.syncCommit);
    而syncCommit默认值=true

  6. chaohuang
    2009年9月23日13:46 | #6

    @xinguozhong
    1. 可以集群。
    2. 当JE创建新的jdb文件时,不影响性能 – 对应用程序的所有增/删/改操作是没有影响的。
    3. 我上面的测试是在syncCommit模式下,单线程的运行结果 (31秒插入100万记录数):
    * envConfig.setTxnNoSync(false)
    * if (true) { t.commitSync(); }

  7. xinguozhong
    2009年9月23日14:05 | #7

    hi Mr.黄
    如果能达到单线程31秒100万,对我们性能没有问题了。

    不过您对于第三点解释,怎么和我看到代码是相反的?

    > if (false) {
    > t.commitSync();
    > } else {
    > t.commitNoSync();
    > }

    我上面回复中贴的您的commitTxn函数和您刚才的回复,怎么恰恰相反,还是我理解错了,困惑了。

  8. chaohuang
    2009年9月23日14:52 | #8

    @xinguozhong
    - 我把之前那个program修改过了(从而以syncCommit模式来运行),具体修改2处地方,如下:

    * envConfig.setTxnNoSync(false)
    * if (true) { t.commitSync(); }

    - 如果你只能达到3000writes/sec, 是不是你测试机器I/O有什么问题呢?使用的是SAN/NAS吗?

  9. xinguozhong
    2009年9月23日15:29 | #9

    受到3x a lot~~~

  10. owen
    2009年10月12日19:01 | #10

    C版和php版本的BDB在插入时 当连续插入数比较多的时候就会出现间歇性龟速。大概条数是百万量级,分布为4个BDB文件后仍会出现。加了flush依然如故。这个问题是什么原因?

  11. David
    2009年10月13日15:38 | #11

    cache 满了以后,插入会比较慢。你可以增加一个trickle线程,让它定期调用DB_ENV->memp_trickle来腾出一部分cache 空间。这样,就可以较小性能抖动。

  12. xinguozhong
    2009年10月14日09:48 | #12

    own我你使用c版bdb插入,是什么性能?什么配置? 可否分享下?
    我使用的c版本是 用java调用bdb-c的api。在setTxnWriteNoSync(true)模式。 key=8字符 value=32字符的字符串能达到 1.4w/s 但是java对象再加上二级数据库 就下降到2.4k/s 如果用setTxnNoSync(true)性能又会提升很多。 目前bdb-je貌似在txSync和txNotSync性能区别不大。

  13. xinguozhong
    2009年10月14日09:51 | #13

    上面说的bdb-je txSync
    性能差距不大可能是我自己代码问题,我再查看下。

  14. hallenzzz
    2009年10月15日01:24 | #14

    貌似完全不支持多核啊?这是JAVA本身的问题吗?无论如何我双核都是两个50%的负载

  15. chaohuang
    2009年10月15日12:06 | #15

    @hallenzzz
    如果CPU负载一直是50%,你查看过IO Wait是多少?我个人觉得,你的瓶颈有可能在IO,所以CPU未能完全利用上。

    你是怎么样来测试的?用的是我的程序吗?几个线程?

  16. hallenzzz
    2009年10月15日17:37 | #16

    是用你的程序,我用的是RAM DISK,就是把内存虚拟成磁盘,所以IO等待几乎可以无视了吧,开始是用单线程,很明显看出写入时的瓶颈在CPU上,双核都是50%,于是我考虑应该是J2SE6.0对多核的支持不好。接着开始测试多线程写入,写同一个类的对象,此时双核都是100%了,不过性能没有提高,反而降低了。我当时认为是锁的原因,于是改成两个线程同时写两个不同类的对象,情况依旧,这就不太对了,因为这两个类之间没有任何关系啊。
    另外,我也试了用硬盘做存储介质,没有任何提高,最快情况下两种介质差不多都是70000/s的写入速度。

  17. chaohuang
    2009年10月15日18:51 | #17

    没明白你到底想说什么 – 是JE不好还是JDK不好?

    我一再申明:我的程序只是做参考,不是benchmark。你要CPU达到100%,程序也不是这么写呀。另外,你是在什么机器配置下达到70k inserts/sec? 是设置了几个线程?

  18. hallenzzz
    2009年10月15日22:59 | #18

    我的配置是AMD4000+,3G内存,RAM DISK,按照论坛上的提示来优化性能,主要就是保证在程序运行过程中没有checkpoint,只有单线程才能达到最高的速度。按照我的理解,BDB应该是基于HASH的,当数据量不大而能全部进内存的时候,BDB应该是最快的数据库select和update引擎。也因为HASH的原因,在insert的时候很耗CPU,这里又出现一个问题,就是一直到JDK6都对并行计算支持得很不好,尤其是多核CPU,在主流的2*4核的服务器上跑JAVA程序很尴尬的。总而言之,BDB应该是很耗CPU的,而JE版更是无法发挥多核服务器应有的性能,这是件很让人遗憾的事情,如果有可能的话,希望能做一些优化,我认为理论上写入速度还有很大提升空间。

  19. chaohuang
    2009年10月16日11:38 | #19

    首先,这个blog是讲BDB-JE的,而非BDB(BDB和BDB-JE是完全不同的2个产品)。我假设你上面是说的BDB-JE,对吗?

    1. 如果要没有checkpoint – 你需要在程序中disable JE的checkpoint线程,像这样:envConfig.setConfigParam (EnvironmentParams.ENV_RUN_CHECKPOINTER.getName(), “false”);
    2. 即使没有disk I/O,CPU和memory的速度也不一致,也会有wait。
    3. JE是基于BTree的,不是hash。据我所知,JE对并发和多线程的支持是很好的。我看到我们的客户在8 CPU * 2 核心, 128G memory, JDK6上使用几年都很好。当然,这个还不是JE运行的最大的机器。
    4. 你要测试的是JE单线程下的极限速度,且是in memory的。那么你可以考虑:
    * 使用deferredWrite 或者temporary选项
    * 设置一个大的JVM heap 和较大的JE cache
    * 调优你的JVM
    * 调优JE的参数(如加大JE’s log file size,缺省是10M,可以尝试1G). 可以参考:http://forums.oracle.com/forums/thread.jspa?threadID=971792&tstart=0
    * 使用*nix平台,一般来说Windows要慢一些。
    5. 我那个程序不是为极限测试所写的。如果你能将JE性能进一步优化,并能分享你的经验的话,我愿意提供相应支持(可能回复会比较慢,我还有其他工作)。有进一步的问题,你可以发到我邮箱。

  20. alanye
    2009年10月19日21:40 | #20

    @chaohuang
    黄超您好,我是初学BDB-JE,正在试图把海量视频中的帧信息存储在BDB中,但是在put过程中发现写入速度越来越慢,不知道是什么问题,或许是Index的关系?请问黄老师的邮箱,能否直接邮件联系您?

  21. chaohuang
    2009年10月20日11:32 | #21

    @alanye
    Hi alanye,

    我接下来会忙校园招聘,所以估计没有时间及时回复你。最及时的办法是:你可以将你的问题发到BDB-JE的官方论坛(见本站右侧边栏的链接)。我的email是chao.huang @ o.com (o -> oracle).

  22. gaohui
    2009年10月21日13:58 | #22

    您好,请问,Berkeley DB的java版本里面在同一个environment下能不能为每一个database指定相应的数据库文件呢,或者是否可以设置每个database分别对应一个文件呢

  23. chaohuang
    2009年10月21日15:13 | #23

    @gaohui
    在JE里,每个environment对应一个磁盘目录(数据和日志文件的混合存放在该目录下,以*.jdb格式)。在同一个JVM里面,可以设置多个environment;每个environment可以包含多个database。

    JE 不支持一个database对应一个(或者若干)jdb文件,事实上也没有必要。当你在程序里面打开environment后,你完全可以通过databaseName来访问和操作对应的database。

    不知道你的应用场景是什么?为什么需要这样做呢 – 一个database对应一个(或者若干)jdb文件?

  24. gaohui
    2009年10月21日15:32 | #24

    恩,据我的同事介绍,php版本的可以在打开数据库的时候就创建一个相应的数据文件,不知是否属实。
    是这样的,我现在要放在je里面大量的数据,为了减小je的压力,想到了多数据库文件存储,于是,我把数据放在同一个environment下,并且分为了256个database,但是发现所有的database的数据是放在同一个jdb中的,当数据量特别大了之后,我担心数据的操作会变慢。

  25. gaohui
    2009年10月21日16:11 | #25

    @chaohuang
    请问如果数据量特别大了之后,je这种文件结构对于数据的操作效率是否会降低呢?

  26. chaohuang
    2009年10月21日16:43 | #26

    @gaohui
    没有必要 – 分为了256个database。也不用担心 – 当数据量特别大了之后,我担心数据的操作会变慢。

    你的数据量有多大?一般来说,数据量大(意味着jdb或比较多),并不意味着慢。只要你的I/O是好的,不是瓶颈。数据量小,速度快是因为数据都cache在内存;数据量大,只要你的应用设计合理(如降低并发更新的热块、减少全表扫描等),I/O分布合理,不觉得会性能很差。

    实际上JE是用B+树(索引)来组织管理数据的,我们看到很多客户在生产环境用JE管理几百GB、若干TB的数据量,并且用的很好。我们也有看到用JE来管理几百TB数据的案例,如archive.org.

    你的数据量多大?

  27. gaohui
    2009年10月22日09:51 | #27

    @chaohuang
    大概有50亿条记录吧,如果不分为256个数据库,就放在一个数据库里面,je的效率能行么

  28. chaohuang
    2009年10月22日10:20 | #28

    @gaohui

    如果你只是觉得50亿条很大的话,因此需要分为256个database,我觉得不尽然。如果你能提供更多的、更具体的信息(比如你的data structure,平台等信息),我们愿意提供建议。你可以给我发Email,或者发到JE论坛。

    另外,我建议每个评估JE的用户,请仔细读一下这个讨论http://forums.oracle.com/forums/thread.jspa?forumID=273&threadID=974400。我肯定你会发现它有用的!

  29. hjhong
    2009年11月4日16:21 | #29

    chao huang,您好!
    最近看了您的关于BDB JE的几篇博客,对我的帮助很大,有几个问题想咨询一下,如果您不忙的话,能否帮忙回复一下,不胜感谢!
    1.由于BDB JE的数据存储在文件里,目前没有可视化的软件来管理数据(如oralce有plsql、toad等工具),维护起来不是很方便,因此我打算用bdb与oracle结合一起使用,即数据在2个数据库都保存,你看是否可行?
    2.如果bdb与oracle一起使用就需要考虑怎么将bdb的数据同步到oracle中去?不知你有什么好的建议?是否有这样的案例?
    3.由于对性能的要求很高,需要采用集群的方式来发布,那么在没有使用磁盘阵列的情况下,如何保证2台bdb服务器之间的数据文件同步呢?

  30. chaohuang
    2009年11月4日18:34 | #30

    @hjhong
    Hi hjhong,

    关于1,BDB-JE提供了一些命令行的工具,如使用DbDump可以将BDB-JE的数据库以文本格式导出(参考Getting Started Guide 文档); 你还可以自己写一段小程序,用cursor等遍历(或者查询)数据库内容 – 具体可以参考我的BDB-JE SQL查询的白皮书和例子(见本博客中的链接)。至于Oracle PL/SQL 或者Toad等GUI的工具,如果你有兴趣,你也可以为JE来写一个呀?做为练手,我的同事haomian.wang就在JE上做了一个GUI 的visualization的小程序,非常有意思。我想你也可以动手试试的。

    2. 有很多。一般来说,都是自己写一些小程序就可以达到; 或者用JE的DbDump -> Oracle sql loader/imp等。

    3. 在即将发布的JE 4.0里面,我们提供了集群选项: 在异构的机器节点/环境中,不需要多机共享的storage也可以做集群。如果你有兴趣,可以和我email联系。不知道你的性能要求有多高? 什么机器配置?

  31. hjhong
    2009年11月6日16:18 | #31

    chao huang,您好!
    非常感谢你的回复,目前我们系统的性能要求是具体如下:
    1、用户数3000万左右
    2、支持的并发连接数为 5000 个连接以上
    3、支持的账户处理能力为 5000次/秒以上
    4、数据查询处理时间平均小于 3 ms,最大小于20 ms;数据更新处
    理时间平均小于 10 ms,最大小于 30 ms

  32. chaohuang
    2009年11月6日16:29 | #32

    @hjhong
    我觉得单个节点的JE应该可以了,当然,你需要一台强劲一点的机器。建议你使用线程池、请求队列等办法来降低线程数(5000个链接不意味着要5000个并发线程)。如果你遇到具体的问题,欢迎和我email交流。当然,你也可以把问题发去JE的论坛。

  33. hjhong
    2009年11月6日16:30 | #33

    chao huang,您好!
    目前实际的机器配置我这里还没拿到,只有测试机器的配置,
    测试机器采用的sun的T2000,
    cpu是8核的,频率不太记得了,好像是3.0G以上吧
    内存是32G的,JDK采用1.6,共2台,只部署了bdb je。
    另外我用你提到的DbDump做了下测试,发现DbDump是做全量备份的,这样的话我假设一个用户有10个业务数据,那3000万大概是3亿的数据量,估计有几十个G的大小,这样备份不是会很慢吗?

  34. chaohuang
    2009年11月6日16:38 | #34

    @hjhong
    机器应该可以的。关于备份:
    - 第一选择是DbBackup:http://www.oracle.com/technology/documentation/berkeley-db/je/java/com/sleepycat/je/util/DbBackup.html
    - DbDump以及备份*.jdb文件都可以用来做全备份和环境迁移。
    - 像我的SQL的例子一样,自己写一段简单的程序,把查询出来的数据:1)保存进文件 2)插入到新库从而实现全量或者增量备份。个人觉得这种办法最灵活。

  35. hjhong
    2009年11月6日16:49 | #35

    chao huang,您好!
    不知道测试机器的配置能否达到这个性能要求呢?实际配置估计会比测试机器要高,2台服务器之间使用F5做负载均衡,采用磁盘阵列来保存bdb的数据文件(这样是否能解决2台服务器之间的数据同步?),我想问下你,bdb支持多个进程同时写入数据吗?我上次测试时发现我一个进程的配置为可写,我再把这个进程启动一次bdb就会报错,改成可读就不会报错,不知道是不是我配置的不对?

  36. chaohuang
    2009年11月6日17:00 | #36

    @hjhong
    性能依赖于很多因素 – 数据结构、算法、应用设计的好很关键。

    BDB和BDB-JE不是一回事,前者是C语言实现的,后者是纯Java语言实现。BDB
    支持多个进程或者多个线程并发读写,BDB-JE只支持多个线程并发读写。我建议你2个产品都试试,找一个最符合你业务需要的产品。

  37. hjhong
    2009年11月6日17:27 | #37

    chao huang,
    非常感谢,前面是我写错了,应该是bdb je,由于要考虑灾难性,所以单机肯定是不行的,如果BDB-JE只支持多线程的读写而不支持多进程的读写的话,那我们2台机器就只能采用HA的方式而不能采用负载均衡的方式了,另外你前面讲的SQL的例子有链接吗?是否就是指<>这份文档?

  38. hjhong
    2009年11月6日17:29 | #38

    文档名怎么没显示:Performing Queries in Oracle Berkeley DB Java Edition

  39. hjhong
    2009年11月9日14:43 | #39

    chao huang,
    我向你的邮箱chao.huang@o.com发的信怎么都被退回来了!

  40. chaohuang
    2009年11月9日14:45 | #40

    把o换成oracle。

  41. 胡大云
    2009年12月16日21:36 | #41

    我们在编程的时候发现使用BDB的C API时,在两个进程使用同一个BDB文件然后依次间隔开始写入时,发现第二个启动后,第一个会在写入时报错
    Berkeley DB error: PANIC: fatal region error detected; run recovery
    然后就不能写入,初始化时使用了TXN和recover 线程标志
    这个问题怎么解决

  42. Emily Fu
    2009年12月18日14:03 | #42

    你好,请问你启动的两个进程的环境分别是如何设置的?

  43. 2009年12月19日11:35 | #43

    你好,
    请问如果用je打开C语言写好的BDB文件?

  44. 2009年12月19日11:39 | #44

    你好,
    请问如果用je打开C语言写好的BDB文件?

    xinguozhong :
    这个例子中我运行了,性能确实比我用java调用c版本的bdb有提高。发现几个问题:
    1.本例中应该没有启用replication,如果启用replication是不是要自己些binding编解码类?
    2.我看见生成的数据库文件应该是和log混在一起了,但是envconfig中没有找到设置数据库文件大小的代码,每个数据库文件size是9.5兆这个对效率可能有影响。
    3.发现如果setTxnSync(True)的话效率急剧降低到3k每秒。

    如何配置java调用C版本的bdb?

  45. 胡大云
    2009年12月22日23:13 | #45

    是这样,我们使用了两个进程去访问BDB,设置选项我没带回来,但是首先BDB能提供两个或两个以上进程同时访问吗?

  46. 胡大运
    2009年12月22日23:22 | #46

    设置的是DB_CREATE |DB_MPOOL |DB_ININ_TXN |DB_RECOVER|DB_INIT_LOCK标志@Emily Fu

  47. Emily Fu
  48. Emily Fu
    2009年12月23日11:43 | #48

    当两个进程的环境都设置为DB_RECOVER时,第二个进程恢复数据库时,第一个进程试图往遭到破坏的数据库(正在恢复)写入/读取数据。因此,出现你所描述的问题。因此,在多进程的场景中,你应该只能在第一个环境中设置其为DB_RECOVER。具体亦请参考上一条评论的链接。

  49. Henry
    2010年1月1日10:08 | #49

    请问下为什么我使用subIndex进行大数据量的查询时第一次用的时间特别久.20万笔要50秒,但再查询同样的Key就只要2秒了.谢谢.

  50. chaohuang
    2010年1月6日11:01 | #50

    @Henry
    可能是你第一个查询的数据不在cache中,时间耗费在IO;而第二次操作时,因为数据都cache在内存,所以比较快。你可以对第一个查询做一些profiling后做一些分析。

    如果你要调优(假设你用的是BDB-JE),我建议你运行一下DbCacheSize (http://www.oracle.com/technology/documentation/berkeley-db/je/java/com/sleepycat/je/util/DbCacheSize.html), 得到一个具体cache设置建议后,相应设置你的JVM的Heap size和JE的cache size。JE缺省使用60%的JVM Heap (即-Xmx的值)。

评论分页
1 2 580
本文的评论功能被关闭了.
Դ