PolePosition Circuits

This file is a complementary narrative for the open source Poleposition benchmark project. Source code snippets are provided for selected tasks to show how they are implemented. As a general rule using plain JDBC required many more lines of code than using modern object-oriented techniques like object databases or object-relational mappers. For example ComplexJdbc.java was written with 550 lines of code. The MongoDB implementation uses around 400 lines of code in ComplexMongoDB.java and ComplexObjectSerialisation.java. The Object Oriented counterparts ComplexDb4o.java, ComplexJdo.java and ComplexHibernate.java are about 150 lines of code.


Content

Poleposition benchmark tasks are called circuits. This document contains explanations to the following circuits:


Complex

The Complex circuit simulates writing, reading, querying and updating a deep object graph that is composed of multiple different classes with an inheritance hierarchy of 5 levels. The sources for the implementations can be found here:

The class model for the Complex circuit looks like this:

class ComplexHolder0 {
    String _name;
    List <ComplexHolder0> _children;
    ComplexHolder0[] _array;
}

class ComplexHolder1 extends ComplexHolder0 {
    int _i1;
}

class ComplexHolder2 extends ComplexHolder1 {
    int _i2;
}

class ComplexHolder3 extends ComplexHolder2 {
    int _i3;
}

class ComplexHolder4 extends ComplexHolder3 {
    int _i4;
}

Member _i2 on ComplexHolder2 is indexed for queries.


Complex.write

ComplexHolder0#generate() generates a structure with depth and objectcount as specificied in the configuration file. The _children List and the _array member are again filled with ComplexHolder0 objects. Varying instances from ComplexHolder0 to ComplexHolder4 are used.

Complex.write stores the complete graph and measures the time taken for each database access technology.


Complex.read

Complex.read accesses the root object of the structure that was generated with Complex.write, loads all attached objects into memory and traverses them, making sure that all objects are present by calculating a checksum over all objects.


Complex.query

Complex.query queries for ComplexHolder2 instances over the indexed _i2 field. The query is run multiple times, specified by the selects property in the configuration file. Sample queries look like this:

// db4o
Query query = db.query();
query.constrain(ComplexHolder2.class);
query.descend("_i2").constrain(currentInt);
ObjectSet result = query.execute();

// JDO
String filter = "this.i2 == param";
Query query = pm.newQuery(ComplexHolder2.class, filter);
query.declareParameters("int param");
Collection result = (Collection) query.execute(currentInt);

// Hibernate
String query = "from org.polepos.teams.hibernate.data.ComplexHolder2 where i2=" + currentInt;
Iterator it = session.iterate(query);

// JDBC
StringBuilder sb = new StringBuilder();
sb.append("select " + HOLDER_TABLE0 + ".id from " + HOLDER_TABLE0);
sb.append(" INNER JOIN " + HOLDER_TABLES[0]);
sb.append(" on " + HOLDER_TABLE0 + ".id = " + HOLDER_TABLES[0] + ".id ");
sb.append(" INNER JOIN " + HOLDER_TABLES[1]);
sb.append(" on " + HOLDER_TABLE0 + ".id = " + HOLDER_TABLES[1] + ".id ");
sb.append(" LEFT OUTER JOIN " + HOLDER_TABLES[2]);
sb.append(" on " + HOLDER_TABLE0 + ".id = " + HOLDER_TABLES[2] + ".id ");
sb.append(" LEFT OUTER JOIN " + HOLDER_TABLES[3]);
sb.append(" on " + HOLDER_TABLE0 + ".id = " + HOLDER_TABLES[3] + ".id ");
sb.append(" where " + HOLDER_TABLES[1] + ".i2 = ?");
PreparedStatement stat = prepareStatement(sb.toString());
ResultSet resultSet stat.executeQuery();

For JDBC we are only showing the main query. Reading individual object intances required another 150 lines of code with our implementation, see ComplexJdbc.java.

// MongoDB
BasicDBObject query = new BasicDBObject();
query.put("_i2", currentInt);
query.put(ComplexObjectSerialisation.TYPE_ATTRIBUTE, "ComplexHolder2");
final DBObject resultDoc = dbCollection().findOne(query, SELECT_MASK); 
 

For MongoDB the selection masks selects the parts of the document which are required for the benchmark to improve the performance


