-
Notifications
You must be signed in to change notification settings - Fork 3
The DBMS is primarily intended to be embedded in an application rather than run as an independent process. This allows the application to efficiently use the database as a primary data structure, without any need for caching, batching, or "server-side" procedures to achieve good performance. Queries and updates return immediately without delays due to network latency or system calls.
Therefore, every application server in a cluster is also a DBMS node, and Revori can hide temporary network interruptions from the app and recover automatically. It can also be embedded on the client side to provide the same advantages to client-server communication, as well as to serverless peer-to-peer architectures.
At a high level, the API supports two basic tasks:
- building and publishing new database revisions
- listening for changes introduced by newly-published revisions
The first task is achieved in three steps:
- Retrieve the latest published Revision
- Build a new Revision using a RevisionBuilder instance
- Publish the result
Note that the application is free to choose a concurrency control strategy for this process. It might use pessimistic strategy that involves holding a mutex for all three steps, or it might use an optimistic STM-style strategy where the whole process is retried in the case of concurrent modification. Another variation is to do a three-way merge to resolve the concurrent modification.
// pessimistic locking
private volatile Revision head;
...
synchronized (lock) {
head = head.builder().table(Users.table).row(uid)
.update(Users.name, name)
.update(Users.email, email)
.commit();
}
// optimistic STM
private final AtomicReference<Revision> head;
...
while (true) {
Revision old = head.get();
if (head.compareAndSet
(old, old.builder().table(Users.table).row(uid)
.update(Users.name, name)
.update(Users.email, email)
.commit()))
{
break;
}
}
// optimistic merge
private final AtomicReference<Revision> head;
...
Revision old = head.get();
Revision new_ = old.builder().table(Users.table).row(uid)
.update(Users.name, name)
.update(Users.email, email)
.commit();
while (! head.compareAndSet(old, new_)) {
Revision h = head.get();
new_ = old.merge(h, new_, conflictResolver, foreignKeyResolver);
old = h;
}
The best choice depends on how much contention there is among concurrent writers and how likely it is that those writes will conflict. Fortunately, it's easy to switch between strategies at configuration time or even at runtime in order to compare their performance under various workloads without modifying the rest of the application.
The task of listening for changes involves creating an instance of DiffMachine and subscribing one or more RowListeners to it. Each RowListener is associated with a query, and any time the database is modified such that the results of that query are changed, the listener will be notified of each row that has been inserted, updated, or deleted.
private final RevisionServer revisionServer;
...
DiffMachine machine = new DiffMachine(revisionServer, true);
TableReference users = reference(Users.table);
machine.subscribe(new RowListener() {
public void handleUpdate(Object[] row) {
log.info("user inserted or updated with name "
+ row[0] + " email " + row[1]);
}
public void handleDelete(Object[] row) {
log.info("user deleted with name " + row[0] + " email " + row[1]);
}
}, new QueryTemplate
(list(reference(users, Users.name),
reference(users, Users.email)),
users, constant(true)));
RevisionServer head = revisionServer.head();
head.merge(head, head.builder().table(Users.table)
.row("1234")
.update(Users.name, "Elroy Mudboy")
.update(Users.email, "[email protected]")
.row("1235")
.update(Users.name, "Creepy Baby")
.update(Users.email, "[email protected]")
.commit());
...
head = revisionServer.head();
head.merge(head, head.builder().table(Users.table)
.delete("1234")
.row("1235")
.update(Users.email, "[email protected]")
.commit());
The above code would generate the following output:
user inserted or updated with name Elroy Mudboy email [email protected]
user inserted or updated with name Creepy Baby email email [email protected]
user deleted with name Elroy Mudboy email [email protected]
user inserted or updated with name Creepy Baby email [email protected]
Javadoc publishing is forthcoming. For now, build the docs after checking out the code by running gradle javadoc
.