Skip to content

Latest commit

 

History

History
67 lines (39 loc) · 4.98 KB

architecture.md

File metadata and controls

67 lines (39 loc) · 4.98 KB

Architecture

Overview

AnyCable arhictecture AnyCable arhictecture

AnyCable real-time server (WS, or WebSocket, since it's a primary transport) is responsible for handling clients, or connections. That includes:

  • low-level connections management
  • subscriptions management
  • broadcasting messages to clients

WebSocket server should include gRPC client built from AnyCable rpc.proto or a compatible HTTP implementation (for RPC over HTTP).

RPC server is a connector between the Ruby application (e.g. Rails) and WebSocket server. It’s an instance of your application with a gRPC endpoint which implements rpc.proto. This server is a part of the anycable CLI.

NOTE: It's also possible to use an HTTP RPC, which can be embedded into your main web server (e.g. Puma). Thus, you can avoid running a separate RPC server process.

The application publish broadcast messages to the WebSocket server (directly via HTTP or via some queuing service, see broadcast adapters). In case of running a WebSocket cluster (multiple nodes), there is also can be a Pub/Sub service responsible for re-transmitting broadcast messages between nodes. You can use embedded NATS as a pub/sub service to miminalize the number of infrastructure dependencies. See Pub/Sub documentation for other options.

State management

AnyCable's is different to the most WebSocket servers in the way connection states are stored: all the information about client connections is kept in WebSocket server; an RPC server operates on temporary, short-lived, objects passed with every gRPC request.

That means, for example, that you cannot rely on instance variables in your channel and connection classes. Instead, you should use specified state objects, provided by AnyCable (read more about channel states).

A client's state consists of three parts:

  • connection identifiers: populated once during the connection, read-only (correspond to identified_by in Action Cable);
  • connection state: key-value store for arbitrary connection metadata (e.g., tagged logger tags stored this way);
  • channel states: key-value store for channels (for each subscription).

This is how AnyCable manages these states under the hood:

  • A client connects to the WebSocket server, Connect RPC is made, which returns connection identifiers and the initial connection state. All subsequent RPC calls contain this information (as long as underlying HTTP request data).
  • Every time a client performs an action for a specific channel, the channel state for the corresponding subscription is provided in the RPC payload.
  • If during RPC invocation connection or channel state has been changed, the changes are returned to the WebSocket server to get merge with the full state.
  • When a client disconnects, the full channel state (i.e., for all subscriptions) is included into the corresponding RPC payload.

Thus, the amount of state data passed in each RPC request is minimized.

Restoring state objects

A state is stored in a serialized form in WebSocket server and deserialized (lazily) during each RPC request (in Rails, we rely on GlobalID for that).

This results in a slightly different behaviour comparing to persistent, long-lived, state.

For example, if you use an Active Record object as an identifier (e.g., user), it's reloaded in every RPC action it's used.

To use arbitrary Ruby objects as identifiers, you must add GlobalID support for them (see AnyCable setup demo).

WebSocket servers

Since AnyCable uses a well-defined protocol for communication between a WebSocket server and a primary web application (e.g., Rails), any WebSocket server that implements AnyCable gRPC or HTTP API can be used.

Since v1.0 the only officially supported (i.e., recommended for production usage) server is AnyCable-Go (written in Golang). AnyCable-Go also supports alternative transports such as Server-Sent Events and long-polling.

For older versions you can still use erlycable (Erlang).

We also have a server written in Ruby–AnyCable Rack Server–which could be used for local experiments to emulate the same architecture as with real AnyCable server.

See the demo of how you can use anycable-rack-server to run system tests.

If you're not happy with the above implementations, you can build your own AnyCable-compatible server.