Complex.update

Complex.update reads the root object of the graph and traverses all holder objects, updates the name, copies all instances from the _children list to _array and stores the holder objects again.

// for each holder, do the following

// OO
holder.setName("updated");
List <ComplexHolder0> children = holder.getChildren();
ComplexHolder0[] array = new ComplexHolder0[children.size()];
for (int i = 0; i < array.length; i++) {
    array[i] = children.get(i);
}
holder.setArray(array);
db.store(holder);

// JDBC 
PreparedStatement nameStat = prepareStatement("update " + HOLDER_TABLE0 + " set name=? where id=?");
PreparedStatement arrayDeleteStat = prepareStatement("delete from " + ARRAY_TABLE + " where parent = ?");
PreparedStatement arrayInsertStat = prepareStatement("insert into tarray (parent, child, pos) values (?,?,?)");
nameStat.setString(1, "updated");
nameStat.setInt(2, holder.getId());
nameStat.addBatch();
arrayDeleteStat.setInt(1, holder.getId());
arrayDeleteStat.addBatch();
List <ComplexHolder0> children = holder.getChildren();
for (int i = 0; i < children.size(); i++) {
    arrayInsertStat.setInt(1, holder.getId());
    arrayInsertStat.setInt(2, children.get(i).getId());
    arrayInsertStat.setInt(3, i);
    arrayInsertStat.addBatch();
}

// MonoDB
// Same as with OO databases, except that we convert everything into documents and store those:
for (BasicDBObject object : serializer.convertToDocument(holder).allObjectsAsArray()) {
	dbCollection().save(object);
}

// The mapping from object to documents looks something like this in general
BasicDBObject dbObject = new BasicDBObject();
dbObject.put(NAME_FIELD, holder.getName());
dbObject.put(NAME_FIELD, holder.getName());
dbObject.put(FIELD_I1, holder.getFieldI1());
// and so on and so forth


Complex.delete

Complex.delete traverses all objects from the root and deletes each object individually.


Nestedlists

The Nestelists circuit simulates writing, reading, querying and updating a deep graph of lists. Objects are partially reused within the lists. The sources for the implementations can be found here:

The class model for the NestedLists circuit looks like this:

class ListHolder {
    private long _id;
    private String _name;
    List <ListHolder> _list;
}

Nestedlists.create

ListHolder#generate() generates a deep structure of lists, reusing an amount of previous objects specified by the reuse property in the configuration file. Nestedlists.create stores the root of this generated graph and measures the time taken.

// OO
db.store(ListHolder.generate(depth(), objectCount(), reuse()));
db.commit();

