Skip to main content

Advanced StateMachine APIs: A Developer Guide

This guide explores the advanced, optional APIs of the StateMachine interface. Implementing these APIs gives you fine-grained control over data handling, event notifications, and multi-raft management. For a guide to the essential, core methods, see the "StateMachine Core API" guide.

DataApi: For High-Performance Data Handling

Implement the DataApi interface when you need to manage state machine data directly, outside of the Raft log. This is essential for performance-critical applications that require zero-copy data streaming.

Example: A File Store StateMachine

Let's imagine a state machine that stores large files. Instead of writing the entire file content to the Raft log, we can use the DataApi to stream the file directly to a separate storage location.

public class FileStoreStateMachine extends BaseStateMachine implements StateMachine.DataApi {

private final File storageDir = new File("/path/to/your/storage");

@Override
public CompletableFuture<DataStream> stream(RaftClientRequest request) {
// The request message contains the file name
String fileName = request.getMessage().getContent().toStringUtf8();
File file = new File(storageDir, fileName);

try {
// Create a DataChannel to write the file to
final FileChannel channel = new FileOutputStream(file).getChannel();
final DataStream stream = () -> channel;
return CompletableFuture.completedFuture(stream);
} catch (IOException e) {
return CompletableFuture.failedFuture(e);
}
}

@Override
public CompletableFuture<?> link(DataStream stream, LogEntryProto entry) {
// The stream is now complete. We can close the channel.
try {
stream.getDataChannel().close();
} catch (IOException e) {
return CompletableFuture.failedFuture(e);
}
return CompletableFuture.completedFuture(null);
}
}

EventApi: Reacting to Cluster Events

Implement EventApi to receive notifications about general lifecycle events within the Raft group.

Example: Logging Leader Changes

public class MyStateMachine extends BaseStateMachine implements StateMachine.EventApi {

@Override
public void notifyLeaderChanged(RaftGroupMemberId groupMemberId, RaftPeerId newLeaderId) {
System.out.println("Leader changed for group " + groupMemberId + ". New leader is " + newLeaderId);
}
}

LeaderEventApi: Leader-Specific Logic

Implement LeaderEventApi to execute logic that should only run when the server is the leader.

Example: Monitoring Slow Followers

public class MyStateMachine extends BaseStateMachine implements StateMachine.LeaderEventApi {

@Override
public void notifyFollowerSlowness(RoleInfoProto leaderInfo, RaftPeer slowFollower) {
System.err.println("Warning: Follower " + slowFollower.getId() + " is slow!");
}
}

FollowerEventApi: Follower-Specific Logic

Implement FollowerEventApi for follower-only event handling.

Example: Handling Leader Timeouts

public class MyStateMachine extends BaseStateMachine implements StateMachine.FollowerEventApi {

@Override
public void notifyExtendedNoLeader(RoleInfoProto roleInfoProto) {
System.err.println("Warning: No leader elected for an extended period of time.");
// You could trigger an alert here
}
}

Registry: Supporting Multiple Raft Groups

If you need to run multiple, independent Raft groups (and thus multiple state machines) within a single server process, you can implement the StateMachine.Registry interface.

Example: A Multi-Tenant StateMachine

public class MultiTenantStateMachineRegistry implements StateMachine.Registry {

private final Map<RaftGroupId, StateMachine> stateMachines = new ConcurrentHashMap<>();

@Override
public StateMachine apply(RaftGroupId groupId) {
return stateMachines.computeIfAbsent(groupId, id -> {
// Create a new state machine for the given group
// You might have different state machine implementations for different groups
return new KeyValueStateMachine();
});
}
}