Software Development

Spring Data Couchbase – Pitfalls to Avoid


In this post I’ll cover some of the issues I have encountered with Spring Data Couchbase, after using it for the last two years.

Save Errors are ignored by default

The default behavior of Spring Data Couchbase is to ignore certain errors that might occur when trying to update documents on Couchbase. That’s right, Spring Data Couchbase won’t throw an exception for those errors or even log them – it will silently ignore them and your application code will continue as the save operation had succeeded. What makes the problem worse is that we are talking about errors that are very unlikely to happen during testing. For example, if Couchbase is temporarily overloaded the Couchbase SDK will throw a TemporaryFailureException, which Spring Data Couchbase will ignore.

Surprisingly, the solution is very simple. We need to override the default CouchbaseTemplate bean and configure it to throw exceptions when a save operation fails:

@Configuration
@EnableCouchbaseRepositories
public class CouchbaseConfig extends AbstractCouchbaseConfiguration {
	
    ...

    @Override
    @Bean(name = BeanNames.COUCHBASE_TEMPLATE)
    public CouchbaseTemplate couchbaseTemplate() throws Exception {
        CouchbaseTemplate couchbaseTemplate = super.couchbaseTemplate();
        couchbaseTemplate.setWriteResultChecking(WriteResultChecking.EXCEPTION);
        return couchbaseTemplate;
    }
}

Concurrency Control is disabled by default

In the case when there are multiple services or instances updating the same Couchbase documents, you’ll need to implement a mechanism to prevent updates from being lost due to race conditions. Take the following example:

In that case, the first update will have been overriden by the second update. Unless you implement some sort of Concurrency Control or Locking Mechanism, concurrent updates might get lost. Luckily, Couchbase supports both Optimistic and Pessimistic Locking. Enabling Optimistic Locking with Spring Data Couchbase is very simple. All you need to do is add a new field to your Entity class which should be annotated with the @Version annotation. For example:

@Document
public class User {
    @Id
    private String id;
    @Field
    private String name;
    @Field
    private Integer age;

    @Version
    private long version;
}

Once you’ve done that, any service or instance which tries to save an out-of-date version of a document will get a CASMismatchException exception. Without this, your application might be losing updates with no-one noticing.

Covered Queries don’t work by default

Covered Queries in Couchbase are a great thing. The Query Planner in Couchbase is smart enough to avoid fetching the actual documents from the data service in the case that the index being used includes all the fields that the query uses.

By default, all the queries that Spring Data Couchbase generates look like this:

SELECT META(`bucket`).id AS _ID, META(`bucket`).cas AS _CAS, `bucket`.* FROM `bucket` WHERE …

There are two things in this SELECT statement that prevents the query from being executed as a covered query. The first is the fact that we are selecting all the fields in the document. Remember that a covered query can only use the fields that are present in the index, so when we write our select statement we’ll need to specify which fields we are interested in. The second issue is that the query is selecting META(`bucket`).cas. The META fields are stored as part of the document, so by selecting META(`bucket`).cas we are forcing the Query Executor to fetch the document from the data service.

In order to have covered queries we need to define what fields the query will select. For example:

public interface UserRepository extends CrudRepository {

    @Query("SELECT META(#{#n1ql.bucket}).id AS _ID, age FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} and name = $name")
    List findByName(String name);

}

Note that selecting the CAS value, which is what Spring Data Couchbase does by default, is necessary for supporting Optimistic Locking. For the cases where we only need to retrieve a subset of the document, we can avoid selecting the CAS value.

Older Versions are vulnerable to query injection

Previous versions of Spring Data Couchbase use string concatenation to build the n1ql queries, which means that they are vulnerable to sql (actually n1ql) injections. Only newer versions have started using parameterized queries. Make sure to upgrade to the latest version of Spring Data Couchbase.

Performance is not great

Compared to using the Couchbase Java SDK directly, there is a performance degradation when using Spring Data Couchbase. Not only will query operations take longer, but also bulk key/value operations like get and save are performed sequentially.

Conclusion

So should you use Spring Data Couchbase? Unless your application needs extremely high performance, I would say yes. Even with all the issues I mentioned above, the convenience that Spring Data Couchbase provides is unmatched by anything else out there. Spring Data Couchbase is quite a big framework, with many features and sometimes strange default behavior, so be sure to read the Reference Documentation.

Software Development
Are You Agile? 6 Signs Your Team May Not Be
Software Development
Considering a microservice architecture? Read this first.
Software Development
Want to be a great developer? Start reading Schopenhauer