Why the expiration not set when incrementing?

Hi,
Why is the expiration not set when incrementing?
I am using binary collection with expiration, as example:

private final ReactiveCollection counterCollection;

    public Mono<Long> incrementCounter(@NonNull String id, Integer expirationTimeout) {
        IncrementOptions incrementOptions = IncrementOptions.incrementOptions().initial(1);
        incrementOptions.expiry(Instant.now().plusSeconds(expirationTimeout));

        return counterCollection.binary().increment(id, incrementOptions).map(CounterResult::content);
    }

As a result, expiration is only set for the first time. With subsequent increments, expiration remains the same.

Hi @Moldavskiy. Welcome to the Forum!

For BinaryCollection.increment and decrement operations, the expiry parameter has no effect if the counter already exists. The other thing to know is that these methods always preserve an existing document’s expiry.

This is the expected (if surprising) behavior, but it’s not documented very well. We’re tracking the documentation issue as JCBC-2030.

As a workaround, you could do a separate touch operation to reset the counter’s expiry.

Alternatively, you could use sub-document counters instead of binary collection counters. They have slightly different semantics:

  • Sub-doc counters are signed and can be decremented below zero. If an operation would result in overflow or underflow, the sub-doc operation fails.
  • Sub-doc counters have different “initial value” semantics. If the counter field doesn’t exist, it’s implicitly 0. This means the first time you increment by 1, the result is 1.
  • Sub-doc counters cannot be at the document root; they must be a field of a JSON object.
  • Sub-doc counters are modified using the mutateIn method, which has the usual KV expiry behavior (document’s expiry is always set to the value of the expiry option, or preserved if the preserveExpiry option is set to true)

Here’s some example code for incrementing a sub-doc counter if you want to try that approach:

String counterFieldPath = "c"; // Arbitrary. Shorter is better, but can't be empty.

MutateInResult mutationResult = collection.mutateIn(
  counterDocumentId,
  singletonList(MutateInSpec.increment(counterFieldPath, 1)),
  MutateInOptions.mutateInOptions()
    .storeSemantics(StoreSemantics.UPSERT)
    .expiry(Duration.ofMinutes(30))
);

long counterValueAfterMutation = mutationResult.contentAs(0, Long.class);

Thanks,
David

2 Likes

Thanks for the answer.

It would be better to make an incrementAndTouch here. xD
Actual prolongation of Expiry on the last action of the user. And it’s bad that this is out of place with respect to other actions with Options (where document’s expiry can always be set).
Which will be faster (binary collection increment and then touch) or (sub-document mutation)?

Which will be faster (binary collection increment and then touch) or (sub-document mutation)?

Probably sub-document mutation. In an informal single-threaded benchmark with client and server running on my laptop, sub-document counter is twice as fast as “increment then touch”.

Thanks,
David