diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
index dfe597debc1b71ae4893a04d7436cdbc7b8a8447..59dbbca49f00d1830e647e4ce396b2f82b518971 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
@@ -113,8 +113,12 @@ OnClickListener, OnItemClickListener {
 					// Wait for the service to be bound and started
 					serviceConnection.waitForStartup();
 					// Load the headers from the database
+					long now = System.currentTimeMillis();
 					Collection<GroupMessageHeader> headers =
 							db.getMessageHeaders(groupId);
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Load took " + duration + " ms");
 					// Display the headers in the UI
 					displayHeaders(headers);
 				} catch(NoSuchSubscriptionException e) {
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
index 5bb2a6c4d5059a80898e39386f216bd3209ad33e..6921e582fe5cebbbe66d1709b7b11d494ee9c7c8 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -123,7 +123,11 @@ implements OnClickListener, DatabaseListener {
 					// We'll also need a contact to receive messages from
 					ContactId contactId = db.addContact("Dave");
 					// Finally, we'll need some authors for the messages
+					long now = System.currentTimeMillis();
 					KeyPair keyPair = crypto.generateSignatureKeyPair();
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Key generation took " + duration + " ms");
 					byte[] publicKey = keyPair.getPublic().getEncoded();
 					PrivateKey privateKey = keyPair.getPrivate();
 					Author author = authorFactory.createAuthor("Batman",
@@ -150,6 +154,7 @@ implements OnClickListener, DatabaseListener {
 						}
 						Group g = i % 2 == 0 ? group : group1;
 						Message m;
+						now = System.currentTimeMillis();
 						if(i % 5 == 0) {
 							m = messageFactory.createAnonymousMessage(null, g,
 									"text/plain", body.getBytes("UTF-8"));
@@ -162,9 +167,20 @@ implements OnClickListener, DatabaseListener {
 									g, author1, privateKey, "text/plain",
 									body.getBytes("UTF-8"));
 						}
+						duration = System.currentTimeMillis() - now;
+						if(LOG.isLoggable(INFO)) {
+							LOG.info("Message creation took " +
+									duration + " ms");
+						}
+						now = System.currentTimeMillis();
 						if(Math.random() < 0.5) db.addLocalGroupMessage(m);
 						else db.receiveMessage(contactId, m);
 						db.setReadFlag(m.getId(), i % 4 == 0);
+						duration = System.currentTimeMillis() - now;
+						if(LOG.isLoggable(INFO)) {
+							LOG.info("Message storage took " +
+									duration + " ms");
+						}
 					}
 					// Insert a non-text message
 					Message m = messageFactory.createAnonymousMessage(null,
@@ -214,9 +230,13 @@ implements OnClickListener, DatabaseListener {
 						// Filter out restricted groups
 						if(g.getPublicKey() != null) continue;
 						try {
+							long now = System.currentTimeMillis();
 							// Load the headers from the database
 							Collection<GroupMessageHeader> headers =
 									db.getMessageHeaders(g.getId());
+							long duration = System.currentTimeMillis() - now;
+							if(LOG.isLoggable(INFO))
+								LOG.info("Full load took " + duration + " ms");
 							// Display the headers in the UI
 							displayHeaders(g, headers);
 						} catch(NoSuchSubscriptionException e) {
@@ -312,7 +332,14 @@ implements OnClickListener, DatabaseListener {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
-					displayHeaders(db.getGroup(g), db.getMessageHeaders(g));
+					long now = System.currentTimeMillis();
+					Group group = db.getGroup(g);
+					Collection<GroupMessageHeader> headers =
+							db.getMessageHeaders(g);
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Partial load took " + duration + " ms");
+					displayHeaders(group, headers);
 				} catch(NoSuchSubscriptionException e) {
 					if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
 				} catch(DbException e) {
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
index c3463dd96ab88570a2c242975c63469f6881eaf2..635eee594085948d4c528f387579759677672e77 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java
@@ -111,8 +111,12 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
 					// Wait for the service to be bound and started
 					serviceConnection.waitForStartup();
 					// Load the headers from the database
+					long now = System.currentTimeMillis();
 					Collection<PrivateMessageHeader> headers =
 							db.getPrivateMessageHeaders(contactId);
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Load took " + duration + " ms");
 					// Display the headers in the UI
 					displayHeaders(headers);
 				} catch(NoSuchContactException e) {
diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
index caf7a0601ac1afe2dc5f75ab2d9fdd44db9bf460..a6f829e990d1dc990e27647b8c646d0cd5e12578 100644
--- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java
@@ -173,8 +173,12 @@ implements OnClickListener, DatabaseListener {
 					for(Contact c : db.getContacts()) {
 						try {
 							// Load the headers from the database
+							long now = System.currentTimeMillis();
 							Collection<PrivateMessageHeader> headers =
 									db.getPrivateMessageHeaders(c.getId());
+							long duration = System.currentTimeMillis() - now;
+							if(LOG.isLoggable(INFO))
+								LOG.info("Full load took " + duration + " ms");
 							// Display the headers in the UI
 							displayHeaders(c, headers);
 						} catch(NoSuchContactException e) {
@@ -281,8 +285,14 @@ implements OnClickListener, DatabaseListener {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
+					long now = System.currentTimeMillis();
 					Contact contact = db.getContact(c);
-					displayHeaders(contact, db.getPrivateMessageHeaders(c));
+					Collection<PrivateMessageHeader> headers =
+							db.getPrivateMessageHeaders(c);
+					long duration = System.currentTimeMillis() - now;
+					if(LOG.isLoggable(INFO))
+						LOG.info("Partial load took " + duration + " ms");
+					displayHeaders(contact, headers);
 				} catch(NoSuchContactException e) {
 					if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
 				} catch(DbException e) {
diff --git a/briar-core/src/net/sf/briar/db/DatabaseModule.java b/briar-core/src/net/sf/briar/db/DatabaseModule.java
index 1f4829d858b84958b6c55d1f7497552cd5d1f84c..540a2990d311880efe0ee8d15cdeaec772cda11e 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseModule.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseModule.java
@@ -1,12 +1,8 @@
 package net.sf.briar.db;
 
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
 import java.sql.Connection;
-import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.Executor;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.Executors;
 
 import net.sf.briar.api.clock.Clock;
 import net.sf.briar.api.clock.SystemClock;
@@ -21,23 +17,11 @@ import com.google.inject.Singleton;
 
 public class DatabaseModule extends AbstractModule {
 
-	/** The minimum number of database threads to keep in the pool. */
-	private static final int MIN_DB_THREADS = 1;
-
-	/** The maximum number of database threads. */
-	private static final int MAX_DB_THREADS = 10;
-
-	/** The time in milliseconds to keep unused database threads alive. */
-	private static final int DB_KEEPALIVE = 60 * 1000;
-
 	@Override
 	protected void configure() {
 		bind(DatabaseCleaner.class).to(DatabaseCleanerImpl.class);
-		// Database tasks may depend on each other, so use an unbounded queue
-		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
 		bind(Executor.class).annotatedWith(DatabaseExecutor.class).toInstance(
-				new ThreadPoolExecutor(MIN_DB_THREADS, MAX_DB_THREADS,
-						DB_KEEPALIVE, MILLISECONDS, queue));
+				Executors.newCachedThreadPool());
 	}
 
 	@Provides