Adding GetReplica API#161
Conversation
| ``` | ||
|
|
||
| Where `ReplicaIndex` is an enum of `FIRST`, `SECOND`, `THIRD`. | ||
| Not supported until there is a concrete use-case for it is `ACTIVE`: the user should use regular `Get`, and can use `GetReplica` on exceptions. |
There was a problem hiding this comment.
I recall a discussion of a RANDOM index as well - which one could do by knowing how many replicas currently and deciding what to put in here, but I wonder if that is needed? Not pushing either way, just curious.
There was a problem hiding this comment.
Yes there is scope for that though I would lean to implementing it as RetryStrategy.random().
On operational it's not strictly required, the user can easily loop through fromIndex until fast-failure. On couchbase2:// it does have utility as it can avoid round-trips to find the last replica (assuming we end up being able to push it down in the GRPC). But the wrap fromIndex parameter also handles the same case. So it would be just sugar - but it's nice sugar.
|
|
||
| - Throws | ||
| - Documented | ||
| - DocumentNotFoundOnReplicaException |
There was a problem hiding this comment.
Is a new exception type necessary for this? This will never return DocumentNotFoundException. Could we not use that instead? I would imagine that users would have a narrow try-catch block around the GetReplica operation if they want to do something in response to that, so it wouldn't get mixed up with a DocumentNotFoundException thrown by the standard Get.
Separate exceptions would also be a departure from the current approach where the mapping of KV status to exception type is shared between all operations. Both DocumentNotFoundException and DocumentNotFoundOnReplicaException correspond to the 0x01 (not found) KV status.
I don't feel strongly about this, just curious about your thoughts.
There was a problem hiding this comment.
Yep fair question. See what you think of my justifications:
This will never return DocumentNotFoundException
It can, as future strategies are allowed to target the active. (Thinking of use-cases like preferred server groups here)
In which case it's very useful for the user to know if the signal came from the active or not - analogous to the existing isReplica on GetReplicaResult. DocumentNotFoundException is a much stronger signal than DocumentNotFoundOnReplicaException and could deserve separate programmatic treatment (the primary SDK3 criteria for whether a new exception is warranted). Though tbh I don't have a concrete use-case for that separate treatment in mind currently.
I also like that it reminds users that replicas are eventually consistent.
There was a problem hiding this comment.
This sounds reasonable, especially considering that eventually we might also be fetching the active from here. I think I'm convinced
|
|
||
| GetReplicaStrategyFromIndexOptions options: | ||
|
|
||
| - `Boolean wrap` - whether to wrap around the available replicas, rather than throwing `ReplicaIndexOutOfBoundsException`. Defaults to false. |
There was a problem hiding this comment.
Why would a user want to wrap instead of being told the replica doesn't exist?
There was a problem hiding this comment.
Cluster deployment has only two replica, but application request THIRD, wrap here would be something like 3 % 2 => 1. I think if we have wrap, then we need clamp too, which will give me something like max(bucket.num_replicas, replica), here max(2, 3) => 2.
also, how about case where the number of replicas satisfies the requested index, but the index resolves into node index -1? is this the same? should we wrap/clamp?
There was a problem hiding this comment.
@dnault primarily to support the random use-case, and primary on couchbase2://. Avoids an 'error hop', and avoids the user needing to code in knowledge of their topology setup into the app.
@avsej I'm not sure about a clamp as it would bias things towards the last node, in the random use-case. Do you have a use-case in mind for it?
-1 was discussed on Slack including w.r.t. wrap - have tightened up the RFC on it; let me know what you think.
|
|
||
| GetReplicaStrategyFromIndexOptions options: | ||
|
|
||
| - `Boolean wrap` - whether to wrap around the available replicas, rather than throwing `ReplicaIndexOutOfBoundsException`. Defaults to false. |
There was a problem hiding this comment.
Cluster deployment has only two replica, but application request THIRD, wrap here would be something like 3 % 2 => 1. I think if we have wrap, then we need clamp too, which will give me something like max(bucket.num_replicas, replica), here max(2, 3) => 2.
also, how about case where the number of replicas satisfies the requested index, but the index resolves into node index -1? is this the same? should we wrap/clamp?
| #### GetReplica | ||
| Allows fetching a document via a replica read strategy. | ||
|
|
||
| todo: There will also be a LookupInReplica, but as it will largely duplicate this interface, to avoid churn it will be added later following initial review. |
|
|
||
| [Design notes](https://github.com/couchbaselabs/sdk-design/blob/main/2026/get-replica) (private to Couchbase employees) are available for optional further context. | ||
|
|
||
| Strategies can read multiple replicas, and the active, though of course per the API must ultimately return a single `GetReplicaResult`. |
| `wrap` edge-case: if the SDK somehow wraps all the way around to the starting point in the same loop (e.g. if the replica chain somehow contains all -1 entries), then raise a `ReplicaIndexCurrentlyUnavailableException`. | ||
|
|
||
| **couchbase2:// specific:** the SDK does not have the cluster topology, and each `GetReplica` call will result in a network call to CNG. | ||
| The CNG side is not implemented at time of this design. This design proposes that the GRPC will allow pushing down the strategy, including the `wrap` option, to allow the gateway to implement the above operational behaviour. |
| 2. Gets the value at the index, in one of two ways depending on the lookup type: | ||
| 1. If the result came from an `exists` lookup, calls Exists to get a boolean, and uses the JSON representation of that boolean as the "value" in the next step. Propagates any exceptions thrown by `Exists`. | ||
| 2. Otherwise, uses the value returned by the server as the "value" in the next step. If the status code at the index is not `SUCCESS`, throws an exception matching the status code. See [RFC-0058](0058-error-handling.md) for error definitions. | ||
| 1. If the result came from an `exists` lookup, calls Exists to get a boolean, and uses the JSON representation of that boolean as the "value" in the next step. Propagates any exceptions thrown by `Exists`. | ||
| 2. Otherwise, uses the value returned by the server as the "value" in the next step. If the status code at the index is not `SUCCESS`, throws an exception matching the status code. See [RFC-0058](0058-error-handling.md) for error definitions. | ||
| 3. Returns the value at the index, converted to the requested type. |
|
|
||
| - March, 2026 - Revision #21 (by Graham Pople) | ||
|
|
||
| - Added `getOrNull`. |
| Signature | ||
|
|
||
| ```csharp | ||
| IGetReplicaResult GetReplica(string id, GetReplicaStrategy strategy, [GetReplicaOptions options]) |
| b) a plugin-based flexible approach that allows Couchbase to add new single-replica strategies in the future without adding another Collection-level API. | ||
| c) solve the issues of the existing `GetAnyReplica` method. | ||
|
|
||
| [Design notes](https://github.com/couchbaselabs/sdk-design/blob/main/2026/get-replica) (private to Couchbase employees) are available for optional further context. |
No description provided.