Replies: 9 comments 2 replies
-
This sounds pretty cool. Some questions:
|
Beta Was this translation helpful? Give feedback.
-
After some discussion with @kjnilsson, the feature doesn't need to use the "aux effect". A simple "mod call effect" would achieve the same. He also reminded me of an important fact: there would be no "execute once" guaranty at all. Here is an example he gave me:
This must be documented because it could take users by surprise. That said, this is the same with Mnesia: a transaction function can be restarted many times if needed to resolve conflicts. Therefore Khepri and Mnesia would behave the same w.r.t. side effects. |
Beta Was this translation helpful? Give feedback.
-
I do not understand what is meant by
You don't have to know a pid to send a message. I would say that messages that should be sent inside or as a result of a transaction should only be sent to processes that can be looked up via a registry ( |
Beta Was this translation helpful? Give feedback.
-
I have a prototype for the stored procedure part: khepri:start(),
%% -> {ok,khepri}
khepri:insert([a], fun() -> io:format("Youpi~n") end),
%% -> ok
khepri:run_sproc([a], []),
%% -> ok
%% Displays "Youpi" on stdout The extracted anonymous function (i.e. the binary of the generated & compiled module) is stored in the database:
UPDATE: I will push the change to the following branch: |
Beta Was this translation helpful? Give feedback.
-
The
The trigger itself is also stored in the state machine's state and thus will be replicated and survive a restart of the Ra node. The procedure runs on the Ra leader node, in the context of a new |
Beta Was this translation helpful? Give feedback.
-
The latest commits to the branch hopefully brought at-least-once execution guaranties. It means that when a stored procedure is triggered by an event, that fact is recorded in the state machine's state in addition to the message sent to the event handler process. When the event handler process is done with the execution, it acks the execution to the state machine. When a new leader is elected, it sends unacked triggered stored procedures to the event handler again. This means that the triggered stored procedures might be executed multiple times, and thus should be idempotent. However we are sure that we don't skip one because the event handler didn't have a chance to run them because the node went down. Thank you @kjnilsson for the help on designing this! I'm happy with the state of this new feature. I now need to extend the currently limited testing. |
Beta Was this translation helpful? Give feedback.
-
I opened a draft pull request (#47). |
Beta Was this translation helpful? Give feedback.
-
The pull request #47 was merged into |
Beta Was this translation helpful? Give feedback.
-
Here is a feedback from the Erlang forum I find interesting:
I filed #57 to track this. Another one on the Erlang mailing-list:
I started the conversation with this person to explain my intent and try to better understand his concerns. This should become an improvement to the code or to the documentation. Update: The person feels reassured by the explanation. I filed #58 to make sure I improve the documentation to prevent confusion with triggers in RDBMS. |
Beta Was this translation helpful? Give feedback.
-
Here is an example: in RabbitMQ, when a runtime parameter is deleted, the
rabbit_event
module is called to emit a notification. This can be executed inside a Mnesia transaction. Mnesia doesn't care about side effects, thus it is perfectly allowed. That notification could be emitted many times if Mnesia needs to run the transaction again if the previous runs didn't succeed.Khepri doesn't allow side effects in its transactions, even sending a message. This is by design because we want transactions to be deterministic and always give the same result. Sending a message is not possible because it would mean that the transaction knows about a process PID and this PID might make no sense at the time a new Ra cluster member joins and applies commands again.
Therefore, having this "a RabbitMQ runtime parameter was deleted" notification being emitted as part of a Khepri transaction is denied. However, it should be possible to rely on the "aux effect" of Ra to perform non-deterministic actions after the command is applied.
It would be nice to be able to run arbitrary code after an event happens.
Events
Here are some types of events which would be of interest:
For Khepri nodes, the state machine could emit an event as part of an "aux effect". This event would contain the initial command (
put
ordelete
), the affected node(s), the return value and even the entire state.Other types of events could be emitted by a dedicated process in Khepri who would monitor cluster membership and arbitrary Erlang nodes/PIDs.
Stored procedures
Like with transactions, if an event needs to trigger and execute some code, that code must be available on all cluster members.
It is possible to reuse the anonymous function extraction feature of the transactions to extract and isolate an anonymous function. The extracted function could be stored inside the database itself under any path the user sees fit (e.g.
/stored-procs/when-node-is-deleted
). This could be a new type of payload (in addition to "data", the only one supported today). It would allow the user to update or remove stored procedures using the regular Khepri API.Link events to stored procedures
Now, we need the actual trigger. The link between events and a stored procedures could be stored inside the state machine's state, like
keep_while
conditions.The dedicated process I mentionned earlier could thus listen to events, look for triggers in the state machine's state and run the associated code if any.
The state machine's state would come from the "aux effect" or the state machine could be queried for other types of events (membership/PIDs monitoring).
The stored procedure function would be able to do anything it wants. There would be no restrictions, unlike what we have for transactions. It would also be able to read the state of the database as it was at the time the event was emitted (regardless of the changes which happened in between).
If the stored procedure wants to modify the database, it would use the regular Khepri API as any caller.
Example
The caller stores an anonymous function:
The caller registers a trigger:
When a node is deleted,
khepri_machine:apply/3
returns the following "aux effect":The monitoring/trigger dedicated process:
In the case of the "a process has terminated" event, the dedicated process should probably remove the trigger automatically.
Subscribe to events
As an added bonus, we can implement event subscriptions on top of that: an arbitrary process could subscribe to the same events and be notified by the monitoring/trigger dedicated process when something happens.
This time, the subscriptions would probably not be stored inside the database, but inside that dedicated process' state. This is a simpler lightweight feature. For a stored subscription, the caller can just use the reguler trigger feature described above.
Feedback?
What do you think? Does it make sense? Will it break the guaranties of Ra(ft)?
Beta Was this translation helpful? Give feedback.
All reactions