Skip to content

Adding GetReplica API#161

Open
programmatix wants to merge 6 commits into
masterfrom
get-replica
Open

Adding GetReplica API#161
programmatix wants to merge 6 commits into
masterfrom
get-replica

Conversation

@programmatix

Copy link
Copy Markdown
Contributor

No description provided.

Comment thread rfc/0053-sdk3-crud.md
```

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread rfc/0053-sdk3-crud.md Outdated
Comment thread rfc/0053-sdk3-crud.md

- Throws
- Documented
- DocumentNotFoundOnReplicaException

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@programmatix programmatix Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@DemetrisChr DemetrisChr Jul 17, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds reasonable, especially considering that eventually we might also be fetching the active from here. I think I'm convinced

Comment thread rfc/0053-sdk3-crud.md Outdated

This comment was marked as low quality.

Comment thread rfc/0053-sdk3-crud.md

GetReplicaStrategyFromIndexOptions options:

- `Boolean wrap` - whether to wrap around the available replicas, rather than throwing `ReplicaIndexOutOfBoundsException`. Defaults to false.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would a user want to wrap instead of being told the replica doesn't exist?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Comment thread rfc/0053-sdk3-crud.md
Comment thread rfc/0053-sdk3-crud.md
Comment thread rfc/0053-sdk3-crud.md

GetReplicaStrategyFromIndexOptions options:

- `Boolean wrap` - whether to wrap around the available replicas, rather than throwing `ReplicaIndexOutOfBoundsException`. Defaults to false.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread rfc/0053-sdk3-crud.md

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 7 comments.

Comment thread rfc/0053-sdk3-crud.md
#### 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.
Comment thread rfc/0053-sdk3-crud.md

[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`.
Comment thread rfc/0053-sdk3-crud.md
`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.
Comment thread rfc/0053-sdk3-crud.md
Comment on lines 1311 to 1314
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.
Comment thread rfc/0053-sdk3-crud.md

- March, 2026 - Revision #21 (by Graham Pople)

- Added `getOrNull`.
Comment thread rfc/0053-sdk3-crud.md
Signature

```csharp
IGetReplicaResult GetReplica(string id, GetReplicaStrategy strategy, [GetReplicaOptions options])
Comment thread rfc/0053-sdk3-crud.md
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants