Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server-side implementation of incremental subscription changes #2030

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

jsdt
Copy link
Contributor

@jsdt jsdt commented Dec 2, 2024

Description of Changes

This adds the new message types for being able to subscribe and unsubscribe to individual queries without affecting other subscriptions. Much of this was taken from #1997, but this version does not remove the existing way of subscribing to a set of queries. This renames that to legacy_ in many places, and we eventually want to get rid of it, but by making this an additive change we don't need to update all the clients at the same time.

This biggest changes are in the SubscriptionManager code. Previously we just stored two mappings:

  1. Query hash to the set of clients subscribing, and
  2. Table id to the set of related queries

Now we also store a mapping from client to the set of subscriptions for that client (which is useful for unsubscribing), and we keep track of both legacy subscriptions (which have a set of subscriptions per client), and the new subscriptions (which are identified by a (ClientId, RequestId) pair).

API and ABI breaking changes

This is additive, and only adds new enum types to existing messages, so this is not breaking. In the future we will want to make a breaking change to remove the legacy versions.

Expected complexity level and risk

  1. This will add some complexity to the client, but this is mostly just tweaking the existing logic.

Testing

This passes existing tests (which exercise the legacy versions), and there are a few basic tests for the new version.

More unit test coverage for incremental subscription changes would be good, but I will probably add that when I start adding client support for it.

Follow-up work

We still need:

  1. Client support for this.
  2. More testing
  3. Handling of module hot swapping. If a module is republished, and a change to indexes breaks any queries, we need to send errors to clients for those queries.

@jsdt jsdt marked this pull request as ready for review December 2, 2024 20:29
@jsdt jsdt requested review from Centril and gefjon as code owners December 2, 2024 20:29
pub query: Box<str>,
/// An identifier for a client request.
/// This should not be reused for any other subscriptions on the same connection.
/// TODO: Can we call this subscription_id? It feels odd that this request_id will be used for multiple messages (the Unsubscribe call).
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there's some confusion here due to a typo in the proposal. The intended design is:

  • Each request/response pair has a unique request_id, assigned by the client. It is not an error for the client to re-use request_ids, as their only purpose is to correlate a request/response pair; they have no semantic meaning to the host.
  • This means that Unsubscribe gets a unique request_id distinct from the one in SubscribeSingle.
  • The host looks at the QueryId included in the Unsubscribe message to determine which query is being removed.

It looks like at some point, someone (almost certainly me) made a copy-paste error in the proposal:

/// Server response to a client `Unsubscribe` request.
struct UnsubscribeApplied {
    /// Provided by the client via the `Subscribe` message.
    request_id: u32,

This should say, "Provided by the client via the Unsubscribe message." That is, the host treats it as an opaque nonce, and does not retain it or recognize it after sending its response.

Sorry for the confusion!

Comment on lines 235 to 236
/// TODO: I assume this is just used for debugging?
pub query_id: QueryId,
Copy link
Contributor

Choose a reason for hiding this comment

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

See previous comment. This query_id is (supposed to be) the thing you want to call subscription_id.

crates/client-api-messages/src/websocket.rs Outdated Show resolved Hide resolved
crates/client-api-messages/src/websocket.rs Outdated Show resolved Hide resolved
Comment on lines 332 to 333
/// TODO: How are we supposed to handle errors without a request_id?
/// Should we drop all subscriptions?
Copy link
Contributor

Choose a reason for hiding this comment

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

When the SDK receives a SubscriptionError message:

  1. The client state is updated such that all SubscriptionHandles are ended.
  2. All rows are removed from the client cache.
  3. on_delete callbacks are invoked.
  4. on_error callbacks are invoked.

NOTE: If a SubscriptionError is returned in response to a Subscribe,
only that particular SubscriptionHandle is ended.
That is, all other active queries shall remain active.
No on_delete callbacks are invoked.

@@ -103,6 +103,9 @@ pub enum SubscriptionError {
SideEffect(Crud),
#[error("Unsupported query on subscription: {0:?}")]
Unsupported(String),
// TODO: Is this correct? Check if you can have multiple queries for the same table in a single call.
Copy link
Contributor

Choose a reason for hiding this comment

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

You can have multiple queries for the same table. They should be added in distinct SubscribeSingle calls, but I suppose the legacy Subscribe can still add several of them at once. Historically we have imposed that they must be disjoint, but the new design as proposed includes client-side handling to allow overlapping queries.

@gefjon gefjon assigned jsdt and unassigned gefjon Dec 3, 2024
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.

Subscriptions: New WebSocket API and server side handling
2 participants