Berkeley DB Java版性能测试
初衷
最近有很多朋友问到关于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。
非常感谢黄超先生写的例子,赞一个
这个例子中我运行了,性能确实比我用java调用c版本的bdb有提高。发现几个问题:
1.本例中应该没有启用replication,如果启用replication是不是要自己些binding编解码类?
2.我看见生成的数据库文件应该是和log混在一起了,但是envconfig中没有找到设置数据库文件大小的代码,每个数据库文件size是9.5兆这个对效率可能有影响。
3.发现如果setTxnSync(True)的话效率急剧降低到3k每秒。
@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台式机。
这个blog说实话真的太慢了。。。。。
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
@xinguozhong
1. 可以集群。
2. 当JE创建新的jdb文件时,不影响性能 – 对应用程序的所有增/删/改操作是没有影响的。
3. 我上面的测试是在syncCommit模式下,单线程的运行结果 (31秒插入100万记录数):
* envConfig.setTxnNoSync(false)
* if (true) { t.commitSync(); }
hi Mr.黄
如果能达到单线程31秒100万,对我们性能没有问题了。
不过您对于第三点解释,怎么和我看到代码是相反的?
> if (false) {
> t.commitSync();
> } else {
> t.commitNoSync();
> }
我上面回复中贴的您的commitTxn函数和您刚才的回复,怎么恰恰相反,还是我理解错了,困惑了。
@xinguozhong
- 我把之前那个program修改过了(从而以syncCommit模式来运行),具体修改2处地方,如下:
* envConfig.setTxnNoSync(false)
* if (true) { t.commitSync(); }
- 如果你只能达到3000writes/sec, 是不是你测试机器I/O有什么问题呢?使用的是SAN/NAS吗?
受到3x a lot~~~
C版和php版本的BDB在插入时 当连续插入数比较多的时候就会出现间歇性龟速。大概条数是百万量级,分布为4个BDB文件后仍会出现。加了flush依然如故。这个问题是什么原因?
cache 满了以后,插入会比较慢。你可以增加一个trickle线程,让它定期调用DB_ENV->memp_trickle来腾出一部分cache 空间。这样,就可以较小性能抖动。
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性能区别不大。
上面说的bdb-je txSync
性能差距不大可能是我自己代码问题,我再查看下。
貌似完全不支持多核啊?这是JAVA本身的问题吗?无论如何我双核都是两个50%的负载
@hallenzzz
如果CPU负载一直是50%,你查看过IO Wait是多少?我个人觉得,你的瓶颈有可能在IO,所以CPU未能完全利用上。
你是怎么样来测试的?用的是我的程序吗?几个线程?
是用你的程序,我用的是RAM DISK,就是把内存虚拟成磁盘,所以IO等待几乎可以无视了吧,开始是用单线程,很明显看出写入时的瓶颈在CPU上,双核都是50%,于是我考虑应该是J2SE6.0对多核的支持不好。接着开始测试多线程写入,写同一个类的对象,此时双核都是100%了,不过性能没有提高,反而降低了。我当时认为是锁的原因,于是改成两个线程同时写两个不同类的对象,情况依旧,这就不太对了,因为这两个类之间没有任何关系啊。
另外,我也试了用硬盘做存储介质,没有任何提高,最快情况下两种介质差不多都是70000/s的写入速度。
没明白你到底想说什么 – 是JE不好还是JDK不好?
我一再申明:我的程序只是做参考,不是benchmark。你要CPU达到100%,程序也不是这么写呀。另外,你是在什么机器配置下达到70k inserts/sec? 是设置了几个线程?
我的配置是AMD4000+,3G内存,RAM DISK,按照论坛上的提示来优化性能,主要就是保证在程序运行过程中没有checkpoint,只有单线程才能达到最高的速度。按照我的理解,BDB应该是基于HASH的,当数据量不大而能全部进内存的时候,BDB应该是最快的数据库select和update引擎。也因为HASH的原因,在insert的时候很耗CPU,这里又出现一个问题,就是一直到JDK6都对并行计算支持得很不好,尤其是多核CPU,在主流的2*4核的服务器上跑JAVA程序很尴尬的。总而言之,BDB应该是很耗CPU的,而JE版更是无法发挥多核服务器应有的性能,这是件很让人遗憾的事情,如果有可能的话,希望能做一些优化,我认为理论上写入速度还有很大提升空间。
首先,这个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性能进一步优化,并能分享你的经验的话,我愿意提供相应支持(可能回复会比较慢,我还有其他工作)。有进一步的问题,你可以发到我邮箱。
@chaohuang
黄超您好,我是初学BDB-JE,正在试图把海量视频中的帧信息存储在BDB中,但是在put过程中发现写入速度越来越慢,不知道是什么问题,或许是Index的关系?请问黄老师的邮箱,能否直接邮件联系您?
@alanye
Hi alanye,
我接下来会忙校园招聘,所以估计没有时间及时回复你。最及时的办法是:你可以将你的问题发到BDB-JE的官方论坛(见本站右侧边栏的链接)。我的email是chao.huang @ o.com (o -> oracle).
您好,请问,Berkeley DB的java版本里面在同一个environment下能不能为每一个database指定相应的数据库文件呢,或者是否可以设置每个database分别对应一个文件呢
@gaohui
在JE里,每个environment对应一个磁盘目录(数据和日志文件的混合存放在该目录下,以*.jdb格式)。在同一个JVM里面,可以设置多个environment;每个environment可以包含多个database。
JE 不支持一个database对应一个(或者若干)jdb文件,事实上也没有必要。当你在程序里面打开environment后,你完全可以通过databaseName来访问和操作对应的database。
不知道你的应用场景是什么?为什么需要这样做呢 – 一个database对应一个(或者若干)jdb文件?
恩,据我的同事介绍,php版本的可以在打开数据库的时候就创建一个相应的数据文件,不知是否属实。
是这样的,我现在要放在je里面大量的数据,为了减小je的压力,想到了多数据库文件存储,于是,我把数据放在同一个environment下,并且分为了256个database,但是发现所有的database的数据是放在同一个jdb中的,当数据量特别大了之后,我担心数据的操作会变慢。
@chaohuang
请问如果数据量特别大了之后,je这种文件结构对于数据的操作效率是否会降低呢?
@gaohui
没有必要 – 分为了256个database。也不用担心 – 当数据量特别大了之后,我担心数据的操作会变慢。
你的数据量有多大?一般来说,数据量大(意味着jdb或比较多),并不意味着慢。只要你的I/O是好的,不是瓶颈。数据量小,速度快是因为数据都cache在内存;数据量大,只要你的应用设计合理(如降低并发更新的热块、减少全表扫描等),I/O分布合理,不觉得会性能很差。
实际上JE是用B+树(索引)来组织管理数据的,我们看到很多客户在生产环境用JE管理几百GB、若干TB的数据量,并且用的很好。我们也有看到用JE来管理几百TB数据的案例,如archive.org.
你的数据量多大?
@chaohuang
大概有50亿条记录吧,如果不分为256个数据库,就放在一个数据库里面,je的效率能行么
@gaohui
如果你只是觉得50亿条很大的话,因此需要分为256个database,我觉得不尽然。如果你能提供更多的、更具体的信息(比如你的data structure,平台等信息),我们愿意提供建议。你可以给我发Email,或者发到JE论坛。
另外,我建议每个评估JE的用户,请仔细读一下这个讨论 – http://forums.oracle.com/forums/thread.jspa?forumID=273&threadID=974400。我肯定你会发现它有用的!
chao huang,您好!
最近看了您的关于BDB JE的几篇博客,对我的帮助很大,有几个问题想咨询一下,如果您不忙的话,能否帮忙回复一下,不胜感谢!
1.由于BDB JE的数据存储在文件里,目前没有可视化的软件来管理数据(如oralce有plsql、toad等工具),维护起来不是很方便,因此我打算用bdb与oracle结合一起使用,即数据在2个数据库都保存,你看是否可行?
2.如果bdb与oracle一起使用就需要考虑怎么将bdb的数据同步到oracle中去?不知你有什么好的建议?是否有这样的案例?
3.由于对性能的要求很高,需要采用集群的方式来发布,那么在没有使用磁盘阵列的情况下,如何保证2台bdb服务器之间的数据文件同步呢?
@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联系。不知道你的性能要求有多高? 什么机器配置?
chao huang,您好!
非常感谢你的回复,目前我们系统的性能要求是具体如下:
1、用户数3000万左右
2、支持的并发连接数为 5000 个连接以上
3、支持的账户处理能力为 5000次/秒以上
4、数据查询处理时间平均小于 3 ms,最大小于20 ms;数据更新处
理时间平均小于 10 ms,最大小于 30 ms
@hjhong
我觉得单个节点的JE应该可以了,当然,你需要一台强劲一点的机器。建议你使用线程池、请求队列等办法来降低线程数(5000个链接不意味着要5000个并发线程)。如果你遇到具体的问题,欢迎和我email交流。当然,你也可以把问题发去JE的论坛。
chao huang,您好!
目前实际的机器配置我这里还没拿到,只有测试机器的配置,
测试机器采用的sun的T2000,
cpu是8核的,频率不太记得了,好像是3.0G以上吧
内存是32G的,JDK采用1.6,共2台,只部署了bdb je。
另外我用你提到的DbDump做了下测试,发现DbDump是做全量备份的,这样的话我假设一个用户有10个业务数据,那3000万大概是3亿的数据量,估计有几十个G的大小,这样备份不是会很慢吗?
@hjhong
机器应该可以的。关于备份:
- 第一选择是DbBackup:http://www.oracle.com/technology/documentation/berkeley-db/je/java/com/sleepycat/je/util/DbBackup.html
- DbDump以及备份*.jdb文件都可以用来做全备份和环境迁移。
- 像我的SQL的例子一样,自己写一段简单的程序,把查询出来的数据:1)保存进文件 2)插入到新库从而实现全量或者增量备份。个人觉得这种办法最灵活。
chao huang,您好!
不知道测试机器的配置能否达到这个性能要求呢?实际配置估计会比测试机器要高,2台服务器之间使用F5做负载均衡,采用磁盘阵列来保存bdb的数据文件(这样是否能解决2台服务器之间的数据同步?),我想问下你,bdb支持多个进程同时写入数据吗?我上次测试时发现我一个进程的配置为可写,我再把这个进程启动一次bdb就会报错,改成可读就不会报错,不知道是不是我配置的不对?
@hjhong
性能依赖于很多因素 – 数据结构、算法、应用设计的好很关键。
BDB和BDB-JE不是一回事,前者是C语言实现的,后者是纯Java语言实现。BDB
支持多个进程或者多个线程并发读写,BDB-JE只支持多个线程并发读写。我建议你2个产品都试试,找一个最符合你业务需要的产品。
chao huang,
非常感谢,前面是我写错了,应该是bdb je,由于要考虑灾难性,所以单机肯定是不行的,如果BDB-JE只支持多线程的读写而不支持多进程的读写的话,那我们2台机器就只能采用HA的方式而不能采用负载均衡的方式了,另外你前面讲的SQL的例子有链接吗?是否就是指<>这份文档?
文档名怎么没显示:Performing Queries in Oracle Berkeley DB Java Edition
chao huang,
我向你的邮箱chao.huang@o.com发的信怎么都被退回来了!
把o换成oracle。
我们在编程的时候发现使用BDB的C API时,在两个进程使用同一个BDB文件然后依次间隔开始写入时,发现第二个启动后,第一个会在写入时报错
Berkeley DB error: PANIC: fatal region error detected; run recovery
然后就不能写入,初始化时使用了TXN和recover 线程标志
这个问题怎么解决
你好,请问你启动的两个进程的环境分别是如何设置的?
你好,
请问如果用je打开C语言写好的BDB文件?
你好,
请问如果用je打开C语言写好的BDB文件?
如何配置java调用C版本的bdb?
是这样,我们使用了两个进程去访问BDB,设置选项我没带回来,但是首先BDB能提供两个或两个以上进程同时访问吗?
设置的是DB_CREATE |DB_MPOOL |DB_ININ_TXN |DB_RECOVER|DB_INIT_LOCK标志@Emily Fu
BDB支持多进程多线程访问,请参考 http://www.oracle.com/technology/documentation/berkeley-db/db/programmer_reference/transapp_app.html。
当两个进程的环境都设置为DB_RECOVER时,第二个进程恢复数据库时,第一个进程试图往遭到破坏的数据库(正在恢复)写入/读取数据。因此,出现你所描述的问题。因此,在多进程的场景中,你应该只能在第一个环境中设置其为DB_RECOVER。具体亦请参考上一条评论的链接。
请问下为什么我使用subIndex进行大数据量的查询时第一次用的时间特别久.20万笔要50秒,但再查询同样的Key就只要2秒了.谢谢.
@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的值)。