Skip to content

Commit

Permalink
Update sections in the docs
Browse files Browse the repository at this point in the history
  • Loading branch information
sleipnir committed Dec 5, 2022
1 parent d731104 commit 58a2255
Showing 1 changed file with 97 additions and 97 deletions.
194 changes: 97 additions & 97 deletions spawn_sdk/spawn_sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,103 @@ Notice that the only thing that has changed is the the kind of actor, in this ca

> **_NOTE:_** Can Elixir programmers think in terms of singleton vs abstract actors as more or less known at startup vs dynamically supervised/registered? That is, defining your actors directly in the supervision tree or using a Dynamic Supervisor for that.
## Pooled Actors

Sometimes we want a particular actor to be able to serve requests concurrently,
however actors will always serve one request at a time using buffering mechanisms to receive requests in their mailbox and serve each request one by one.
So to get around this behaviour you can configure your Actor as a Pooled Actor, this way the system will generate a pool of actors to meet certain requests. See an example below:

```elixir
defmodule SpawnSdkExample.Actors.PooledActor do
use SpawnSdk.Actor,
name: "pooled_actor",
kind: :pooled,
stateful: false

require Logger

defact ping(_data, %Context{} = ctx) do
Logger.info("Received Request. Context: #{inspect(ctx)}")

Value.of()
|> Value.void()
end
end
```

> **_NOTE:_** It is important to mention that Pooled Actors cannot be stateful because concurrent accesses for Stateful actors could generate undesired inconsistencies.
To invoke a pooled actor, it is necessary to pass the pooled: true option to the invoke function. See an example:

```elixir
iex(spawn_a1@127.0.0.1)16> for _n <- 0..8, do: SpawnSdk.invoke("pooled_actor", system: "spawn-system", command: "ping", pooled: true)
```

In the logs you will see that even though you have invoked the actor by the registered name more than one instance of the actor will be created. As we can see below:

