Skip to content

Commit

Permalink
Change Phrasing, Grammar, and Fix Typos in files starting with letter…
Browse files Browse the repository at this point in the history
…s I-Z (#390)
  • Loading branch information
cjrkoa authored Apr 16, 2024
1 parent ed1bab5 commit af0264a
Show file tree
Hide file tree
Showing 10 changed files with 39 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
changelog.csv
*.jar
*.tmp
*.DS_store

**/__pycache__/
10 changes: 5 additions & 5 deletions join-process.typ
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

= Join Process <Chapter::JoinProcess>

There are 2 types of nodes in this architecture. The first type is the OTT Monolith, which refers to the current node.js monolithic server. The second type is the Smart Load Balancer, which needs to know which monoliths control which rooms, and how to route requests to the correct monolith.
There are 2 types of nodes in this architecture. The first type is the OTT Monolith, which refers to the current node.js monolithic server. The second type is the Smart Load Balancer, which needs to know which Monoliths control which rooms, and how to route requests to the correct Monolith.

For the sake of simplicity, the initial implementation of the Balancer will be a single node. This can be upgraded to a cluster of load balancers in the future.

Expand All @@ -15,7 +15,7 @@ For the sake of simplicity, the initial implementation of the Balancer will be a

In the Monolith, "loading" a room is literally creating an instance of a room in memory.

Shown in @Figure::join-room-happy-path, the client is joining a room that is already loaded on a Monolith node. First, the client initiates a websocket connection to the Balancer, in the form of a HTTP request with the GET method, and headers to indicate that a protocol upgrade is required. The client immediately sends an "auth" message to convey their identity to the Balancer. The Balancer looks at the path of the request and extracts the room name, and references it's internal hashmap to find the monolith that is hosting the room. The Balancer then opens a websocket connection using the auth token provided by the client.
Shown in @Figure::join-room-happy-path, the client is joining a room that is already loaded on a Monolith node. First, the client initiates a WebSocket connection to the Balancer, in the form of a HTTP request with the GET method, and headers to indicate that a protocol upgrade is required. The client immediately sends an "auth" message to convey their identity to the Balancer. The Balancer looks at the path of the request and extracts the room name, and references it's internal hashmap to find the Monolith that is hosting the room. The Balancer then opens a WebSocket connection using the auth token provided by the client.

If the client fails to provide an auth token, the Balancer must terminate the connection as "timed out".

Expand All @@ -30,7 +30,7 @@ Balancers must be able to determine which Monolith nodes are hosting which rooms

Monolith nodes must gossip to Balancer nodes to inform them of the rooms that they have loaded. This also implies that they must notify all Balancers of their existence on startup. The Balancer must maintain a hashmap of room names to Monolith nodes.

They must also maintain a hashmap of monolith nodes to a list of rooms that they are hosting to verify that only one monolith is hosting a room at a time. When the gossip is received, the Balancer must check to see if In the event that a Balancer finds that more than one Monolith is hosting a room, it must randomly select one of the Monoliths to be the authoritative node for that room, and inform the other Monoliths that they must unload the room. This method will not work as effectively if there is more than one Balancer, but it is a simple solution for the initial implementation.
They must also maintain a hashmap of Monolith nodes to a list of rooms that they are hosting to verify that only one Monolith is hosting a room at a time. When the gossip is received, the Balancer must check to see if, in the event a Balancer finds more than one Monolith is hosting a room, it must randomly select one of the Monoliths to be the authoritative node for that room, and inform other Monoliths that they must unload the room. This method will not work as effectively if there is more than one Balancer, but it is a simple solution for the initial implementation.

Monoliths must gossip:

Expand All @@ -50,7 +50,7 @@ The gossip message must contain a list of rooms that the Monolith is hosting (@F

The client will have three options: generating a temporary room with a uuid, creating a temporary room with inputs, and creating a permanent room that can be rejoined in the future.

The options follow similar sequence paths, as shown in @Figure::create-room-diag. The client starts a new Balancer connection through the websocket, then the Balancer is responsible for deciding which node is best fit to handle the additional room. Once the right one is picked, the room is instantiated on that Monolith and the client connects. Before proceeding with the client connection, the Balancer must wait for the Monolith to confirm that the room has been loaded. If the room is not loaded within a certain amount of time, the Balancer must terminate the connection as "timed out".
The options follow similar sequence paths, as shown in @Figure::create-room-diag. The client starts a new Balancer connection through the WebSocket, then the Balancer is responsible for deciding which node is best fit to handle the additional room. Once the right one is picked, the room is instantiated on that Monolith and the client connects. Before proceeding with the client connection, the Balancer must wait for the Monolith to confirm that the room has been loaded. If the room is not loaded within a certain amount of time, the Balancer must terminate the connection as "timed out".

Room generation requires no inputs from the client, instead a new uuid is automatically used as the room name. On the other hand, room creation has the client submit a set of inputs for the name and settings of the room. Generation only provides temporary rooms that are discarded after the room is unloaded. Creation can provide either temporary or permanent rooms, depending on the client inputs during the initial process. The settings for permanent rooms are stored in postgres even after being unloaded so that they persist and can be called upon to be reloaded at any point in the future.

Expand All @@ -70,7 +70,7 @@ Rooms are only kept loaded in memory if there are clients that are connected to

=== Monolith Node Selection <Section::MonolithNodeSelection>

The Balancer must be able to select the Monolith that is most appropriate to handle the join. Specifically,
The Balancer must be able to select the Monolith that is most appropriate to handle the join. Specifically:

- If the requested room is already loaded, the Balancer must select the Monolith that is hosting the room.
- Otherwise, the Balancer must select the Monolith with the lowest number of loaded rooms. The Monolith will load the room on demand, if it exists in the database.
Expand Down
10 changes: 5 additions & 5 deletions protocol.typ
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

== Messaging Protocol

The Monolith must maintain a maximum of one websocket connection to each Balancer.
The Monolith must maintain a maximum of one WebSocket connection to each Balancer.

The client communicates over the websocket with messages in JSON format, so the Balancer should do the same. The Balancer will send messages to the Monolith in the following format:
The client communicates over the WebSocket with messages in JSON format, so the Balancer should do the same. The Balancer will send messages to the Monolith in the following format:

```json
{
Expand Down Expand Up @@ -36,7 +36,7 @@ For broadcast messages, the Monolith can omit the `id` field.

=== Messages sent during Join and Leave

When a client opens a websocket connection, the client must immediately send an "auth" message. This is because the browser's websocket API does not allow for the client to send headers with the initial connection request.
When a client opens a WebSocket connection, the client must immediately send an "auth" message. This is because the browser's WebSocket API does not allow for the client to send headers with the initial connection request.

```json
{
Expand All @@ -45,7 +45,7 @@ When a client opens a websocket connection, the client must immediately send an
}
```

Once the the Balancer, receives this message, it must send a message to the Monolith to update the room's state.
Once the the Balancer receives this message, it must send a message to the Monolith to update the room's state.

```json
{
Expand Down Expand Up @@ -84,7 +84,7 @@ These messages conform to the the protocol defined by the #link("https://github.

== Messages sent during Monolith connection startup

When a Monolith starts up, and load balancing is enabled, it must listen on a separate port for incoming balancer connections. Balancers will initiate connections based on their configured Monolith discovery method. Upon accepting a new connection, the Monolith must send an "init" message to the Balancer to inform it of the port that it is listening for normal HTTP requests on, an auth token to verify authenticity, and a MonolithID to identify specific Monolith instances.
When a Monolith starts up and load balancing is enabled, it must listen on a separate port for incoming balancer connections. Balancers will initiate connections based on their configured Monolith discovery method. Upon accepting a new connection, the Monolith must send an "init" message to the Balancer to inform it of the port that it is listening for normal HTTP requests on, an auth token to verify authenticity, and a MonolithID to identify specific Monolith instances.

The MonolithID is generated as a UUID. While it's theoretically possible for UUIDs to be duplicated due to the finite space of possible values, the probability of such an event is so incredibly low that it's considered practically negligible.

Expand Down
14 changes: 7 additions & 7 deletions room-states.typ
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

= Maintaining Room State <Chapter::RoomState>

Room State#index[Room State] refers to the state of a Room, which includes, but is not limited to, the following:
Room State #index[Room State] refers to the state of a Room, which includes, but is not limited to, the following:
- Room Settings (title, description, visibility, queue mode, etc.)
- The current video
- Video Queue
Expand All @@ -14,18 +14,18 @@ In this document, when we say something is in a "good" or "healthy" state, we me

== Lost Balancer-Monolith Connections

When a Balancer-Monolith connection is lost, clients connected to that monolith need to be handled appropriately. Otherwise, the accuracy of room state is compromised.
All client connections that are in rooms on the lost monolith are no longer valid, so they need to be disconnected and removed
from their rooms. From there, they can reconnect to a monolith with a new websocket connection to the balancer.
When a Balancer-Monolith connection is lost, clients connected to that Monolith need to be handled appropriately. Otherwise, the accuracy of room state is compromised.
All client connections that are in rooms on the lost Monolith are no longer valid, so they need to be disconnected and removed
from their rooms. From there, they can reconnect to a Monolith with a new WebSocket connection to the Balancer.

== Duplicate Rooms Across Monoliths <Section::duplicate-rooms-across-monoliths>

As required by @Req::room-uniqueness, 2 Monoliths must never have the same room loaded, as the load balancer should be directing all connections for a given room to the designated monolith for that room.
As required by @Req::room-uniqueness, 2 Monoliths must never have the same room loaded, as the load balancer should be directing all connections for a given room to the designated Monolith for that room.
In the case that this does happen, the system is in a bad state and it must be resolved. This can occur as a race condition within the context of any number of Balancers. The planned solution to this issue is to have the load balancer unload rooms that were not the
first instance of that particular room. This means that the duplicate instances would be unloaded and the clients could then rejoin the room in a healthy state.

In order to accomplish this, every room must be associated with a "load epoch". The load epoch #index[load epoch] is a system global atomic counter that is incremented every time a room is loaded, maintained in redis.
When a room is loaded, the load epoch is incremented and the room is associated with the current load epoch. There is no need to ever reset the load epoch to 0. If the value rolls over, then the system will still function correctly. However, it must remain in the range of an unsigned 32 bit integer, because javascript does not support 64 bit integers #cite(<mdn-js-int>).
When a room is loaded, the load epoch is incremented and the room is associated with the current load epoch. There is no need to ever reset the load epoch to 0. If the value rolls over, then the system will still function correctly. However, it must remain in the range of an unsigned 32 bit integer, because JavaScript does not support 64 bit integers #cite(<mdn-js-int>).

Whenever the Balancer is notified of the room load and the room is already loaded, it checks to see if the load epoch of the new room is less than the load epoch of the existing room. If it is, then the old room is unloaded, clients are kicked, and the new room is treated as the source of truth.
Otherwise, the new room is unloaded and the old room is treated as the source of truth.
Expand All @@ -37,7 +37,7 @@ Otherwise, the new room is unloaded and the old room is treated as the source of

== Maintaining Room State Across Service Restarts <Section::state-across-restarts>

When OTT is deployed, the Monolith is restarted. In order to make this not disruptive to end users, room state is constantly being flushed to redis. When OTT starts up, it gets a list of all the rooms that were loaded and tries to restore their state. The problem is that if a monolith restarts, then it will load all the rooms that are in redis. In a deployment with more than one monolith, this results in rooms existing on more than one monolith.
When OTT is deployed, the Monolith is restarted. To not disrupt end users, room state is constantly being flushed to redis. When OTT starts up, it gets a list of all the rooms that were loaded and tries to restore their state. The problem is that if a Monolith restarts, then it will load all the rooms that are in redis. In a deployment with more than one Monolith, this results in rooms existing on more than one Monolith.

It's possible to simply allow the system to reach equilibrium using the mechanism described in @Section::duplicate-rooms-across-monoliths. However, this would result in a high memory usage on the Monolith upon startup. It would also result in a lot of unnecessary network traffic and load on redis.

Expand Down
10 changes: 5 additions & 5 deletions service-discovery.typ
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ There are three current implementations of the `ServiceDiscoverer` trait.
caption: "Class Diagram for Service Discoverers."
) <Figure::service-discoverers>

The first implementation is used when connecting to the instance of the monolith on fly.io. Before connecting the user inputs the port that the monolith should listen on for the load balancer connection and a string representing the fly app. These properties are both encapsulated by the `FlyDiscoveryConfig` struct.
The first implementation is used when connecting to the instance of the Monolith on fly.io. Before connecting the user inputs the port that the Monolith should listen on for the load balancer connection and a string representing the fly app. These properties are both encapsulated by the `FlyDiscoveryConfig` struct.

To discover the monolith, the configuration and a query are passed into the discoverer struct, `FlyServiceDiscoverer`. In order to connect to the instance on fly.io, an IPv6 DNS lookup is created using the query `global.<fly app name>.internal` #cite(<fly-dns-lookups>). The discovery process uses the results of the lookup and connects the balancer.
To discover the Monolith, the configuration and a query are passed into the discoverer struct, `FlyServiceDiscoverer`. In order to connect to the instance on fly.io, an IPv6 DNS lookup is created using the query `global.<fly app name>.internal` #cite(<fly-dns-lookups>). The discovery process uses the results of the lookup and connects the Balancer.

The second implementation is used when manually connecting to any instance excluding the one on fly.io and the discovery process works slightly differently. Any number of monolith connections, represented by the `ConnectionConfig` are passed into the manual discoverer. The discoverer then clones the monoliths and connects.
The second implementation is used when manually connecting to any instance excluding the one on fly.io and the discovery process works slightly differently. Any number of monolith connections, represented by the `ConnectionConfig` are passed into the manual discoverer. The discoverer then clones the Monoliths and connects.

`HarnessServiceDiscoverer` is the third implementation and is used for testing with the harness. The discoverer opens a port and listens for incoming websocket connections. When a connection is made, the discoverer listens for a message from the harness dictating all the monoliths that are visible to the balancer.
`HarnessServiceDiscoverer` is the third implementation and is used for testing with the harness. The discoverer opens a port and listens for incoming WebSocket connections. When a connection is made, the discoverer listens for a message from the harness dictating all the Monoliths that are visible to the Balancer.

The fourth implementation comes in the form of `DnsServiceDiscoverer` when connecting the monolith to the docker dns server. This process follows a similar procedure to that of `FlyServiceDiscoverer` but queries IPv4 addresses instead of IPv6 addresses as Docker does not support IPv6 addresses.
The fourth implementation comes in the form of `DnsServiceDiscoverer` when connecting the Monolith to the Docker DNS server. This process follows a similar procedure to that of `FlyServiceDiscoverer` but queries IPv4 addresses instead of IPv6 addresses as Docker does not support IPv6 addresses.
Loading

0 comments on commit af0264a

Please sign in to comment.