Subdocument operations in parallel strange behaviour

Hello
I am using Couchbase Server in version 4.5.1 and Couchbase Javva client version 2.4.6. I thought that updates in different parts of the document (subdoc operations) in parallel are supported, but the behaviour I observed in the tests is strange. When executing code which updates four different parts of document, like below:

@Test
public void shouldUpdatePartsOfDocumentsParralel() throws InterruptedException
{
    //configuration
    final int updatesCount = 1;

    //given
    final JsonObject data = prepareDocument();
    final String docuId = "documentXYZ";

    final List updates = Stream.of("map1", "map2", "map3", "firstName") //
            .map( path -> (Callable) () ->
            {
                final long updatingStartDate = System.currentTimeMillis();
                System.out.println(String.format("Updating for %s started %d ms", path, updatingStartDate));
                IntStream.rangeClosed(0, updatesCount).boxed().forEach(i ->
                {
                    try
                    {
                        bucket.mutateIn(docuId).upsert(path, i).withDurability(PersistTo.ONE).execute();
                    }
                    catch (final Exception e)
                    {
                        System.out.println(path + " " + e.getMessage());
                    }
                });
                final long updatingEndDate = System.currentTimeMillis();
                return "";
            }).collect(Collectors.toList());

    final ExecutorService executorService = Executors.newWorkStealingPool(updates.size());

    //CREATING DOCUMENT
    //when
    final JsonDocument document = JsonDocument.create(docuId, data);
    try {
        bucket.upsert(document);
    } catch (final Exception e) {
        System.out.println("Error during creating document" + e.getMessage());
        throw e;
    }

    //UPDATING DOCUMENT

    executorService.invokeAll(updates)
            .stream()
            .map(future -> {
                try {
                    return future;
                }
                catch (Exception e) {
                    throw new IllegalStateException(e);
                }
            })
            .forEach(System.out::println);
    System.out.println("After test received document: " + bucket.get(docuId).content().toMap());
}

The output is:

Updating for map1 started 1499858844353 ms
Updating for firstName started 1499858844353 ms
Updating for map2 started 1499858844353 ms
Updating for map3 started 1499858844353 ms
firstName com.couchbase.client.java.error.DurabilityException: Durability requirement failed: The CAS on the active node changed for ID "documentXYZ", indicating it has been modified in the meantime.
map3 com.couchbase.client.java.error.DurabilityException: Durability requirement failed: The CAS on the active node changed for ID "documentXYZ", indicating it has been modified in the meantime.
map2 com.couchbase.client.java.error.DurabilityException: Durability requirement failed: The CAS on the active node changed for ID "documentXYZ", indicating it has been modified in the meantime.
map1 com.couchbase.client.java.error.DurabilityException: Durability requirement failed: The CAS on the active node changed for ID "documentXYZ", indicating it has been modified in the meantime.
firstName com.couchbase.client.java.error.DurabilityException: Durability requirement failed: The CAS on the active node changed for ID "documentXYZ", indicating it has been modified in the meantime.
map3 com.couchbase.client.java.error.DurabilityException: Durability requirement failed: The CAS on the active node changed for ID "documentXYZ", indicating it has been modified in the meantime.
map2 com.couchbase.client.java.error.DurabilityException: Durability requirement failed: The CAS on the active node changed for ID "documentXYZ", indicating it has been modified in the meantime.
After test received document: {firstName=1, lastName=Brzęczyszczykiewicz, map3=1, map2=1, map1=1}

I am receiving exception on each attempt to updating the document, but surprisingly all changes are saved into document. Could someone explain what is the cause of this behaviour and what I am doing wrong?

Thanks for help!
Regards
Sebastian

I’m not a Java expert, but from what I can follow in your code it seems like you’re essentially scheduling 4 subdoc mutations in parallel - i.e. you’re not waiting for the Server to respond to the first before firing the second (and subsequent).

The Subdoc API in Couchbase Server Data Service does allow updates in parallel, but I think what you’re seeing here is an artifact of how Durability is exposed in the Java SDK - the Java SDK is performing the operation on a specific revision (CAS value), so it can check when that revision is written to disk.

If you remove the Durability requirement from all but the last request (assuming you only care when all of the subdoc updates are persisted, and not when only the intermediate ones are) then that should improve things.

As an aside, you can also use the multi-mutation API to simply update all four paths in a single request; meaning the server only has to perform one update instead of 4. That will also likely avoid the persistTo issue you’re seeing, given there will only be one mutation request sent to the server.

To use CAS doesn’t it have to be read first?
Does the Java SDK do it automatically under the hood?

This test for me is simulating situation when 4 different calls (clients) mutate four independent parts of the document, so yes there are different calls without waiting for respond from the Server.

The strange situation for me is, despite receiving an exception when I perform an operation, the result of the operation is saved effectively in the database. As a developer, getting an exception is a signal to me that I need to re-execute operations to save the result in database. I wonder what such action is motivated and I am trying to understand