```elixir
iex(spawn_a1@127.0.0.1)16> for _n <- 0..8, do: SpawnSdk.invoke("pooled_actor", system: "spawn-system", command: "ping", pooled: true)

2022-11-25 11:21:53.758 [spawn_a1@127.0.0.1]:[pid=<0.1392.0> ]:[notice]:Activating Actor pooled_actor-5 with Parent pooled_actor-5 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.759 [spawn_a1@127.0.0.1]:[pid=<0.1391.0> ]:[debug]:Actor pooled_actor-5 reactivated. ActorRef PID: #PID<0.1392.0>
2022-11-25 11:21:53.760 [spawn_a1@127.0.0.1]:[pid=<0.1392.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-5", system: "spawn-system", parent: "pooled_actor-5", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.763 [spawn_a1@127.0.0.1]:[pid=<0.1394.0> ]:[notice]:Activating Actor pooled_actor-4 with Parent pooled_actor-4 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.763 [spawn_a1@127.0.0.1]:[pid=<0.1393.0> ]:[debug]:Actor pooled_actor-4 reactivated. ActorRef PID: #PID<0.1394.0>
2022-11-25 11:21:53.765 [spawn_a1@127.0.0.1]:[pid=<0.1394.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-4", system: "spawn-system", parent: "pooled_actor-4", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.767 [spawn_a1@127.0.0.1]:[pid=<0.1396.0> ]:[notice]:Activating Actor pooled_actor-7 with Parent pooled_actor-7 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.767 [spawn_a1@127.0.0.1]:[pid=<0.1395.0> ]:[debug]:Actor pooled_actor-7 reactivated. ActorRef PID: #PID<0.1396.0>
2022-11-25 11:21:53.768 [spawn_a1@127.0.0.1]:[pid=<0.1396.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-7", system: "spawn-system", parent: "pooled_actor-7", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.770 [spawn_a1@127.0.0.1]:[pid=<0.1398.0> ]:[notice]:Activating Actor pooled_actor-2 with Parent pooled_actor-2 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.771 [spawn_a1@127.0.0.1]:[pid=<0.1397.0> ]:[debug]:Actor pooled_actor-2 reactivated. ActorRef PID: #PID<0.1398.0>
2022-11-25 11:21:53.772 [spawn_a1@127.0.0.1]:[pid=<0.1398.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-2", system: "spawn-system", parent: "pooled_actor-2", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.774 [spawn_a1@127.0.0.1]:[pid=<0.1400.0> ]:[notice]:Activating Actor pooled_actor-8 with Parent pooled_actor-8 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.775 [spawn_a1@127.0.0.1]:[pid=<0.1399.0> ]:[debug]:Actor pooled_actor-8 reactivated. ActorRef PID: #PID<0.1400.0>
2022-11-25 11:21:53.776 [spawn_a1@127.0.0.1]:[pid=<0.1400.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-8", system: "spawn-system", parent: "pooled_actor-8", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.777 [spawn_a1@127.0.0.1]:[pid=<0.1402.0> ]:[notice]:Activating Actor pooled_actor-9 with Parent pooled_actor-9 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.778 [spawn_a1@127.0.0.1]:[pid=<0.1401.0> ]:[debug]:Actor pooled_actor-9 reactivated. ActorRef PID: #PID<0.1402.0>
2022-11-25 11:21:53.778 [spawn_a1@127.0.0.1]:[pid=<0.1402.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-9", system: "spawn-system", parent: "pooled_actor-9", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.779 [spawn_a1@127.0.0.1]:[pid=<0.1212.0> ]:[debug]:Lookup Actor pooled_actor. PID: #PID<0.1400.0>
2022-11-25 11:21:53.780 [spawn_a1@127.0.0.1]:[pid=<0.1400.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-8", system: "spawn-system", parent: "pooled_actor-8", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.780 [spawn_a1@127.0.0.1]:[pid=<0.1212.0> ]:[debug]:Lookup Actor pooled_actor. PID: #PID<0.1392.0>
2022-11-25 11:21:53.781 [spawn_a1@127.0.0.1]:[pid=<0.1392.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-5", system: "spawn-system", parent: "pooled_actor-5", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.781 [spawn_a1@127.0.0.1]:[pid=<0.1212.0> ]:[debug]:Lookup Actor pooled_actor. PID: #PID<0.1394.0>
[
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil
]
iex(spawn_a1@127.0.0.1)17> 2022-11-25 11:21:53.782 [spawn_a1@127.0.0.1]:[pid=<0.1394.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-4", system: "spawn-system", parent: "pooled_actor-4", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:22:03.999 [spawn_a1@127.0.0.1]:[pid=<0.1402.0> ]:[debug]:Deactivating actor pooled_actor-9 for timeout
2022-11-25 11:22:04.226 [spawn_a1@127.0.0.1]:[pid=<0.1392.0> ]:[debug]:Deactivating actor pooled_actor-5 for timeout
2022-11-25 11:22:07.271 [spawn_a1@127.0.0.1]:[pid=<0.1400.0> ]:[debug]:Deactivating actor pooled_actor-8 for timeout
2022-11-25 11:22:10.167 [spawn_a1@127.0.0.1]:[pid=<0.1396.0> ]:[debug]:Deactivating actor pooled_actor-7 for timeout
2022-11-25 11:22:12.038 [spawn_a1@127.0.0.1]:[pid=<0.1394.0> ]:[debug]:Deactivating actor pooled_actor-4 for timeout
2022-11-25 11:22:12.613 [spawn_a1@127.0.0.1]:[pid=<0.1398.0> ]:[debug]:Deactivating actor pooled_actor-2 for timeout
```

It is possible to configure the minimum and maximum pool size by passing the min_pool_size and max_pool_size options. The actor below would create a pool of a maximum of 100 Actors:

```elixir
defmodule SpawnSdkExample.Actors.PooledActor do
use SpawnSdk.Actor,
name: "pooled_actor",
kind: :pooled,
min_pool_size: 1,
max_pool_size: 10,
stateful: false

# Code omitted for brevity
end
```

> **_ALERT:_** Keep in mind that a large pool size does not always mean better performance. In fact, smaller pool sizes sometimes often have a much greater effect in this regard. It is interesting that you experiment with different pool sizes until you find the one that best suits your workload.
## Side Effects

