falkordb-rs
FalkorDB Rust client
Usage
Installation
Just add it to your Cargo.toml, like so:
= { = "0.3.0" }
Run FalkorDB instance
Docker:
Code Example
use ;
// Connect to FalkorDB
let connection_info: FalkorConnectionInfo = "falkor://127.0.0.1:6379".try_into
.expect;
let client = new
.with_connection_info
.build
.expect;
// Select the social graph
let mut graph = client.select_graph;
// Create 100 nodes and return a handful
let mut nodes = graph.query
.with_timeout
.execute
.expect;
// Can also be collected, like any other iterator
while let Some = nodes.data.next
Features
Waiting for background operations
Some FalkorDB operations finish after the command that starts them returns: when you create or
drop an index or constraint, the request returns immediately while the index is populated (or the
constraint is enforced) on a background worker thread, and GRAPH.COPY can fail transiently while
the server is unable to fork. The eager methods
(create_index, create_unique_constraint, copy_graph, …) stay fire-and-forget, but every
one of them now has an additive *_op builder that adds explicit, opt-in waiting while keeping
full backward compatibility.
Each builder offers .execute() (non-blocking, identical to the eager method) and .wait() /
.wait_with(WaitOptions) terminals. For index and constraint builders, .wait() blocks until the
operation has actually taken effect (the index/constraint becomes operational or is dropped),
returning FalkorDBError::Timeout if it does not happen in time. For the copy builder, GRAPH.COPY
is already blocking on the server, so .wait() simply retries transient could not fork failures
with backoff; it does not verify the copied contents (that remains the caller's responsibility).
use ;
use Duration;
let connection_info: FalkorConnectionInfo = "falkor://127.0.0.1:6379".try_into
.expect;
let client = new
.with_connection_info
.build
.expect;
let mut graph = client.select_graph;
// Fire-and-forget, exactly like `create_index` (returns as soon as the server accepts it):
graph.create_index_op
.execute
.expect;
// Block until the index is actually operational (default 30s readiness timeout):
graph.create_index_op
.wait
.expect;
// A unique constraint reports a *distinct* error if existing data violates it:
match graph.create_unique_constraint_op
.wait_with
// Copy a graph, retrying transient `could not fork` failures:
let _copy = client.copy_graph_op
.wait
.expect;
The same builders exist on the async client/graph; just await the terminals:
use ;
let connection_info: FalkorConnectionInfo = "falkor://127.0.0.1:6379".try_into
.expect;
let client = new_async
.with_connection_info
.build
.await
.expect;
let mut graph = client.select_graph;
graph.create_index_op
.wait
.await
.expect;
tokio support
This client supports nonblocking API using the tokio runtime.
It can be enabled like so:
= { = "0.3.0", = ["tokio"] }
Currently, this API requires running within a
multi_threaded tokio scheduler, and
does not support the current_thread one, but this will probably be supported in the future.
The API uses an almost identical API, but the various functions need to be awaited:
use ;
// Connect to FalkorDB
let connection_info: FalkorConnectionInfo = "falkor://127.0.0.1:6379".try_into
.expect;
let client = new_async
.with_connection_info
.build
.await
.expect;
// Select the social graph
let mut graph = client.select_graph;
// Create 100 nodes and return a handful
let mut nodes = graph.query
.with_timeout
.execute
.await
.expect;
// Graph operations are asynchronous, but parsing is still concurrent:
while let Some = nodes.data.next
Note that thread safety is still up to the user to ensure, I.e. an AsyncGraph cannot simply be sent to a task spawned
by tokio and expected to be used later,
it must be wrapped in an Arc<Mutex<_>> or something similar.
SSL/TLS Support
This client is currently built upon the redis crate, and therefore supports TLS
using
its implementation, which uses either rustls or
native_tls.
This is not enabled by default, and the user just opt-in by enabling the respective features: "rustls"/"native-tls" (
when using tokio: "tokio-rustls"/"tokio-native-tls").
For Rustls:
= { = "0.3.0", = ["rustls"] }
= { = "0.3.0", = ["tokio-rustls"] }
For Native TLS:
= { = "0.3.0", = ["native-tls"] }
= { = "0.3.0", = ["tokio-native-tls"] }
TCP Keepalive
Long-lived clients behind NATs, stateful firewalls, or idle-timeout-enforcing proxies can silently lose their TCP sessions. The builder exposes TCP-level socket settings to prevent this:
use FalkorClientBuilder;
use Duration;
// Convenience: just enable keepalive with a 30-second idle timeout
let client = new
.with_tcp_keepalive
.build
.expect;
// Or full control via redis::io::tcp::TcpSettings
let settings = default
.set_nodelay
.set_keepalive;
let client = new
.with_tcp_settings
.build
.expect;
Note: TCP settings apply to direct Redis TCP connections only. Unix-domain socket / embedded connections and the Sentinel connection path are not affected.
Read-only Queries and Replica Routing
Read-only queries (ro_query and call_procedure_ro) can be served from
replica nodes, taking read load off the primary. When the client connects to a
Redis Sentinel deployment that exposes readable replicas, it automatically
builds a dedicated read-only connection pool that routes those queries to a
replica. Writes always go to the primary.
Connection pool sizing: When readable replicas are present the client opens a second pool of up to
num_connectionsadditional connections (one per slot) alongside the primary pool. Size your pool limits and file-descriptor limits accordingly.
use FalkorClientBuilder;
let client = new
// A Sentinel endpoint, e.g. falkor://127.0.0.1:26379
.with_connection_info
.build
.expect;
// `true` only when readable replicas are available.
if client.reads_from_replicas
let mut graph = client.select_graph;
// Writes go to the primary.
graph.query.execute.expect;
// Read-only queries are served from a replica when one is available.
let mut nodes = graph.ro_query.execute.expect;
This behavior is fully backward compatible: against a single node (or any
deployment without readable replicas), ro_query / call_procedure_ro
transparently fall back to the primary connection, and reads_from_replicas()
returns false. See examples/readonly_replica.rs
for a complete working example.
Tracing
This crate fully supports instrumentation using the tracing crate, to use
it, simply, enable the tracing feature:
= { = "0.3.0", = ["tracing"] }
Note that different functions use different filtration levels, to avoid spamming your tests, be sure to enable the correct level as you desire it.
Embedded FalkorDB Server
This client supports running an embedded FalkorDB server, which is useful for:
- Testing without external dependencies
- Embedded applications
- Quick prototyping and development
To use the embedded feature, enable it in your Cargo.toml:
= { = "0.3.0", = ["embedded"] }
Requirements
redis-servermust be installed and available in PATH (or you can specify a custom path)falkordb.somodule must be installed (or you can specify a custom path)
You can install these from:
- Redis: https://github.com/redis/redis
- FalkorDB: https://github.com/falkordb/falkordb
Usage Example
use ;
// Create an embedded configuration with defaults
let embedded_config = default;
// Or customize the configuration:
// let embedded_config = EmbeddedConfig {
// redis_server_path: Some(PathBuf::from("/path/to/redis-server")),
// falkordb_module_path: Some(PathBuf::from("/path/to/falkordb.so")),
// db_dir: Some(PathBuf::from("/tmp/my_falkordb")),
// ..Default::default()
// };
// Build a client with embedded FalkorDB
let client = new
.with_connection_info
.build
.expect;
// Use the client normally
let mut graph = client.select_graph;
graph.query.execute.expect;
// The embedded server will be automatically shut down when the client is dropped
The embedded server:
- Spawns a
redis-serverprocess with the FalkorDB module loaded - Uses Unix socket for communication (no network port)
- Automatically cleans up when the client is dropped
- Can be configured with custom paths, database directory, and socket location
Testing
Running Tests
This project includes both unit tests and integration tests.
Unit Tests
Unit tests don't require a running FalkorDB instance:
# Run all unit tests
# Run unit tests with embedded feature
Integration Tests
Integration tests require a running FalkorDB instance. The easiest way to run them is using Docker:
# Using the provided script (requires Docker)
# Or manually start FalkorDB and run tests
# With async support
# Clean up
&&
CI Integration Tests
Integration tests are automatically run in GitHub Actions using Docker services. See .github/workflows/integration-tests.yml for the CI configuration.