Advanced Topics
As TCStore API is a storage API targeted for distributed datasets, the picture wouldn't be complete without addressing ...
Durability
Atomicity guarantees
Asynchronicity
Durability Guarantees
TCStore API will always aim for maximum durability. The exact meaning of this depends on the configuration of the store itself:
For non-persistent, non-replicated stores, it means the data made it to the server.
For persistent, non-replicated stores, it means the data made it to the disk of the server.
For persistent, replicated stores, it means the data made it to the disk of the servers and all replicas.
Atomicity
Beside the previously discussed read and write settings, TCStore API makes a Record the atomic unit of work.
So when you build a Stream<Record<?>> where you filter and then mutate (a bulk update), it will make sure you never mutate a Record<?> that doesn’t pass that Predicate you used to filter, even if some other writer mutated the given Record<?> concurrently.
Also, if you mutate two Cell instances of one Record, depending on the write and read settings either both new or old values would be observed, but a reader would never be able observe one old and one new value for that given write.
The underlying principle is that a Record instances are effectively immutable.
Asynchronicity
As mentioned previously, TCStore API is targeted at distributed deployments. As such, it is inherently asynchronous. Although all the API examples we used in this document are synchronous, TCStore exposes an asynchronous API for you to use:
Asynchronous Operations
AsyncDatasetWriterReader<String> asyncAccess =
counterAccess.async(); // <1>
Operation<Boolean> addOp =
asyncAccess.add("counter10", counterCell.newCell(10L)); // <2>
Operation<Optional<Record<String>>> getOp =
addOp.thenCompose((b) -> asyncAccess.get("counter10")); // <3>
Operation<Void> acceptOp = getOp.thenAccept(or -> or.ifPresent( // <4>
r -> System.out.println("The record with key " + r.getKey() +
" was added")));
try {
acceptOp.get(); // <5>
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
1. Retrieves an asynchronous accessor to the Dataset in the form of AsyncDatasetWriterReader.
2. Schedule a write a new Record, identified by the key counter10 with an initial value of counterCell as 10. This returns an Operation instance which can then be waited upon (as a java.util.concurrent.Future) or combined with other operations (as a java.util.concurrent.CompletionStage).
3. Chain a get, to be performed once the write is complete, to the write operation.
This does NOT affect the write nor does it combine the write and the get into an atomic unit.
The result of this combination is also an Operation instance.
4. To the get Operation instance, chain a Consumer that processes the Optional<Record<String>> returned from the get. The result of this chain construction returns an Operation<Void>. Consumer returns no result.
5. As with addOp and getOp, the acceptOp Operation is a handle to an operation scheduled for background completion. This operation sequence will complete on its own but, to await completion, one of the Future.get methods must be called.
The default API is synchronous, as it is probably more obvious for everyone to get started with. But the power of the asynchronous API is exposed as well.
Note: When using the asynchronous API, operation sequencing is not maintained. If, in the example above, the get was not chained to the write, the get could be executed before the write even though the Operation instances were created in the opposite order. Mind, that two asynchronous operations are independent.