The atomic counter feature of Couchbase has been discussed extensively. But, a few crucial issues seem to have been glossed over. Most tutorials, such as this, as well as the official document, go as follows:
- Step 1 – Initialize the counter.
- Step 2 – Increment the counter.
There are a few crucial issues left out in this discussion. You are likely to encounter these issues on day one if you try to use atomic counter in a real application. I hope this post will answer those questions for you.
Initializing the Counter
The need to explicitly initialize a counter is a major pain. Imagine an application where you wish to keep track of the number of times an item (such as a photo) is seen by users. The logic to increment the counter will have to go as follows:
- Retrieve the counter value.
- If counter is missing, set it to 0.
- Increment the counter.
Step #2 is problematic in a multi-user environment. If two threads run almost simultaneously, step #2 of one thread may overwrite the value set by Step #3 of another thread. A better solution will be this:
- Add the counter with value 0. Catch the exception which will be thrown if the counter already exists. This error is harmless and can be safely ignored.
- Increment the counter.
This works. But, I am not sure about the additional overhead added by Step #1.
One solution is to initialize the counter at the time the related document is added to the database. For example, when a new document for a photo is added, also add the counter. This will work fine. But, what if documents were already added by a previous version of the application that did not set the counter value?
Fortunately, the Java API has a perfect solution for this problem. The incr() method takes as argument a default initial value. If the counter does not exist, system will simply set this value and return it from the incr() method. Example:
CouchbaseClient client = ...; String key = "photo-views::476005f9-b83c-4b34-93a7-e9f6f55c1b60"; long val = client.incr(key, 1L, 1L);
For the very first time the incr() method is called for a key, it will fail to find the value. It will then set the counter value in the database to the default value, which is the third parameter (1 in this case). The method will return the default value, without incrementing it. For all subsequent calls, the method will increment the counter by the step amount, which is the second parameter.
Reading a Counter Value
Counter values are stored as string in the database. Or, at least, the Java API returns it as String. So, to read the value, do something like this:
Object val = client.get(key); long counter = Long.parseLong((String) val);
That’s it folks. Now you know.