Actors can also emit side effects to other Actors as part of their response. See an example:
Expand Down Expand Up @@ -458,103 +555,6 @@ end
In the example above the ´clock´ action will be called every 15 seconds.

## Pooled Actors

Sometimes we want a particular actor to be able to serve requests concurrently,
however actors will always serve one request at a time using buffering mechanisms to receive requests in their mailbox and serve each request one by one.
So to get around this behaviour you can configure your Actor as a Pooled Actor, this way the system will generate a pool of actors to meet certain requests. See an example below:

```elixir
defmodule SpawnSdkExample.Actors.PooledActor do
use SpawnSdk.Actor,
name: "pooled_actor",
kind: :pooled,
stateful: false

require Logger

defact ping(_data, %Context{} = ctx) do
Logger.info("Received Request. Context: #{inspect(ctx)}")

Value.of()
|> Value.void()
end
end
```

> **_NOTE:_** It is important to mention that Pooled Actors cannot be stateful because concurrent accesses for Stateful actors could generate undesired inconsistencies.
To invoke a pooled actor, it is necessary to pass the pooled: true option to the invoke function. See an example:

```elixir
iex(spawn_a1@127.0.0.1)16> for _n <- 0..8, do: SpawnSdk.invoke("pooled_actor", system: "spawn-system", command: "ping", pooled: true)
```

In the logs you will see that even though you have invoked the actor by the registered name more than one instance of the actor will be created. As we can see below:

