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

[question] Send history (last message) on initial connect? #745

Open
BusterNeece opened this issue Dec 4, 2023 · 8 comments
Open

[question] Send history (last message) on initial connect? #745

BusterNeece opened this issue Dec 4, 2023 · 8 comments

Comments

@BusterNeece
Copy link

Using Centrifugo v5

In our application, the way we use Centrifugo is as a high-performance way of sending "now playing" updates to players listening to Internet radio stations.

In our case, we don't end up using the more complicated parts of Centrifugo (bidirectional traffic, etc.) and instead just use a simple anonymous unidirectional SSE/websocket broadcast that anyone can connect to, as the info contained within it is public.

One problem we run into, though, is that when a user initially connects, we'd ideally like to immediately send them the last message that was posted to that channel. I've set up history on the channels to be one message in size, forced recovery, and passed the "recover: true" parameter in cf_connect, but absent the specific epoch/offset, it doesn't actually send the message history back.

Is there a way to accomplish this in Centrifugo as-is? If not, would it be possible to add it?

@FZambia
Copy link
Member

FZambia commented Dec 5, 2023

Hello, @BusterNeece

I think it's not possible with Centrifugo as-is. For now, you can manually call history API with limit: 1, reverse: true to get last message in channel history. But it's more code, more synchronization and you are not using bidirectional client so the request should go over the backend.

We can try to add this feature - need some time to think on possible ways to configure this. Option for a channel namespace which enables recovery of last message only should be sufficient, right? If message exists in history – recovery will be indicated as successful, if missing – unsuccessful.

UPD. Or probably client should decide which recovery mode to use - since some offset or using last message mode.

@BusterNeece
Copy link
Author

@FZambia Yeah, I think if the cf_connect param (or the corresponding connect command on the websocket side) could include a recovery request a-la reconnections in such a way that the client could say "send me the last message if you have it" (we'd only ever need the most recent message, not any of the ones farther back in history, in our case).

That would really be phenomenal for our use-case.

@BusterNeece
Copy link
Author

As a note, we've worked around this for now by setting up an on-connect handler that points back at our (PHP) web application. Its response includes the "initial" load for the channels the user subscribes to, so they can count on an immediate first data set upon the initial response. It somewhat diminishes the "high-performance" nature of Centrifugo in our use-case, as every new initial connection is also a PHP request, but subsequent requests don't require such a thing and use significantly fewer resources (and since it's removing the need for the client-side JS to make x API requests to populate the initial data, it's really closer to a zero-sum thing for us really).

@FZambia
Copy link
Member

FZambia commented Dec 26, 2023

@BusterNeece hello! Spent some time thinking, making recovery to return the latest publication from channel history is definitely possible. The devil is in details though, and I am trying to understand how to implement a feature with a convenient behaviour in edge cases.

The question is – what is the desired behavior when the latest message was not found by Centrifugo in its history?

What I can imagine:

  • Centrifugo can just return was_recovering:true and recovered: false flags in channel subscribe reply. Like it does for offset-based recovery now. Client will need to load the state from the backend in this case I suppose. Which makes client more complicated.
  • probably Centrifugo needs to provide a way to fill the history with the latest data in that case. Centrifugo could call some endpoint of the backend upon every latest message load miss like this - and fail subscription entirely until channel history is populated by the backend (by publishing data to the channel from the backend to Centrifugo API). This may not be a good thing for unidirectional approach you have as one channel will affect the entire connection – in bidirectional case we have subscription objects decoupled from connection so complex behavior and individual re-subscriptions are possible.
  • Probably for such cases you don't need to do anything since channel will be quickly populated by itself

What do you think about this from your use case perspective?

@BusterNeece
Copy link
Author

@FZambia I mean, in my case I'd be 100% okay with just passing some flag that informs me that history couldn't be prepopulated, because as soon as history exists for a given record, it'll be sent to Centrifugo that very same second, so if Centrifugo doesn't have history, then neither will my backend app.

But I can absolutely see a use-case for adding a hook to proxy to the backend to populate this history if it doesn't exist. It just wouldn't be necessary for my use-case.

@FZambia FZambia changed the title [Question] Send history on initial connect? [question] Send history (last message) on initial connect? Jan 21, 2024
@BusterNeece
Copy link
Author

@FZambia Hey, so I'm finally getting around to playing with this in our application, and it seems my config isn't parsing right and I'm not quite sure why. Mind taking a look?

Here's our TOML (don't mind the {{ }} business, that's Dockerize templating)

allow_anonymous_connect_without_token = true
api_insecure = true
admin = true
admin_insecure = true
port = 6_020
internal_port = 6_025
websocket_disable = true
uni_websocket = true
uni_sse = true
uni_http_stream = true
grpc_api = true
grpc_api_port = 6_301
force_recovery = true
allowed_origins = ["*"]

{{ if isTrue .Env.ENABLE_REDIS }}
engine = "redis"
redis_address = "{{ .Env.REDIS_HOST }}:{{ default .Env.REDIS_PORT "6379" }}"
redis_db = 0
{{ end }}

[[namespaces]]
name = "station"
history_size = 1
history_ttl = "30s"
allow_subscribe_for_client = true
allow_subscribe_for_anonymous = true
allow_history_for_client = true
allow_history_for_anonymous = true

[[namespaces]]
name = "global"
history_size = 1
history_ttl = "2m"
allow_subscribe_for_client = true
allow_subscribe_for_anonymous = true
allow_history_for_client = true
allow_history_for_anonymous = true

The error I'm getting is:

"error validating config: both history size and history ttl required for recovery"

I suppose it's looking at the global config and seeing it's missing, but it's set on each channel level...do I need to set the "force_recovery" on the per-channel level for it to work right?

@BusterNeece
Copy link
Author

Update: After moving force_recovery to the per-channel config, it validated correctly...but I can't get it to actually "recover" things. Basically, I don't have any way of knowing what epoch it's currently on, I just want it to send what it has in history regardless of whether I have a valid epoch/offset. Is there a way to do that?

@FZambia
Copy link
Member

FZambia commented Mar 18, 2024

@BusterNeece hey, did you try from the branch I sent you in Discord?

In general, I want to pay more attention to this in next months, this all is very similar to https://pusher.com/blog/introducing-cache-channels/ - could you take a look at this link, does it look very similar to what you want?

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

No branches or pull requests

2 participants