eigr / massa

A Stateful Serverless framework on the BEAM VM
https://eigr.io
Apache License 2.0
55 stars 5 forks source link

Potential to integrate Vaxine as CRDT backend. #113

Open thruflo opened 2 years ago

thruflo commented 2 years ago

Hi,

Just raising the suggestion following a discord discussion with @sleipnir. Vaxine (https://vaxine.io) is a project developing a rich-CRDT database, building on https://antidotedb.eu, with the core database in Erlang and the higher levels in Elixir.

Hopefully Vaxine could be one of the pluggable storage options underpinning the Eigr proxy CRDT entity types. Happy to feed in on interface design and put some time into a working demo integration if useful. On which, it would be useful to understand:

Thanks,

James.

sleipnir commented 2 years ago

Hello @thruflo, how are you? Thanks for sharing your thoughts. As I told you in particular, we are still building these abstractions so that we can plug in different persistence backends to our Entity types.

In general, and in my personal understanding, stateful entities should have a live GenServer instance per PersistenceId, that is, whenever the client sends a request that has a persistence id X, this request will always be handled by the equivalent GenServer X instance. That way we were able to spread these instances across the cluster and still keep a healthy reference to them when we need to reference them. In turn, this GenServer will have to deal with the entire lifecycle of the function state for that given persistenceId, that is, things like hydration (activation), persistence, snapshots, and queries should be the responsibility of this GenServer. But in order to abstract these concepts and implement different types of storage, we will have to make this GenServer a client (has a dependency) of a module that knows how to do all these things (recover and save entities). And this is where I think there are two possible approaches in Elixir to deal with this, namely Behaviors and Protocols. We can define a Protocol and implementers can implement this protocol for the type of entity of interest (EventSourced, CRDT, KV, and etc..), the same goes for if we are going to use a Behaviour, and so when we create the GenServer we can query the configuration and inject the equivalent implementation into the GenServer module as part of its internal state. Example with Behaviour.

defmodule EventSourcedEntityActor do
  use GenServer
  def init(%{persistor: persistor, persistence_id: x} = initial_state) do
    Process.flag(:trap_exit, true)
    .....
    {:ok, initial_state}
  end

  def handle_call({:persist_event, event}, from, %{persistor: persistor, persistence_id: id} = state)
    persistor.save(id, event)
    ....
  end

  def handle_call(:snapshot, from, %{persistor: persistor, persistence_id: id} = state)
    persistor.snapshot(id, state)
    ....
  end

  def terminate(_reason, %{persistor: persistor, persistence_id: id} = state)
    persistor.snapshot(id, state)
  end
  ....
end

So basically we need to start by defining the Behavior or Protocol contract.

After that, another question would be how to manage these dependencies in the Proxy, that is, we are going to add directly to the proxy dependencies all possible implementations that we have, and this leads to an increasingly larger Proxy with more dependencies and possibly consuming more resources, Or is there any alternative to this scenario? I don't know in Elixir or Erlang any plugin pattern, or library, that is viable for our scenario, but I'm researching it. In Cloudstate/AkkaServeless they solved this problem by generating a specific binary for each type of storage used (although they could have solved this with dynamic classpath, but due to the use of GraalVM this would not be possible either).

Anyway, I tried to give you some context, any new ideas would be welcome as we are still in the design phase of this item.

thruflo commented 2 years ago

Thanks, bear with me and I'll have a chat with the team our end. On the proxy dependencies point, yes, my first thought would be a strategy pattern (where you can configure the persistence option much like you configure the email sending library for Phoenix) but I think it depends on how you're packaging and deploying.

sleipnir commented 2 years ago

Thanks, bear with me and I'll have a chat with the team our end. On the proxy dependencies point, yes, my first thought would be a strategy pattern (where you can configure the persistence option much like you configure the email sending library for Phoenix) but I think it depends on how you're packaging and deploying.

Yes, the idea is that the adapter itself is located via the runtime configuration, so when the proxy is deployed we take the adapter implementation via the configuration and when we start GenServer we pass it on