// JDBC
ListHolder root = ListHolder.generate(depth(), objectCount(), reuse());
final PreparedStatement listHolderStatement = prepareStatement("insert into " + LISTHOLDER_TABLE + " (fid, fname) values (?,?)");
final PreparedStatement listStatement = prepareStatement("insert into " + LIST_TABLE + " (fid, fitem, felement) values (?,?,?)");
_rootId = (int) root.id();
root.accept(new Visitor<ListHolder>() {
    public void visit(ListHolder listHolder) {
        try {
            int listHolderId = (int) listHolder.id();
            listHolderStatement.setInt(ID, listHolderId);
            listHolderStatement.setString(NAME, listHolder.name());
            listHolderStatement.addBatch();
            List<ListHolder> list = listHolder.list();
            if(list != null && ! list.isEmpty()){
                int position = 0;
                for (ListHolder child : list) {
                    listStatement.setInt(ID, listHolderId);
                    listStatement.setInt(ITEM, (int) child.id());
                    listStatement.setInt(ELEMENT, position++);
                    listStatement.addBatch();
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
});
listStatement.executeBatch();
listHolderStatement.executeBatch();
listStatement.close();
listHolderStatement.close();
commit();

// MongoDB
// Everything is stored in a single document. However the cyclic references in the lists are an issue.
// To work around that issue the lists are flattened and the cycles are stored as mapping between ids. 
// After that is a simple document store / load
DBObject document = convertToDocument(listHolder);
dbCollection().insert(document);

// The mapping from object to documents looks something like this in general
DBObject document = new BasicDBObject();
document.put(ID_FIELD, listHolder.id());
document.put(NAME_FIELD, listHolder.name());
// and so on and so forth

Nestedlists.read

Nestedlists.read loads the root object of the structure stored with Nestedlists.create and traverses all objects and adds them to the checksum.

// OO just traverses the root with a simple visitor
public void read() throws Throwable {
    root().accept(new Visitor<ListHolder>(){
        public void visit(ListHolder listHolder){
            addToCheckSum(listHolder);
        }
    });
}
private ListHolder root() {
    Query query = db().query();
    query.constrain(ListHolder.class);
    query.descend("_name").constrain(ListHolder.ROOT_NAME);
    ObjectSet objectSet = query.execute();
    return (ListHolder) objectSet.next();
}


// JDBC requires a recursive read method and a set to check if an instance was already loaded
public void read() throws Throwable {
    ListHolder root = root();
    root.accept(new Visitor<ListHolder>(){
        public void visit(ListHolder listHolder){
            addToCheckSum(listHolder);
        }
    });
}
private ListHolder root() throws SQLException {
    PreparedStatement listHolderStatement = prepareStatement("select * from " + LISTHOLDER_TABLE + " where fid = ?");
    PreparedStatement listStatement = prepareStatement("select * from " + LIST_TABLE + " where fid = ? order by felement");
    Set<ListHolder> found = new HashSet<ListHolder>();
    ListHolder root = recurseRead(listHolderStatement, listStatement, _rootId, found);
    closePreparedStatement(listHolderStatement);
    closePreparedStatement(listStatement);
    return root;
}
private ListHolder recurseRead(PreparedStatement listHolderStatement,
        PreparedStatement listStatement, int id, Set<ListHolder> found) throws SQLException {
    listHolderStatement.setInt(ID, id);
    ResultSet listHolderResultSet = listHolderStatement.executeQuery();
    listHolderResultSet.next();
    ListHolder listHolder = new ListHolder();
    listHolder.id(id);
    if(found.contains(listHolder)){
        return listHolder;
    }
    found.add(listHolder);
    listHolder.name(listHolderResultSet.getString(NAME));
    listHolderResultSet.close();
    listStatement.setInt(ID, id);
    
    ResultSet listResultSet = listStatement.executeQuery();
    List ids = new ArrayList(); 
    if(listResultSet.next()){
        do{
            ids.add(listResultSet.getInt(ITEM));
        } while(listResultSet.next());
        listResultSet.close();
        List <ListHolder> list = new ArrayList<ListHolder>();
        for (Integer childId : ids) {
            list.add(recurseRead(listHolderStatement, listStatement, childId, found));
        }
        listHolder.list(list);
    }
    return listHolder;
}

// MongoDB
// As earlier mentioned, everything is stored in one document. 
// We can load the document and then deserialize the flat document back to an object graph with cycles in it.

final DBObject document = dbCollection().findOne(new BasicDBObject(NAME_FIELD, ListHolder.ROOT_NAME));
ListHolder root = convertFromDocument(document);

Nestedlists.update

Nestedlists.update traverses the graph of lists created in Nestedlists.create and updates the name of each Listholder individually (using batched prepared statements with JDBC).


Nestedlists.delete

Nestedlists.delete traverses the graph of lists created in Nestedlists.create and deletes each Listholder individually (using batched prepared statements with JDBC).


Inheritancehierarchy

The Inheritancehierarchy circuit simulates writing, reading, querying and deleting objects of a class hierarchy with a depth of 5 levels. The sources for the implementations can be found here:

The class model for the InheritanceHierarchy circuit looks like this:

class InheritanceHierarchy0 {
    int i0;
}
class InheritanceHierarchy1 extends InheritanceHierarchy0 {
   int i1  
}
class InheritanceHierarchy2 extends InheritanceHierarchy1 {
   int i2  
}
class InheritanceHierarchy3 extends InheritanceHierarchy2 {
   int i3  
}
class InheritanceHierarchy4 extends InheritanceHierarchy3 {
   int i4  
}

Member i2 on InheritanceHierarchy2 is indexed for queries. Since we are simulating a class structure that contains tens or even hundreds of classes, we store the data for each class in separate tables, in the JDBC and OR mapper code.


InheritanceHierarchy.write

InheritanceHierarchy.write stores a number of InheritanceHierarchy4 objects and measures the time taken.

// OO
int count = setup().getObjectCount();
for (int i = 1; i<= count; i++) {
    InheritanceHierarchy4 inheritancheHierarchy4 = new InheritanceHierarchy4();
    inheritancheHierarchy4.setAll(i);
    db.store(inheritancheHierarchy4);
}
commit();


// JDBC
PreparedStatement[] statements = new PreparedStatement[5];
for (int i = 0; i < 5; i++) {
    statements[i] = prepareStatement("insert into " + TABLES[i] + " (id, parent, i" + i + ") values (?,?,?)");
}
int count = setup().getObjectCount();
for (int j = 0; j < 5; j++) {
	for (int i = 1; i <= count; i++) {  
		InheritanceHierarchy4 inheritanceHierarchy4 = new InheritanceHierarchy4();
		inheritanceHierarchy4.setAll(i);
		statements[j].setInt(1, i);
		statements[j].setInt(2, i);
		statements[j].setInt(3, inheritanceHierarchy4.getIx(j));
		statements[j].addBatch();
	}
	statements[j].executeBatch();
	statements[j].close();
}
commit();

// MongoDB
int count = setup().getObjectCount();
for (int i = 1; i <= count; i++) {
	InheritanceHierarchy4 inheritancheHierarchy4 = new InheritanceHierarchy4();
	inheritancheHierarchy4.setAll(i);
	final DBObject document = serialize(inheritancheHierarchy4);
	dbCollection().insert(document);
}
dbCollection().commit();

InheritanceHierarchy.read

InheritanceHierarchy.read executes a number of queries for all InheritanceHierarchy4 objects and iterates over each.


InheritanceHierarchy.query

InheritanceHierarchy.query executes a number of queries for all InheritanceHierarchy4 objects with a where clause for the indexed i2 member.


InheritanceHierarchy.delete

InheritanceHierarchy.delete simulates deleting all InheritanceHierarchy4 objects individually.

// db4o
Query q = db.query();
q.constrain(InheritanceHierarchy4.class);
ObjectSet<InheritanceHierarchy4> deleteSet = q.execute();
while(deleteSet.hasNext()){
    db().delete(deleteSet.next());
}
db.commit();

// JDO
Extent extent = db().getExtent(InheritanceHierarchy4.class, false);
Iterator it = extent.iterator();
while(it.hasNext()){
    db().deletePersistent(it.next());
}
extent.closeAll();
commit();

// Hibernate
Transaction tx = session.beginTransaction();
Iterator it = session.iterate("from org.polepos.teams.hibernate.data.InheritanceHierarchy4");
while(it.hasNext()){
    db().delete(it.next());
}
tx.commit();


// JDBC
int count = setup().getObjectCount();
PreparedStatement[] statements = new PreparedStatement[5];
for (int i = 0; i < 5; i++) {
    statements[i] = prepareStatement("delete from " + TABLES[i] + " where id=?");
}
for (int j = 0; j < 5; j++) {
    for (int i = 1; i <= count; i++) {
        statements[j].setInt(1, i);
        statements[j].addBatch();
    }
statements[j].executeBatch();
statements[j].close();
commit(); 

// MongoDB
for (DBObject dbObject : dbCollection().find()) {
	dbCollection().remove(dbObject);
	addToCheckSum(5);
}

Flatobject

The Flatobject circuit simulates writing, updating and deleting plain simple flat objects as well as querying for indexed int members and string members. The sources for the implementations can be found here:

The class model for the FlatObject circuit looks like this:

class IndexedObject {
    int _int;
    String _string;
}

Both the _int and the _string field are indexed.


FlatObject.write

FlatObject.write simulates storing a number of simple objects to a database.


FlatObject.queryIndexedString

FlatObject.queryIndexedString simulates querying for a number of flat objects by an indexed string member.


FlatObject.queryIndexedInt

FlatObject.queryIndexedInt simulates querying for a number of flat objects by an indexed int member.


FlatObject.update

FlatObject.write simulates updating a string member on a number of simple objects after querying for an indexed int member.


FlatObject.delete

FlatObject.delete simulates deleting a number of simple objects individually after querying for an indexed int member.