Open Credo

February 17, 2014 | Cassandra

New features in Cassandra 2.0 – Lightweight Transactions on Update

In our previous posts we gave an overview of Cassandra’s new compare-and-set (lightweight transaction) commands and a more detailed look into the API for using them when inserting new rows into the database.

In this third post, we are going to cover update statements. We recommend reading the previous posts, as there are some details which are the same for inserts and updates which are not repeated here.

WRITTEN BY

David Borsos

David Borsos

New features in Cassandra 2.0 – Lightweight Transactions on Update

Scenario

UPDATE statements, as well as INSERTs can leverage the compare-and-set mechanics. To demonstrate this we are going to show a possible implementation of an optimistic locking mechanism based on versioning the data.

Suppose we have a simple application that can be used to edit and view blog entries, using a rich web frontend. A typical way of doing this would be a server-side application that sends whole entries to the users’ browser, where they can edit the contents then send it back to the server, which saves it.

Before compare-and-set operations

Let’s start with a simple (pre-Cassandra 2.0) implementation. We will probably have a service to retrieve entries:

public BlogEntry getEntry(String id) {
    PreparedStatement queryStmt = session.prepare(
        "SELECT * FROM blog_entry WHERE id = ?");
    Statement stmt = queryStmt.bind(id);
    ResultSet rs = session.execute(stmt);
    Row row = rs.one();

    if (row != null) {
        return new BlogEntry(
            row.getString("id"),
            row.getString("contents"),
            row.getInt("version"));
    } else {
        throw new BlogEntryNotFoundException(
           "No blog entry found by id " + id);
    }
}

We will have another service to update, which saves whatever is posted to it, incrementing the version:

public void updateBlogEntry(BlogEntry entry) {
    PreparedStatement updateStmt = session.prepare(
        "UPDATE blog_entry SET " +
        "contents = ?, " +
        "version = ? " +
        "WHERE id = ?");

    int newVersion = entry.getVersion() + 1;
    Statement stmt = updateStmt.bind(
        entry.getContents(),
        newVersion,
        entry.getId());

    session.execute(stmt);
}

Now, should two users decide to edit the same entry at the same time:

BlogEntry entry = getEntry("cassandra20");
entry.setContents(entry.getContents() + " Slightly modified it, to be reviewed.");
updateBlogEntry(entry);

And our other user would do something like:

BlogEntry entry = getEntry("cassandra20");
entry.setContents(entry.getContents() + " Added second half, fixed typos.");
updateBlogEntry(entry);

The sequence of events is shown below:

cas20-optlock

After both updates, querying the contents of the blog_entry table shows the following:

select * from blog_entry;

id           | contents                                          | version
-------------+---------------------------------------------------+---------
cassandra20  | Initial contents. Added second half, fixed typos. | 2

(1 rows)

User 2 applied her changes later in time, and those changes completely overwrote User 1’s changes. Even worse, we had no notification whatsoever – this happened completely silently, and the only way we can detect it is to have User 1 check the contents manually.

One can attempt to enhance updateBlogEntry with a version checking SELECT prior to applying the changes, but that won’t exclude the possibility of such overwrites completely.

A slightly better way of handling such scenarios is stopping User 2 before applying her changes, and signal to her that someone else has changed the data while she was editing it. This would prevent us from losing User 1’s changes, and User 2 might have a chance of merging the data (well, there are lots of possibilities, but for now let’s just stick to at least notifying our users).

Using compare-and-set operations

Let’s introduce an improved version of our update method:

public void updateVersionedBlogEntry(BlogEntry entry)
            throws OptimisticLockFailure {
    PreparedStatement updateStmt = session.prepare(
        "UPDATE blog_entry SET " +
        "contents = ?, " +
        "version = ? " +
        "WHERE id = ? " +
        "IF version = ?");

    int newVersion = entry.getVersion() + 1;
    Statement stmt = updateStmt.bind(
        entry.getContents(),
        newVersion,
        entry.getId(),
        entry.getVersion());

    ResultSet rs = session.execute(stmt);
    Row row = rs.one();

    if (row.getBool("[applied]")) {
        log("Successfully modified entry " + entry.getId() +
            " to version " + newVersion);
    } else {
        throw new OptimisticLockFailure(
            "ERROR - entry " + entry.getId() + " has been modified” +
            “ by another user!" +
            " Its current version is " + row.getInt("version"));
    }
}

There should not be many surprises after the INSERT scenario. When updating, Cassandra will atomically check whether the version of the entries matches. If they do, that means no other user has modified the entry while we were editing, so saving is safe. Otherwise we can check once again for the [applied] field in the returned ResultSet, and see the condition fields’ new values.

Running the same sequence of events shows that User 2 receives an error message:

2013-11-12 12:25:47:047 - Successfully modified entry cassandra20 to version 2
2013-11-12 12:25:47:047 - ERROR - entry cassandra20 has been modified by another user! Its current version is 2

We can see that the original changes were preserved by querying the table:

select * from blog_entry;

id           | contents                                                | version
-------------+---------------------------------------------------------+---------
cassandra20  | Initial contents. Slightly modified it, to be reviewed. | 2

(1 rows)

This concludes our series of posts about Cassandra compare-and-set operations. We hope that you find them useful. We are planning to publish another Cassandra-related article soon which will contain some details about the other major new feature of Cassandra 2.0: triggers.

 

This blog is written exclusively by the OpenCredo team. We do not accept external contributions.

RETURN TO BLOG

SHARE

Twitter LinkedIn Facebook Email

SIMILAR POSTS

Blog