The latest release of FalkorDB V4.0.5 includes a new ability to easily clone graphs. In this blog post we'll be developing a state machine framework where a machine is represented by a graph. Whenever a FSM (finite state machine) is executed a copy of the initial graph is created and the execution is bound to that dedicated clone. This approach is extremely flexible, as one can easily adjust a machine and changes will be applied to all future executions seamlessly, or in case a modification needs to be A/B tested a clone of the graph is easily made and compared to a baseline. Let's get started, we'll create a simple machine which: 1. Download a source file 2. Count the number of lines in that file 3. Delete the file
State Machine Representation
Our graph representation of a state machine is a simple DAG (directed acyclic graph)
Nodes represent states, in each machine there’s a START state and an END state in addition to intermediate states.
Every state node contains the following attributes:
- Cmd – the shell command to run
- Description – short description of the state
- Output – command output (available after execution)
- ExitCode – command exit code (available after execution)
The states are connected to one another via a NEXT directed edge.
# StateMachine
class StateMachine:
def create_state(self, cmd, desc):
def connect_states(self, src, dest):
# Create machine
machine = StateMachine("line_count")
# Create states
A = machine.create_state("curl -o graph.c https://raw.github.com/FalkorDB/FalkorDB/master/src/graph/graph.c", "download file")
B = machine.create_state("wc -l ./graph.c", "count lines")
C = machine.create_state("rm ./graph.c", "delete file")
# Connect steps
machine.connect_states(A, B)
machine.connect_states(B, C)
Running the State Machine
Once we’re ready to execute our FSM, a copy of the DAG is created, this is done automatically via the handy new GRAPH.COPY command, as we don’t want to taint our machine “template” with specific execution information.
The execution begins at the START state, our runner executes the state’s command, once the command completes the runner updates the states’s Output and ExitCode attributes and proceed to the next state, this process repeats itself until the last state is executed.
# Run Machine
# clone machine
machine = self.clone()
print(f"Running machine {machine.name}")
# get first state
state = machine.initial_state()
# run state
while state is not None:
machine.run_state(state)
# get next state
state = machine.next_state(state)
print(f"Finished running machine {machine.name}")
return machine
Output:
Running machine line_count_2024-03-06 14:38:00.292751
Executing curl -o graph.c https://raw.githubusercontent.com/FalkorDB/FalkorDB/master/src/graph/graph.c
Output:
Executing wc -l ./graph.c
Output: 1426 ./graph.c
Executing rm ./graph.c
Output:
Finished running machine line_count_2024-03-06 14:38:00.292751
Conclusions
With very little effort we’ve been able to build a simple state machine system, which takes advantage of a number of FalkorDB unique features:
- the ability to store, maintain and switch effortlessly between thousandths of different graphs
- our new feature to quickly create copies of graphs