Skip to content

Commit

Permalink
Merge pull request #77 from BarryCarlyon/conduit
Browse files Browse the repository at this point in the history
WIP Conduits examples and notes
  • Loading branch information
BarryCarlyon authored Apr 15, 2024
2 parents d096653 + d379ce8 commit 379e7a5
Show file tree
Hide file tree
Showing 8 changed files with 1,533 additions and 0 deletions.
189 changes: 189 additions & 0 deletions eventsub/conduits/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Twitch Conduits

Work in Progress free form notes

# Documentation

- [Get Conduits](https://dev.twitch.tv/docs/api/reference/#get-conduits)
- [Create Conduits](https://dev.twitch.tv/docs/api/reference/#create-conduits)
- [Update Conduits](https://dev.twitch.tv/docs/api/reference/#update-conduits)
- [Delete Conduits](https://dev.twitch.tv/docs/api/reference/#delete-conduit)
- [Get Conduit Shards](https://dev.twitch.tv/docs/api/reference/#get-conduit-shards)
- [Update Conduit Shards](https://dev.twitch.tv/docs/api/reference/#update-conduit-shards)

- [Create EventSub Subscription](https://dev.twitch.tv/docs/api/reference/#create-eventsub-subscription)
- [Delete EventSub Subscription](https://dev.twitch.tv/docs/api/reference/#delete-eventsub-subscription)
- [Get EventSub Subscriptions](https://dev.twitch.tv/docs/api/reference/#get-eventsub-subscriptions)

# Tools

[Barry's Twitch Conduit Tools](https://github.com/barrycarlyon/twitch_conduit_tools) can help you visualise your Conduits and Shards

# Whats here

It's all Websocket examples:

## `auto_shard.js`

- Creates a WebSocket
- If set to auto will create a Conduit if one doesn't exist OR will use the ONLY conduit that exists
- It assigns itself to the `SHARD_ID` assinged from env, which generally is gonna be shard 0 as if it auto creates it only makes a conduit of 1 shard.

It assumes another process handles subscription creation.

It's not _that_ practical but theres use cases for this. As after connect and the conduit/single shard is ready then the same process can create (or check) that all needed subscriptions are created and assigned to the conduit.

Or you just need to spawn a socket against the ONLY created conduit you have.

## `shard.js`

This one is more practical/expected use cases.

You define the `TWITCH_CONDUIT_ID` and `TWITCH_SHARD_ID` then it creates a WebSocket and assigns itself to the defined Shards.

You might do a auto scale up version, where it iterates the shards on the conduit to find a disconnected/available slot and self assigns itself.
And if it doesn't find a shard self creates a shard and self assigns.

But if you have multiple disconnected shards you have missed events as Twitch only retries on the message on another shard once, so if the second shard is also dead, RIP the entire message

## A practical flow

For a NEW setup

1. Create a conduit, with Y shards
2. Create Y websocket processes each assigned the `CONDUIT_ID` and a `SHARD_ID` to go on, on `welcome` they do the PATCH call themself for their `SHARD_ID`
3. Create Subscriptions to your Conduit

This should ensure you are ready to recieve messages, before messages occur, but sure you could create conduit of 1 shard, connect 1 shard and then create subscriptions on the Conduit and from there scale up if you get a lot of traffic.

### Practical Flow example

Another process handles token gernation and conduit create and subscription assign

1. Externally create a conduit
2. Assign subscriptions to that conduit
3. `TWITCH_ACCESS_TOKEN="TOKEN" TWITCH_CONDUIT_ID="conduit-id" TWITCH_SHARD_ID="0" node shard_simple.js`

And away you go, so the bare minimum data is to tell the process a token to use, a conduit to go on and which shard it is. If you were loading a token and "active conduit ID" from storage that can be done in file.

So it just needs a shard, or if you are just filling for dead shards, iterate shards till you find a disconnect shard to self assign to.

And you don't even need to pass in the ClientID as the ClientID is obtained from validating the App Access Token. But if you did it would check they both matched

# Sharding

When you update the shard count subscriptions are rebalanced between the shards, you have no control over whose subscriptiosn go where.

Subscription to Shard balancing is done via the broadcaster ID in the condition of the subscription.

So all Subscription types for a given broadcaster will be sent to the same shard, but you the developer have no control over broadcaster/shard assignment.

If a shard dies due to websocket disconnection/webhook death, it doesn't rebalance the shards, the message is just retried once on another shard.

So in a two shard conduit, where one of the shards is state `websocket_disconnected` all messages will end up on the other shard.

In a three shard contuit, where two of the shards is state `websocket_disconnected` you might lose messages if messages for shard 3 go to shard 2 and both shard 3 and 2 are dead but shard 1 is the alive one.

So you would want to be consuming [Conduit Shard Disabled](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#conduitsharddisabled) and possibly auto updating your shards to take out the dead shards from the stack by an [PATCH to update shards](https://dev.twitch.tv/docs/api/reference/#update-conduit-shards) and decrease the (shard count on the conduit](https://dev.twitch.tv/docs/api/reference/#update-conduits)

# Limits and Death notes

A given Client ID can have to up 5 Conduits

A given Conduit can have between 1 and 20,000 shards

You assign a Conduit x shards but remember they are 0 indexed, so a Conduit of 1 shard the shard ID is 0

Subscription limits and costings is the same as "regular" EventSub

A Subscription Type exists to help track a Shard dying unexpectedly, [Conduit Shard Disabled](https://dev.twitch.tv/docs/eventsub/eventsub-subscription-types/#conduitsharddisabled)

As per the [Guide](https://dev.twitch.tv/docs/eventsub/handling-conduit-events/)

> NOTE A Webhook shard follows the same disabling logic as a normal Webhook, and is only disabled after an extended outage of consecutive failures. If a notification is sent to an active Webhook shard and the Webhook callback fails, the notification will not be resent to another shard.
For a Shard assigned to a WebSocket it dies the moment the WebSocket Disconnects.

If a message fails to send to a given shard it is retried _ONLY ONCE_ on another Shard.

IMO the general use case of a Conduit is to attach the shards to WebSockets, generally utilising the Chat topics to instantly reconnect a chat bot to the rooms the chat bot needs to be in.

But it works real nice to anything you want to connect a lot of subscriptions to even a single socket connection ala Deprecated PubSub

# Scaling

## Conduit Scale up

1. [Update Conduits](https://dev.twitch.tv/docs/api/reference/#update-conduits) to increase the shard count
2. [Assign the new shard(s)](https://dev.twitch.tv/docs/api/reference/#update-conduit-shards) to transports

## Conduit Scale Down

### Scale down from the end

#### WebSockets

1. Disconnect WebSockets from the end of the list
2. [Update Conduits](https://dev.twitch.tv/docs/api/reference/#update-conduits) to decrease the shard count

Steps could be done in either order here for websockets, as when you disconnect it will auto update that shard to dead. Where as Webhooks is problemtatic due to retry rules

#### Webhooks

1. [Update Conduits](https://dev.twitch.tv/docs/api/reference/#update-conduits) to decrease the shard count

### Scale down from the middle

For Example: If you have 10 shards and want to kill shard 5

> 0 index remember
1. Copy Shard 9 to shard 4 via [Update Conduit Shards](https://dev.twitch.tv/docs/api/reference/#update-conduit-shards)
2. [Update Conduits](https://dev.twitch.tv/docs/api/reference/#update-conduits) to decrease the shard count

# Maintenance

## Webhooks

you wanna take shard 5 offline for a bit? Copy shard 10 to shard 5, the decrease shard count, when done increase shard count again.

If you take a webhook offline, Twitch will retry on the same shard, due to the webhook retry logic, rather than retrying on another shard.

## Websockets

just disconnect the websocket Twitch will retry on another BUT ONLY ONCE

If you want to take more than one Websocket offline for maintenance you should Update Conduits to take the effected shards out/moved to another shard.

You can have multiple shards assigned to the same logical endpoint/sessionID.

### HandOver a socket

On a conduit of one shard, doing a hand over theres two approaches

1. Make a new Socket
2. Update shard 0 to the new socket

This shouldn't incur any downtime/missed events

or

1. Make a new socket
2. Increase shard count by 1 (so 2 shards)
3. Assign the new socket to shard 1
4. When happy new socket is read swap shard 1 to shard 0 and shard 0 to shard 1 in the same PATCH request
5. When happy disconnect (the new shard 1 that was originall shard 0) shard 1 and descrease shard count back to 1

The second method is more invasive but can mean you don't lose any events whilst handovering

# Userland

Since a conduit uses App Access Tokens to operate generally you wouldn't expose them in userland.

But theres nothing really stopping you doing it in a way that protects the App Access Token from visiblity.

And you can connect a given Transport to a Conduit _and_ also susbcribe non conduit events to the transport.

I run a WebSocket consumer on my home machine that at boot connects to a Shard _and_ connects non conduit/shard topics to the same transport.

In this case it's some chat topics where I don't have the relevant ["Conduit Scopes"](https://github.com/BarryCarlyon/twitch_misc/tree/main/eventsub/websockets/web/chat#how-does-authentication-work) on the target channels.
Loading

0 comments on commit 379e7a5

Please sign in to comment.