#
Read-Only Replicas
For high-availability and high-throughput scenarios, Curiosity Workspace supports a primary-replica architecture. In this setup, one node (the Primary) handles all write operations, while one or more Read-Only Replicas serve read queries and custom endpoints.
#
Shared Storage Requirement
Replicas work by reading the same underlying graph data files as the Primary. Therefore, all nodes in the cluster must have access to the same shared storage location.
#
File System Considerations
The graph database relies on file locks and efficient I/O.
- Clustered File Systems (Recommended): For production workloads, use a clustered file system designed for concurrent access, such as GFS2 (Red Hat Global File System 2). These systems ensure strict data consistency and resiliency.
- POSIX-Compatible NAS: Standard NAS file systems (like NFS) are acceptable if and only if they implement proper POSIX lock semantics.
- Warning: Many NFS implementations have limits on the number of active file locks or latency issues that can affect database stability. Validate the specific capabilities of your NAS (e.g., maximum file locks) before deploying to production.
- Avoid: Standard local file systems (ext4, XFS) shared via simple network mounts without locking coordination are not suitable for simultaneous access by multiple active database servers.
#
Configuration
Replicas are configured using command-line flags or environment variables during deployment.
#
Code Execution on Replicas
When writing Custom Endpoints, it is important to understand that they may run on a Replica.
- ReadOnlyCodeEndpointExecutionScope: By default, endpoints on a replica run in this scope. You can read from the graph (
Graph.Query(),Graph.Get()) but cannot modify it. - Forwarding Writes: If your endpoint needs to modify data, it should check if it is running on a replica. If so, it can use
RunEndpointOnPrimaryAsync(...)to execute the logic on the Primary node.
#
Example: Handling Writes in an Endpoint
if (Graph.Internals.ReadOnly)
{
// We are on a replica, forward the request to the primary
return await RunEndpointOnPrimaryAsync<string>("MyWriteEndpoint", Body);
}
else
{
// We are on the primary, perform the write
var node = await Graph.GetOrAddLockedAsync("Log", "123");
// ... update node ...
await Graph.CommitAsync(node);
return "Updated";
}