```elixir
iex(spawn_a1@127.0.0.1)16> for _n <- 0..8, do: SpawnSdk.invoke("pooled_actor", system: "spawn-system", command: "ping", pooled: true)

2022-11-25 11:21:53.758 [spawn_a1@127.0.0.1]:[pid=<0.1392.0> ]:[notice]:Activating Actor pooled_actor-5 with Parent pooled_actor-5 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.759 [spawn_a1@127.0.0.1]:[pid=<0.1391.0> ]:[debug]:Actor pooled_actor-5 reactivated. ActorRef PID: #PID<0.1392.0>
2022-11-25 11:21:53.760 [spawn_a1@127.0.0.1]:[pid=<0.1392.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-5", system: "spawn-system", parent: "pooled_actor-5", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.763 [spawn_a1@127.0.0.1]:[pid=<0.1394.0> ]:[notice]:Activating Actor pooled_actor-4 with Parent pooled_actor-4 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.763 [spawn_a1@127.0.0.1]:[pid=<0.1393.0> ]:[debug]:Actor pooled_actor-4 reactivated. ActorRef PID: #PID<0.1394.0>
2022-11-25 11:21:53.765 [spawn_a1@127.0.0.1]:[pid=<0.1394.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-4", system: "spawn-system", parent: "pooled_actor-4", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.767 [spawn_a1@127.0.0.1]:[pid=<0.1396.0> ]:[notice]:Activating Actor pooled_actor-7 with Parent pooled_actor-7 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.767 [spawn_a1@127.0.0.1]:[pid=<0.1395.0> ]:[debug]:Actor pooled_actor-7 reactivated. ActorRef PID: #PID<0.1396.0>
2022-11-25 11:21:53.768 [spawn_a1@127.0.0.1]:[pid=<0.1396.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-7", system: "spawn-system", parent: "pooled_actor-7", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.770 [spawn_a1@127.0.0.1]:[pid=<0.1398.0> ]:[notice]:Activating Actor pooled_actor-2 with Parent pooled_actor-2 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.771 [spawn_a1@127.0.0.1]:[pid=<0.1397.0> ]:[debug]:Actor pooled_actor-2 reactivated. ActorRef PID: #PID<0.1398.0>
2022-11-25 11:21:53.772 [spawn_a1@127.0.0.1]:[pid=<0.1398.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-2", system: "spawn-system", parent: "pooled_actor-2", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.774 [spawn_a1@127.0.0.1]:[pid=<0.1400.0> ]:[notice]:Activating Actor pooled_actor-8 with Parent pooled_actor-8 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.775 [spawn_a1@127.0.0.1]:[pid=<0.1399.0> ]:[debug]:Actor pooled_actor-8 reactivated. ActorRef PID: #PID<0.1400.0>
2022-11-25 11:21:53.776 [spawn_a1@127.0.0.1]:[pid=<0.1400.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-8", system: "spawn-system", parent: "pooled_actor-8", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.777 [spawn_a1@127.0.0.1]:[pid=<0.1402.0> ]:[notice]:Activating Actor pooled_actor-9 with Parent pooled_actor-9 in Node :"spawn_a1@127.0.0.1". Persistence false.
2022-11-25 11:21:53.778 [spawn_a1@127.0.0.1]:[pid=<0.1401.0> ]:[debug]:Actor pooled_actor-9 reactivated. ActorRef PID: #PID<0.1402.0>
2022-11-25 11:21:53.778 [spawn_a1@127.0.0.1]:[pid=<0.1402.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-9", system: "spawn-system", parent: "pooled_actor-9", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.779 [spawn_a1@127.0.0.1]:[pid=<0.1212.0> ]:[debug]:Lookup Actor pooled_actor. PID: #PID<0.1400.0>
2022-11-25 11:21:53.780 [spawn_a1@127.0.0.1]:[pid=<0.1400.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-8", system: "spawn-system", parent: "pooled_actor-8", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.780 [spawn_a1@127.0.0.1]:[pid=<0.1212.0> ]:[debug]:Lookup Actor pooled_actor. PID: #PID<0.1392.0>
2022-11-25 11:21:53.781 [spawn_a1@127.0.0.1]:[pid=<0.1392.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-5", system: "spawn-system", parent: "pooled_actor-5", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:21:53.781 [spawn_a1@127.0.0.1]:[pid=<0.1212.0> ]:[debug]:Lookup Actor pooled_actor. PID: #PID<0.1394.0>
[
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil,
ok: nil
]
iex(spawn_a1@127.0.0.1)17> 2022-11-25 11:21:53.782 [spawn_a1@127.0.0.1]:[pid=<0.1394.0> ]:[info]:Received Request. Context: %SpawnSdk.Context{state: nil, caller: nil, self: %Eigr.Functions.Protocol.Actors.ActorId{name: "pooled_actor-4", system: "spawn-system", parent: "pooled_actor-4", __unknown_fields__: []}, metadata: %{}}
2022-11-25 11:22:03.999 [spawn_a1@127.0.0.1]:[pid=<0.1402.0> ]:[debug]:Deactivating actor pooled_actor-9 for timeout
2022-11-25 11:22:04.226 [spawn_a1@127.0.0.1]:[pid=<0.1392.0> ]:[debug]:Deactivating actor pooled_actor-5 for timeout
2022-11-25 11:22:07.271 [spawn_a1@127.0.0.1]:[pid=<0.1400.0> ]:[debug]:Deactivating actor pooled_actor-8 for timeout
2022-11-25 11:22:10.167 [spawn_a1@127.0.0.1]:[pid=<0.1396.0> ]:[debug]:Deactivating actor pooled_actor-7 for timeout
2022-11-25 11:22:12.038 [spawn_a1@127.0.0.1]:[pid=<0.1394.0> ]:[debug]:Deactivating actor pooled_actor-4 for timeout
2022-11-25 11:22:12.613 [spawn_a1@127.0.0.1]:[pid=<0.1398.0> ]:[debug]:Deactivating actor pooled_actor-2 for timeout
```

It is possible to configure the minimum and maximum pool size by passing the min_pool_size and max_pool_size options. The actor below would create a pool of a maximum of 100 Actors:

```elixir
defmodule SpawnSdkExample.Actors.PooledActor do
use SpawnSdk.Actor,
name: "pooled_actor",
kind: :pooled,
min_pool_size: 1,
max_pool_size: 10,
stateful: false

# Code omitted for brevity
end
```

> **_ALERT:_** Keep in mind that a large pool size does not always mean better performance. In fact, smaller pool sizes sometimes often have a much greater effect in this regard. It is interesting that you experiment with different pool sizes until you find the one that best suits your workload.
## Declaring the supervision tree

Once we define our actors we can now declare our supervision tree:
Expand Down

0 comments on commit 58a2255

Please sign in to comment.