DynaRust is a distributed key–JSON‑value built in Rust. It's designed to be reliable and easy to manage, allowing you to add or remove nodes (servers) dynamically without interrupting service 🔄.
It combines in‑memory caching, on‑disk persistence, automatic cross‑node replication and background synchronization for eventual consistency, delivering a fault‑tolerant, horizontally scalable datastore.
With its advanced real‑time update capabilities, DynaRust pushes live changes with latencies below 5 ms 🚀. In fact, on a typical VPS (1 GB RAM, 100 Mbps bandwidth), a single node can comfortably sustain peak traffic of up to 5000 live connections 🔥—and you can increase capacity even further simply by adding more nodes to your cluster!
| Metric | Value |
|---|---|
| Container Resources | 0.25 vCPU, 0.5 GB RAM |
| Data Storage | 100,000 records (50-word lorem ipsum JSON/record) |
| Memory Consumption | 350 MB |
| Cluster Startup | < 1 sec |
| GET Operation Latency | ~20 ms |
| Cold storage Usage | ~30 MB of disk data |
While inserting ~300 rows/sec we had a SSE client open on a key flawlessly getting live updates in <5 ms (Cheapest AWS EC2)
-
🔥 HOT RELOAD & REAL‑TIME UPDATES: Enjoy lightning‑fast, real‑time updates using Server‑Sent Events (SSE). Subscribe to a key with:
# Example: Subscribe to 'statusKey' in the 'notifications' table curl -N http://localhost:8080/notifications/subscribe/statusKey
Changes are pushed instantly (< 5 ms latency). On a standard VPS (1GB RAM, 100Mbps), a single node handles up to 5000 simultaneous live connections 💪. Need more capacity? Just add more nodes!- Use Case Example: Imagine a web UI needing push notifications. Store device IDs as keys in a
devicestable. Use a separatestatuskey in the same table. The frontend listens todevices/subscribe/status. The backend iterates through device keys, performs actions, and updates thestatuskey, instantly notifying all listening frontends. Simple and blazing fast! ⚡️
- Use Case Example: Imagine a web UI needing push notifications. Store device IDs as keys in a
-
Access Control:
- Read: Anyone can read records.
- Write/Delete: Only the record’s owner (as specified in the
ownerfield) can modify or delete it. - Enforcement: All
PUTandDELETEoperations require anAuthorizationheader. The server verifies that the requester matches the record’s owner.
-
Cluster Security:
- At compile time a SHA256 encryption key is embeded in the compiled binary (if that changes somehow in the future (recompile binary with different key) you won't be able to load the table, steps to properly compile are: run bash encryption.sh && cargo build --release and distribute only the binary under target/release/ to your nodes);
- Each node should have a JWT_SECRET set, without this env var the node won't even start
- Each node must present a secret token (set via the
CLUSTER_SECRETenvironment variable) to join the cluster, ensuring only trusted nodes participate.
- Each node must present a secret token (set via the
- Each node should have a JWT_SECRET set, without this env var the node won't even start
- At compile time a SHA256 encryption key is embeded in the compiled binary (if that changes somehow in the future (recompile binary with different key) you won't be able to load the table, steps to properly compile are: run bash encryption.sh && cargo build --release and distribute only the binary under target/release/ to your nodes);
-
Transport Security (HTTPS):
- Easy Certificate Generation:
- Run
bash cert.sh, provide a password, and a.p12certificate will be generated under thecert/directory.
- Run
- HTTPS Mode:
- Set
DYNA_MODE=httpsto enable HTTPS
- Set
- Easy Certificate Generation:
Security is enforced at every layer: from user access to node-to-node communication, ensuring your data remains private and protected.
-
🌐 Distributed Storage: Data is automatically partitioned and spread across all nodes in the cluster.
-
🗄️ Automatic Snapshots: Every 60 minutes DynaRust writes a JSON snapshot of the entire in‑memory store to
./snapshots/snapshot_<ts>.json. By default only the last 100 snapshots are kept; older files are pruned automatically. You can override the retention limit with theSNAP_LIMITenvironment variable (e.g.SNAP_LIMIT=200). -
✅ High Availability: If one node fails, the remaining nodes continue to serve requests for the available data.
-
🔄 Dynamic Cluster Membership: Nodes can join or leave the cluster seamlessly without manual re‑configuration.
-
🤝 Automatic State Sync: New or returning nodes fetch the latest state from the cluster automatically.
-
💾 Persistent Storage: Data is saved to a local
storage.dbfile to prevent data loss upon node restarts. -
🔌 RESTful API: A simple HTTP interface (using
GET,PUT,DELETE) makes it easy to interact with your data.
All operations except GET require a valid JWT in the Authorization: Bearer <token> header.
-
🛂 Register / Log In (HTTP POST)
Register a new user or log in an existing one.- URL:
/auth/{user} - Body:
{ "secret": "my_password" } - Responses:
•200 OKand- On first call (user didn’t exist):
{ "status": "User created" } - On subsequent calls with correct secret:
{ "token": "<JWT‑TOKEN‑HERE>" }
400 Bad Requestif user exists on register
•401 Unauthorizedif secret is wrong - On first call (user didn’t exist):
- URL:
-
✍️ Store or Update a Value (HTTP PUT)
Create or update a value under{table}/{key}.- URL:
/default/key/mykey - Headers:
Content-Type: application/json Authorization: Bearer <JWT‑TOKEN> - Body:
{ "value": "mydata" } - Success:
•201 Created
• Body (VersionedValue):{ "value": "mydata", "version": 1, "timestamp": 1618880821123, "owner": "alice" } - Errors:
•401 Unauthorizedif missing/invalid JWT or not owner on update
- URL:
-
🔍 Retrieve a Value (HTTP GET)
Anyone can fetch a key’s latest value.- URL:
/default/key/mykey - Success:
•200 OK
• Body:•{ "value": "mydata", "version": 1, "timestamp": 1618880821123, "owner": "alice" }404 Not Foundif key/table missing
- URL:
-
🗑️ Delete a Value (HTTP DELETE)
Only the owner may delete.- URL:
/default/key/mykey - Header:
Authorization: Bearer <JWT‑TOKEN> - Success:
•200 OK
• Body:{ "message": "Deleted locally" } - Errors:
•401 Unauthorizedif no JWT or not owner
•404 Not Foundif key/table missing
- URL:
-
📚 Fetch Entire Table Store (HTTP GET)
List all key→VersionedValue pairs in a table.- URL:
/default/store - Success:
•200 OK
• Body:{ "key1": { "value":"v1","version":2,…,"owner":"bob" }, "key2": { … } } 404 Not Foundif table missing
- URL:
-
🔑 List or Batch‑Fetch Keys
6.1 GET/default/keys
•200 OK→
json ["key1","key2",…]
6.2 POST/default/keys
- Body:
json ["key1","key2","key3"]
-200 OK→
json { "key1": { "value":"v1","version":… }, "key3": { … } }
(non‑existent keys are omitted) -
🔔 Subscribe to Real‑Time Updates (SSE)
Instant updates on a single key.- URL:
/default/subscribe/mykey - Usage:
curl -N http://localhost:6660/default/subscribe/mykey
- Each update is a JSON event:
{ "event": "Updated", "value": { … } }
- URL:
# 1) Register Alice
curl -i -X POST http://localhost:6660/auth/alice \
-H "Content-Type: application/json" \
-d '{"secret":"s3cr3t"}'
# 2) Log in (get token)
TOKEN=$(curl -s -X POST http://localhost:6660/auth/alice \
-H "Content-Type: application/json" \
-d '{"secret":"s3cr3t"}' | jq -r .token)
# 3) PUT (must include token)
curl -i -X PUT http://localhost:6660/default/key/foo \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"value":"hello"}'
# 4) GET
curl -i http://localhost:6660/default/key/foo
# 5) DELETE (owner only)
curl -i -X DELETE http://localhost:6660/default/key/foo \
-H "Authorization: Bearer $TOKEN"
# 6) Node stats
curl http://localhost:6660/stats
Response:
{"tables":{"auth":2,"test":20088,"default":1},"total_keys":20091,"total_requests":5,"average_latency_ms":0.4171416,"active_sse_connections":0}
⚠️ PUT/DELETE without a valid JWT → 401 Unauthorized
🔍 GET is always open (no auth needed).
DynaRust uses eventual consistency via state synchronization:
- 👤 Client Request: A client sends an HTTP request (e.g.,
PUT /default/key/mykey) to any node. - 🖥️ Local Processing: The receiving node updates/reads its in‑memory store 💭 immediately.
- 💾 Persistence: The node periodically saves its memory state to
storage.dbfor durability. - 🔄 Cluster Synchronization (Gossip):
- Nodes exchange membership info & state updates periodically.
- This ensures all nodes eventually converge to the same state (Eventual Consistency).
- Real‑time Updates: Subscribed clients receive changes via SSE instantly (< 5ms) ⚡️.
- 🚀 Performance Highlight: A typical VPS node (1GB RAM, 100Mbps) handles up to 5000 concurrent live SSE connections. Add more nodes to scale capacity!
- 🤝 Joining: A new node contacts an existing node (
JOIN_ADDRESS), fetches the cluster state, and joins.
+----------------+
| Client |
| 👤 |
+----------------+
│ │ │ │
HTTP: POST /auth │ │ │ │ HTTP: PUT/GET/DELETE /{table}/key/{key}
(register/login) │ │ │ │
↓ ↓ ↓ ↓
+-----------------------+
| API Gateway (Actix) |
| • /auth/{user} | ←── issues JWT on login
| - registration |
| - login → JWT |
| • KV endpoints |
| - JWT guard on PUT/DELETE
| - GET open to all |
| • /{table}/store, |
| /{table}/keys |
| • /{table}/subscribe │ ←── SSE subscription
+-----------------------+
│
│ local reads/writes
↓
+-----------------------+
| In‐Memory Store |
| (HashMap<String, |
| HashMap<String, |
| VersionedValue>) |
| 💭 |
+-----------------------+
│ │
internal │ │ external
writes ←───┘ │ writes
(X-Internal-Request) │
↓
+-----------------------+
| Replication Module |
| (Fan‐out PUT/DEL to |
| all other nodes via |
| HTTP + X-Internal) |
+-----------------------+
│ │
│ gossip │ HTTP
│ ↓
+------------------------------------+
| Other Nodes (Replicas) |
| — apply internal writes — |
| |
+------------------------------------+
│
│ periodic
│ snapshot & WAL
↓
+-----------------------+
| Disk Persistence |
| (cold_save, WAL) |
| 💾 |
+-----------------------+
Cluster Membership & Gossip (Eventual Consistency)
──────────────────────────────────────────────────
┌───────────────┐ gossip(UDP/HTTP) ┌───────────────┐
│ Current Node │ ── heartbeat & membership ─│ Other Node │
│ (ClusterData)│ ──────────────────────────>│ (ClusterData)│
└───────────────┘ <─────────────────────────┘───────────────┘
🔄 🔄
Legend:
• Client: issues HTTP requests (and SSE connects).
• API Gateway: Actix routes, JWT auth, validation, SubscriptionManager.
• In‐Memory Store: local hashmaps of VersionedValue {value,version,timestamp,owner}.
• Replication Module: fan‑out writes to peers using `X-Internal-Request`.
• Other Nodes: receive internal requests, update memstore (no auth, no events).
• Disk Persistence: periodic snapshots + WAL for durability.
• Cluster Membership: heartbeat sync via broadcaster tasks for node discovery.
• SSE Subscriptions: real‐time `EventSource` streams on `/subscribe/{key}`.
Docker simplifies deployment and dependency management.
- Docker: Version 20.10 or newer installed and running.
- 🌐 Network Connectivity: Ensure containers on the same Docker network can reach each other on the
LISTEN_ADDRESSport (e.g.,6660).
-
🧱 Build the Image (if not done):
docker build -t dynarust:latest . -
🏃 Run the Container(s):
-
Running the First Node:
# Create a network first (recommended) docker network create dynanet # Start the first node, detached (-d), named, on the network, mapping host port 6660 docker run -d --name dynarust-node1 --network dynanet -p 6660:6660 \ dynarust:latest 0.0.0.0:6660
Listens on port 6660 inside the container.
-
Running Additional Nodes: Use the container name (
dynarust-node1) and internal port (6660) as theJOIN_ADDRESS.# Start a second node, map host port 6661, join node1 docker run -d --name dynarust-node2 --network dynanet -p 6661:6660 \ dynarust:latest 0.0.0.0:6660 dynarust-node1:6660 -
💾 Data Persistence (Important!): Mount a volume to persist the
storage.dbfile outside the container.# Create a named volume (e.g., dynarust-data1) docker volume create dynarust-data1 # Run node1 with the volume mounted to /app (where storage.db is saved) docker run -d --name dynarust-node1 --network dynanet -p 6660:6660 \ -v dynarust-data1:/app \ dynarust:latest 0.0.0.0:6660 # Run subsequent nodes with their own volumes docker volume create dynarust-data2 docker run -d --name dynarust-node2 --network dynanet -p 6661:6660 \ -v dynarust-data2:/app \ dynarust:latest 0.0.0.0:6660 dynarust-node1:6660
(Adjust the mount source/target
/appif your Dockerfile placesstorage.dbelsewhere).
-
You can either build DynaRust directly from source or use a pre‑built Docker image.
-
📂 Clone the Repository:
git clone https://github.com/yourfavDev/DynaRust # Replace with actual URL if needed cd DynaRust
-
🧱 Build the Project: Compile the Rust code for an optimized release build:
cargo build --release
The final binary is located at
target/release/DynaRust.
If you prefer containerization and have Docker installed:
- 🧱 Build the Docker Image:
From the repository’s root directory:
This command builds a container image named
docker build -t dynarust:latest .dynarustwith the taglatest.
After building (or using Docker), start DynaRust nodes using command‑line arguments to set the listening address and (optionally) join an existing cluster.
Command Syntax:
./target/release/DynaRust <LISTEN_ADDRESS> [JOIN_ADDRESS]LISTEN_ADDRESS(Required): 📡 The IP address and port for this node to listen on (e.g.,127.0.0.1:6660for local,0.0.0.0:6660for all interfaces).JOIN_ADDRESS(Optional): 🔗 The IP address and port of an existing DynaRust node to join. Omit this to start a new cluster.
Example 1: Start the First Node 🟢
Starts a single node locally on port 6660, forming a new cluster:
./target/release/DynaRust 127.0.0.1:6660Check the terminal for output logs 📝.
Example 2: Start a Second Node and Join the First 🔗
Starts a second node on port 6661 and joins the cluster via the first node (127.0.0.1:6660):
./target/release/DynaRust 127.0.0.1:6661 127.0.0.1:6660The new node synchronizes its state with the cluster 🤝.
Add more nodes by providing unique LISTEN_ADDRESS values and specifying any existing node as the JOIN_ADDRESS.
Interact with your DynaRust cluster using standard HTTP requests. Send requests to any node; the system handles routing and consistency internally.
❗️ Important Notes:
- Replace
localhost:6660in examples with the actualLISTEN_ADDRESSof a running node. - Replace
{table}with your desired table name (e.g.,default,users,products). If omitted, the"default"table is used implicitly in older versions, but explicit use is recommended. - For
PUTrequests, the body must be JSON (e.g.,{"value": "your-data"}) and theContent-Type: application/jsonheader must be set.
Encountering issues? Check these common points:
-
❌ Node Fails to Join Cluster:
- Target Node Running? Is the node at
JOIN_ADDRESSactive? - Network: Can the joining node reach the
JOIN_ADDRESS(IP & port)?- Native: Use
ping <ip>andnc -zv <ip> <port>. - Docker: Are containers on the same
docker network? Use container names (e.g.,dynarust-node1:6660). Checkdocker logs <container_name>.
- Native: Use
- Address/Port Match? Double-check
LISTEN_ADDRESSandJOIN_ADDRESS. - Logs: Check logs on both nodes (see Debugging below).
- Target Node Running? Is the node at
-
❓ Data Not Synchronizing / Inconsistent Reads:
- Patience: Eventual consistency takes time (usually milliseconds to seconds). Allow a brief moment for gossip.
- Membership: Check cluster view:
curl http://<any-node-address>/membership. Are all nodes listed? - Logs: Look for network errors or sync issues.
-
❓💾 Data Lost After Restart:
- Permissions: Does the process have write access to the directory containing
storage.db? Enough disk space? - Docker Persistence: Did you mount a volume correctly (see Deployment with Docker)? Without a volume, data is lost when the container stops.
- Permissions: Does the process have write access to the directory containing
-
🐞 General Debugging: Enable detailed logs using the
RUST_LOGenvironment variable:- Native Execution:
RUST_LOG=debug ./target/release/DynaRust <LISTEN_ADDRESS> [JOIN_ADDRESS]
- Docker Execution: Add
-e RUST_LOG=debugto yourdocker runcommand.docker run -d --name dynarust-node1 --network dynanet -p 6660:6660 \ -v dynarust-data1:/app \ -e RUST_LOG=debug \ dynarust:latest 0.0.0.0:6660
Then check logs:
docker logs dynarust-node1 - Native Execution:
With robust real‑time updates (up to 5000 live connections per node 🔥) and seamless scalability 🚀, DynaRust is ideal for applications needing instantaneous data propagation across distributed environments. Enjoy building! 🎉

