MatrixAI / Relay

Service-Centric Networking for Matrix Automatons
0 stars 0 forks source link

Network Composition Operators (Implementation) #17

Open CMCDragonkai opened 5 years ago

CMCDragonkai commented 5 years ago

This issue tracks the implementation of network composition operators that can be specified in Architect. These are only relevant to connections between Automatons.

Here are some inspirations:

Implementation may involve a combination of low level network implementation and also "hidden" automatons. Although they may end up not actually being hidden, just abstracted via operator overloading.

CMCDragonkai commented 5 years ago

Having too many symbolic operators will be hard to read, so there should be human readable analogues of these things. But the most commonly used network combinator operators can given symbols since it will be faster.

Possible operators to use:

Load Balancing has many variations, so it's not a simple thing:

Bounded channels also has variations:

More variations can be found here:

Consider asynchronous vs synchronous communication.

ramwan commented 5 years ago

To clarify the above a bit more, we can look at examples in Haskell, letting automatons be simulated by functions.

$> ghci
Prelude> let b = \x -> x + 1              # automaton b
Prelude> let a = \b x -> b x + 1          # automaton a
Prelude> c = a b                          # a has a static dependency on b
Prelude> let b2 = \x -> x + 2             # automaton b2
Prelude> c = a (b . b2)                   # composition of automatons
Prelude> let (|>) = ( . b2)               # sample load balancing operator (eg. round robin)
Prelude> c = a ((|>) b)                   # a depending on a (round robin) load balanced b

At the Architect language level, devs would be interacting with underlying datastructures in a very abstract way as demonstrated above where an automaton a's requests to b are load balanced and a depends on b to function.

CMCDragonkai commented 5 years ago

We need to consider how the above network combinators relate to zero down-time QoS requirements. Also whether certain operators allow autoscaling or that certain operators don't allow autoscaling. We need to consider what is something that the human operator needs to decide, and what can be automatically derived (and avoid leaky abstractions).

ramwan commented 5 years ago

A potential issue to think about is how developers may dictate arbitrary Automaton communication flows. For example, let's use the following syntax to denote simple communication flows.

=>        request-response from left
<=        request-response from right
->        push
<-        pull

Following is a simple example where A sends a message to B which sends a message to C which sends a response back to A. A->B->C->A The developer should be able to dictate whether it's important that the message returns to the same A or whether any A is ok to receive this message.

This concept can be further extended to compositions such as A->B->C->D->B->A Maybe something like . can be used to donate the same automaton as used already and + be used to show any automaton can receive the message.

For example, the above example could be A -> B -> C -> D -> B+ -> A. showing that D can send it's message to any B but that B must in turn send its message to the original A, possibly as this A was where a user sent their request.

Yes this use of . and + is rather ugly but I think the idea is interesting. Would also bring up some automaton network implementation issues which I haven't enumerated yet.

CMCDragonkai commented 5 years ago

After some discussion, here are some additional considerations.

We must distinguish between closure dependencies and composition.

Composition is usually with regards to independent Automatons that are composed together into a new Automaton. The Automatons involved in composition are not aware of each other during their definition.

However when a Automaton is defined/declared using the Automaton {...} syntax, it may specify things as dependencies. In this case the very definition of such an Automaton involves connections to other dependent Automatons.

So the underlying Relay network has to support both possibilities.

Note that this blog post examines the difference: https://matrix.ai/2014/12/13/functionally-composing-service-oriented-architecture/

It's also important to consider that when we are fulfilling dependencies lazily or statically, we can fulfill the dependency with qualifiers applied to their connection. One idea is that load balancing is actually a "hidden" automaton that is composed on another Automaton, and the resulting composition is used as an Automaton to fulfill a dependency requirement.

Therefore circles and arrow diagrams can be confusing if we are distinguishing between connections formed from the dependency specification and connections formed by an explicit composition.

On another issue, fan out has a split variant vs a copy variant:

fanout(A, [B, C])

        a
      +----> B
   a  |
A +---+
      | a
      +----> C

split(A, [B,C])

           a
         +----> B
   (a,b) |
A +------+
         | b
         +----> C

Fan out is defined as copying the output of A and passing both into B and C, and this can be scaled up to fan out into further Automatons.

Whereas split requires understanding of the response output itself, understanding the structure that is. The structure must be something that is "splittable". In the case of FRP, this usually uses the simple tuple example. Where A is a function producing a signal of tuples, and the split splits the tuples to different sides.

Not every combinator will be a binary operator, as you can see with fanout and split. We also need to consider whether to use a sequence of operators (like fanout(fanout(fanout())) or allow variadic or list based operators. Not sure if these are equivalent.

CMCDragonkai commented 5 years ago

https://en.wikibooks.org/wiki/Haskell/Understanding_arrows I like the diagrams!

CMCDragonkai commented 5 years ago

Today I found another example of network composition. Converting a Push Automaton to a Pull Automaton or converting a Pull Automaton to a Push Automaton.

For example a push Automaton is a like an HTTP serving Automaton. Now imagine another Automaton offering polling/callback endpoints, which is designed for a pulling Automaton to pull tasks out. Normally we cannot compose these Automatons. But if we have a network combinator that polls/pulls tasks and converts them to HTTP requests out, then it's possible to compose these 2 Automatons together.

I realised this, when I was creating a HTTP microservice that would have low scalability, and low capacity to do backlog or queuing. So rather than pushing tasks to the workers (which can cause all sorts of problems, like overloading the backlog, dropping requests, failed requests... etc), it would be far more efficient for the system to pull tasks when they are ready to process another task. However hardcoding the pulling would make the service less composable and modular, and add extra complexity to the microservice.