31 CEDA LSS versus Oracle BerkeleyDB

CEDA has a high performance storage engine which features atomic transactions, automatic crash recovery, incremental backup, slave mirroring, optional transaction durability and concurrent reading with writing. It is well suited to being used as an embedded key-value database, as an alternative to products like the Oracle BerkeleyDB.

The following are the results of a comparison of the write performance of the CEDA LSS against the Oracle BerkeleyDB (C++ version).

The test involved writing a million (key,value) pairs using 1000 atomic transactions (not flushed) on a laptop which is an x64 Windows 7 platform with a pair of SSDs in RAID0 (striped). In both cases the database used a 512MB cache. Note that certainly for the CEDA LSS the file was unbuffered (i.e. not using the Windows file system cache) (see the code used for the test).

Results with 4096 byte mapped values

With 4096 byte mapped values the total amount of data to be written to disk is about 4GB. For both databases this is 8 times the size of the cache, so the test is dominated by the writing of data to non-volatile storage.

BerkeleyDB was not space efficient. It wrote over 12GB to disk (more than 3 times the amount of actual data). By contrast the CEDA space overhead was tiny (about 0.3%).

BerkeleyDB performance was very poor. It took 2 minutes and 20 seconds using over 1 million write operations while CEDA only took 4 seconds using less than 1000 write operations.

BerkeleyDB used over a million write operations while the CEDA LSS used less than a thousand.

These results were discussed on an Oracle BerkeleyDB forum here.

Results for other sizes of the mapped values are tabulated below. Also shown are results for a B+Tree implemented on top of the CEDA LSS.

Across all the mapped value sizes tested, the CEDA LSS completed the workload between 5.7 and 40 times faster than BerkeleyDB. It also used substantially less disk space and required far fewer write operations.

BerkeleyDB

Mapped value
size (bytes)
Time
(sec)
Database
size (bytes)
Log files
size (bytes)
__db files
size (bytes)
Effective rate
(MB/sec)
Disk space
wastage factor
43.78304168961782579205509693443.0363.30
83.80340213761887436805509693444.0248.36
163.83451624961992294405509693445.9833.14
324.21722288642516582405509693449.0621.87
644.4410214604831457280055096934415.4613.44
1285.4923093248050331648055096934423.629.45
2567.3642130636882837504055096934434.216.82
51211.9678215680134217728055096934441.674.94
102430.91756733440296747008055096934431.855.11
204866.48226021376238026752055096934429.535.43
40961408226021376442499072055096934427.963.22
819221216418021376867172352055096934436.893.13

CEDA LSS

Mapped value
size (bytes)
Time
(sec)
Database
size (bytes)
Effective rate
(MB/sec)
Disk space
wastage factor
40.662569011217.32.1408
80.662936012823.11.8350
160.663774873634.71.5729
320.675347737656.91.3369
640.698545894499.51.1869
1280.74149422080175.31.0987
2560.81277348352310.81.0506
5121.01533725184491.01.0264
10241.091045430272902.91.0130
20481.6420693647361195.61.0065
40963.994117757952980.91.0034
819210.18213495808774.31.0016

CEDA B+Tree implemented on top of the LSS

Mapped value
size (bytes)
Time
(sec)
Database
size (bytes)
Effective data rate
(MB/sec)
Disk space
wastage factor
40.261258291244.01.0486
80.261677721658.71.0486
160.272464153684.81.0267
320.3040370176127.21.0093
640.3872876032180.71.0122
1280.50136839168259.41.0062
2560.76264765440331.31.0029
5121.35520617984367.31.0012
10242.241032847360439.41.0008
20483.862057830400508.01.0009
40966.874106747904569.71.0007
819214.78217165824532.01.0021

BerkeleyDB Test Code


// Test code minus error handling and timing:
void BerkeleyTest(int objectSize)
{
    const char* environPath = "env";
    const char* dbPath = "my_db.db";
    DbEnv env(0);
    env.open(
        environPath,
        DB_CREATE |
            DB_INIT_LOCK |
            DB_INIT_LOG |
            DB_INIT_MPOOL |
            DB_INIT_TXN,
        0);
    Db database(&env, 0);
    database.open(
        NULL,
        dbPath,
        NULL,
        DB_BTREE,
        DB_CREATE | DB_AUTO_COMMIT,
        0);
    __int64 keyid = 0;
    std::vector buffer(objectSize);

    // note: only this for loop is being timed
    for (int i=0 ; i < 1000 ; ++i)
    {
        DbTxn* txn = NULL;
        env.txn_begin(NULL, &txn, 0);
        for (int j=0 ; j < 1000 ; ++j)
        {
            Dbt key(&keyid, sizeof(keyid));
            Dbt data(buffer.data(),objectSize);
            database.put(
                txn,
                &key,
                &data,
                DB_NOOVERWRITE);
            ++keyid;
        }
        txn->commit(0);
    }

    database.close(0);
    env.close(0);
}

# DB_CONFIG
set_cachesize   0       536870912        0
set_flags       DB_TXN_NOSYNC
set_lg_regionmax        1048576
set_lg_max              10485760
set_lg_bsize            2097152

Comparison of I/O

The total number of I/O operations and total I/O bytes for the process were recorded using the Windows Task Manager. The numbers in both the following tables seem repeatable down to the last digit.

BerkeleyDB

Mapped value
size (bytes)
ReadsWritesBytes readBytes written
425381619770201576287
826426119770212867996
1627563119770243405945
3232896119770314159978
64381264919770406681096
128562848119770731210030
2568751905197701244327637
512239791074051953416262215365406
102436013557579729478618187660111973
2048307110083532325228210624106569
4096326610095252325228212672112224
819230287203856924129055425322162241

These numbers reveal inherent inefficiencies in BDB, particularly for 1024 byte mapped values. It is reading 3x the amount of data it is supposed to be writing!

CEDA LSS

Mapped value
size (bytes)
ReadsWritesBytes readBytes written
4011025229824
8012029229568
16014037230080
32018053230592
64026085231104
1280410149232128
2560720277233664
51201330533237760
1024025501045244928
2048049902069260288
4096098804117327872
81920196808213450240