Back to Site
Loading...
Searching...
No Matches
storage.h
Go to the documentation of this file.
1#pragma once
2
53#include "librats/util/rats_export.h"
55#include "librats/peer/peer.h"
57#include "librats/core/bytes.h"
58#include "librats/util/json.h"
59
60#include <string>
61#include <vector>
62#include <functional>
63#include <map>
64#include <memory>
65#include <mutex>
66#include <unordered_map>
67#include <atomic>
68#include <chrono>
69#include <thread>
70#include <optional>
71#include <condition_variable>
72
73namespace librats {
74
78enum class StorageValueType : uint8_t {
79 BINARY = 0x01, // Raw binary data
80 STRING = 0x02, // UTF-8 string
81 INT64 = 0x03, // 64-bit signed integer
82 DOUBLE = 0x04, // 64-bit floating point
83 JSON = 0x05 // JSON document
84};
85
89enum class StorageOperation : uint8_t {
90 OP_PUT = 0x01, // Insert or update
91 OP_DELETE = 0x02 // Delete key
92};
93
98 NOT_STARTED, // Sync not initiated
99 IN_PROGRESS, // Sync currently running
100 COMPLETED, // Sync completed successfully
101 FAILED // Sync failed
102};
103
107struct RATS_API StorageEntry {
108 std::string key; // Key string
109 StorageValueType type; // Value type
110 std::vector<uint8_t> data; // Serialized value data
111 uint64_t timestamp_ms; // Unix timestamp in milliseconds (for LWW)
112 std::string origin_peer_id; // Peer that created/modified this entry (hex PeerId)
113 uint32_t checksum; // CRC32 checksum for integrity
114 bool deleted; // Tombstone marker for deleted entries
115
117 : type(StorageValueType::BINARY),
118 timestamp_ms(0),
119 checksum(0),
120 deleted(false) {}
121
122 StorageEntry(const std::string& k, StorageValueType t,
123 const std::vector<uint8_t>& d, uint64_t ts,
124 const std::string& peer_id)
125 : key(k), type(t), data(d), timestamp_ms(ts),
126 origin_peer_id(peer_id), checksum(0), deleted(false) {
127 calculate_checksum();
128 }
129
130 // Calculate CRC32 checksum
132
133 // Verify checksum
134 bool verify_checksum() const;
135
136 // Serialize entry to binary format
137 std::vector<uint8_t> serialize() const;
138
142 void serialize_into(std::vector<uint8_t>& out) const;
143
145 size_t serialized_size() const;
146
151 static bool deserialize(const uint8_t* data, size_t size, size_t offset,
152 StorageEntry& entry, size_t& bytes_read);
153
156 static bool deserialize(const std::vector<uint8_t>& data, size_t offset,
157 StorageEntry& entry, size_t& bytes_read);
158
159 // Compare for LWW resolution (returns true if this entry wins)
160 bool wins_over(const StorageEntry& other) const;
161};
162
167 StorageOperation operation; // PUT or DELETE
168 std::string key; // Affected key
169 StorageValueType type; // Value type (for PUT)
170 std::vector<uint8_t> old_data; // Previous value (if any)
171 std::vector<uint8_t> new_data; // New value (for PUT)
172 uint64_t timestamp_ms; // Operation timestamp
173 std::string origin_peer_id; // Peer that made the change
174 bool is_remote; // True if change came from another peer
175};
176
192 static constexpr uint32_t kMaxValueSize = 2 * 1024 * 1024;
194 static constexpr uint32_t kMaxSyncBatchBytes = 2 * 1024 * 1024;
197 static constexpr uint32_t kMinSyncBatchBytes = 16 * 1024;
198
199 std::string data_directory; // Directory for storage files
200 std::string database_name; // Database filename prefix
201 bool enable_sync; // Enable network synchronization
202 uint32_t compaction_threshold; // Number of tombstones before compaction
203 uint32_t max_value_size; // Maximum value size in bytes (<= kMaxValueSize)
204 bool persist_to_disk; // Whether to persist data to disk
213
215 : data_directory("./storage"),
216 database_name("rats_storage"),
217 enable_sync(true),
219 max_value_size(1024 * 1024), // 1 MiB max value size
220 persist_to_disk(true),
221 sync_batch_bytes(256 * 1024), // 256 KiB per snapshot chunk
222 sync_min_interval_ms(5000) {}
223};
224
229 size_t total_entries; // Total number of entries
230 size_t deleted_entries; // Number of tombstones
231 uint64_t total_data_bytes; // Total size of stored data
232 uint64_t disk_usage_bytes; // Disk space used
233 uint64_t entries_synced; // Entries synced from peers
234 uint64_t entries_sent; // Entries sent to peers
235 uint64_t sync_requests_received; // Number of sync requests received
236 uint64_t sync_requests_sent; // Number of sync requests sent
237 uint64_t sync_chunks_sent; // Snapshot chunks put on the wire
238 uint64_t sync_chunks_received; // Snapshot chunks applied from peers
239 uint64_t resyncs_scheduled; // Snapshots owed to peers that filled up
240 std::chrono::steady_clock::time_point last_sync_time; // Last sync timestamp
241 StorageSyncStatus sync_status; // Current sync status
242};
243
247using StorageChangeCallback = std::function<void(const StorageChangeEvent&)>;
248using StorageSyncCompleteCallback = std::function<void(bool success, const std::string& error_message)>;
249
257class RATS_API StorageManager final : public Subsystem {
258public:
267 explicit StorageManager(const StorageConfig& config = StorageConfig());
268
272 ~StorageManager() override;
273
276
277 // =========================================================================
278 // Subsystem
279 // =========================================================================
280
281 void attach(NodeContext& ctx) override;
282 void start() override;
283 void stop() override;
284
285 // =========================================================================
286 // Configuration
287 // =========================================================================
288
292 void set_config(const StorageConfig& config);
293 const StorageConfig& get_config() const;
294
295 // =========================================================================
296 // Put Operations (Write)
297 // =========================================================================
298
299 bool put(const std::string& key, const std::string& value);
300 bool put(const std::string& key, int64_t value);
301 bool put(const std::string& key, double value);
302 bool put(const std::string& key, const std::vector<uint8_t>& value);
303 bool put_json(const std::string& key, const librats::Json& value);
304
305 // =========================================================================
306 // Get Operations (Read)
307 // =========================================================================
308
309 std::optional<std::string> get_string(const std::string& key) const;
310 std::optional<int64_t> get_int(const std::string& key) const;
311 std::optional<double> get_double(const std::string& key) const;
312 std::optional<std::vector<uint8_t>> get_binary(const std::string& key) const;
313 std::optional<librats::Json> get_json(const std::string& key) const;
314 std::optional<StorageValueType> get_type(const std::string& key) const;
315
316 // =========================================================================
317 // Delete and Query Operations
318 // =========================================================================
319
320 bool remove(const std::string& key);
321 bool has(const std::string& key) const;
322 std::vector<std::string> keys() const;
323 std::vector<std::string> keys_with_prefix(const std::string& prefix) const;
324 size_t size() const;
325 bool empty() const;
326 void clear();
327
328 // =========================================================================
329 // Persistence Operations
330 // =========================================================================
331
332 bool save();
333 bool load();
334 size_t compact();
335
336 // =========================================================================
337 // Synchronization Operations
338 // =========================================================================
339
343 bool is_synced() const;
344
345 // =========================================================================
346 // Event Callbacks
347 // =========================================================================
348
351
352 // =========================================================================
353 // Statistics
354 // =========================================================================
355
358
359private:
360 // Network message handlers (run on a reactor thread).
361 void on_storage_message(const PeerId& from, ByteView payload);
362 void on_peer_connected(const PeerId& peer_id);
363 void on_peer_disconnected(const PeerId& peer_id);
364 void on_peer_writable(const PeerId& peer_id);
365
366 PeerNetwork* network_ = nullptr;
367 StorageConfig config_;
368
369 // In-memory storage.
370 //
371 // Ordered, not hashed, and that is load-bearing: a snapshot is streamed one
372 // bounded chunk at a time, and between chunks the only thing carried over is
373 // the last key sent. An ordered map turns that key back into a position with
374 // upper_bound(), so a stream costs nothing to keep alive and cannot be
375 // derailed by concurrent writes — an unordered_map would need either a
376 // materialised key list per peer or iterators that a rehash invalidates.
377 // It also makes keys_with_prefix() a range scan instead of a full walk.
378 mutable std::mutex storage_mutex_;
379 std::map<std::string, StorageEntry> entries_;
380
381 // Sync state.
382 //
383 // Lock order, exactly one rule: storage_mutex_ and sync_mutex_ are never held
384 // at the same time, and neither is ever held across a call into PeerNetwork.
385 mutable std::mutex sync_mutex_;
386 StorageSyncStatus sync_status_;
387 bool initial_sync_complete_;
388 std::chrono::steady_clock::time_point last_sync_time_;
389
392 struct PeerSync {
393 bool streaming = false;
394 bool started = false;
395 std::string cursor;
396 bool owed = false;
397 std::chrono::steady_clock::time_point last_start{};
398 };
399 std::unordered_map<PeerId, PeerSync, PeerId::Hash> peers_;
405 uint64_t sync_epoch_ = 0;
406
407 // The sync thread: serializes and paces every snapshot chunk, so no reactor
408 // thread ever does work proportional to the size of the database.
409 std::atomic<bool> sync_running_{false};
410 std::thread sync_thread_;
411 std::condition_variable sync_cv_;
412 // Sync tuning, copied out of the config when the thread starts and read-only
413 // afterwards, so the thread never races a set_config() on another thread.
414 size_t batch_bytes_{0};
415 std::chrono::milliseconds sync_interval_{0};
416
417 // Statistics
418 mutable std::mutex stats_mutex_;
419 StorageStatistics stats_;
420
421 // Callbacks
422 StorageChangeCallback change_callback_;
423 StorageSyncCompleteCallback sync_complete_callback_;
424
425 // Background persistence thread
426 std::atomic<bool> running_;
427 std::thread persistence_thread_;
428 std::condition_variable persistence_cv_;
429 std::mutex persistence_mutex_;
430 bool dirty_; // Flag indicating unsaved changes
431
432 // Wire opcodes (MessageType::Storage payload, byte 0)
433 static constexpr uint8_t OP_ENTRY = 1;
434 static constexpr uint8_t OP_SYNC_REQUEST = 2;
435 static constexpr uint8_t OP_SYNC_CHUNK = 3;
436
437 // SYNC_CHUNK flags (byte 1)
438 static constexpr uint8_t FLAG_LAST = 0x01;
439
444 static constexpr size_t kMaxInboundMessage = 8 * 1024 * 1024;
445
446 // Private methods
447 void initialize();
448 void shutdown();
449 void persistence_thread_loop();
450 void start_sync_thread();
451 void stop_sync_thread();
452 void sync_thread_loop();
454 enum class ChunkResult {
455 Continue,
456 Blocked,
457 Finished
458 };
460 ChunkResult stream_snapshot_chunk(const PeerId& peer);
463 void schedule_snapshot(const PeerId& peer, bool requested_by_peer);
464
465 // Internal put with full control
466 bool put_internal(const std::string& key, StorageValueType type,
467 const std::vector<uint8_t>& data,
468 uint64_t timestamp_ms = 0,
469 const std::string& origin_peer_id = "",
470 bool broadcast = true);
471
472 // Serialization helpers
473 std::vector<uint8_t> serialize_value(int64_t value) const;
474 std::vector<uint8_t> serialize_value(double value) const;
475 std::vector<uint8_t> serialize_value(const std::string& value) const;
476 int64_t deserialize_int64(const std::vector<uint8_t>& data) const;
477 double deserialize_double(const std::vector<uint8_t>& data) const;
478 std::string deserialize_string(const std::vector<uint8_t>& data) const;
479
480 // Network operations. Every one of them honours send()'s return value: a peer
481 // that answers "no room" is owed a snapshot instead of further entries.
483 void replicate_entry(const StorageEntry& entry, const PeerId* except);
484 void broadcast_entry(const StorageEntry& entry) { replicate_entry(entry, nullptr); }
485 void forward_entry(const StorageEntry& entry, const PeerId& except) {
486 replicate_entry(entry, &except);
487 }
488 void send_sync_request(const PeerId& peer_id);
489
490 // Apply a remote entry with LWW; fills `out_event` and returns true if applied.
491 bool apply_remote_entry(const StorageEntry& entry, StorageChangeEvent* out_event);
493 uint32_t apply_chunk(const PeerId& from, const uint8_t* data, size_t size, uint32_t count);
494
495 // File path helpers
496 std::string get_data_file_path() const;
497 std::string get_index_file_path() const;
498
499 // Disk I/O
500 bool write_data_file();
501 bool read_data_file();
502
503 // Utility
505 static void sanitize_config(StorageConfig& config);
506 uint64_t get_current_timestamp_ms() const;
507 std::string get_our_peer_id() const;
508 void notify_change(const StorageChangeEvent& event);
509 void mark_dirty();
510};
511
512// CRC32 calculation utility function
513uint32_t storage_calculate_crc32(const void* data, size_t length);
514
515// Convert StorageValueType to string
517
518// Convert string to StorageValueType
520
521} // namespace librats
Lightweight byte container aliases and a non-owning byte view.
Non-owning view over a contiguous run of bytes.
Definition bytes.h:27
StorageManager - Distributed key-value storage with peer synchronization.
Definition storage.h:257
librats::Json get_statistics_json() const
bool request_sync()
Request a full snapshot from one connected peer.
bool put(const std::string &key, double value)
bool put(const std::string &key, const std::vector< uint8_t > &value)
std::optional< std::string > get_string(const std::string &key) const
bool put_json(const std::string &key, const librats::Json &value)
void start() override
StorageManager & operator=(const StorageManager &)=delete
std::optional< librats::Json > get_json(const std::string &key) const
void set_config(const StorageConfig &config)
Replace the configuration.
std::vector< std::string > keys() const
const StorageConfig & get_config() const
void stop() override
StorageManager(const StorageManager &)=delete
void set_change_callback(StorageChangeCallback callback)
StorageSyncStatus get_sync_status() const
void attach(NodeContext &ctx) override
std::optional< double > get_double(const std::string &key) const
std::optional< std::vector< uint8_t > > get_binary(const std::string &key) const
bool put(const std::string &key, int64_t value)
StorageManager(const StorageConfig &config=StorageConfig())
Constructor.
std::optional< StorageValueType > get_type(const std::string &key) const
bool remove(const std::string &key)
std::optional< int64_t > get_int(const std::string &key) const
StorageStatistics get_statistics() const
bool has(const std::string &key) const
void set_sync_complete_callback(StorageSyncCompleteCallback callback)
~StorageManager() override
Destructor - saves data and cleans up resources.
bool put(const std::string &key, const std::string &value)
std::vector< std::string > keys_with_prefix(const std::string &prefix) const
A pluggable network subsystem.
A small, self-contained JSON value type for librats.
Definition node.h:73
StorageValueType string_to_storage_value_type(const std::string &str)
StorageValueType
Value types supported by the distributed storage.
Definition storage.h:78
uint32_t storage_calculate_crc32(const void *data, size_t length)
std::function< void(const StorageChangeEvent &)> StorageChangeCallback
Callback function types for storage events.
Definition storage.h:247
StorageSyncStatus
Storage synchronization status.
Definition storage.h:97
StorageOperation
Storage operation types for change events.
Definition storage.h:89
std::function< void(bool success, const std::string &error_message)> StorageSyncCompleteCallback
Definition storage.h:248
std::string storage_value_type_to_string(StorageValueType type)
A lightweight handle to a connected peer.
Self-certifying peer identity.
The narrow contract a subsystem needs from the node — and nothing more.
Storage change event - passed to change callbacks.
Definition storage.h:166
StorageValueType type
Definition storage.h:169
std::vector< uint8_t > old_data
Definition storage.h:170
std::vector< uint8_t > new_data
Definition storage.h:171
StorageOperation operation
Definition storage.h:167
Storage configuration.
Definition storage.h:188
static constexpr uint32_t kMaxSyncBatchBytes
Ceiling on sync_batch_bytes, for the same reason.
Definition storage.h:194
std::string data_directory
Definition storage.h:199
static constexpr uint32_t kMaxValueSize
Ceiling on max_value_size: the default connection low-water mark.
Definition storage.h:192
uint32_t max_value_size
Definition storage.h:203
uint32_t sync_min_interval_ms
Minimum gap between two snapshots served to the same peer.
Definition storage.h:212
std::string database_name
Definition storage.h:200
uint32_t compaction_threshold
Definition storage.h:202
uint32_t sync_batch_bytes
Target payload size of one snapshot chunk.
Definition storage.h:208
static constexpr uint32_t kMinSyncBatchBytes
Floor on sync_batch_bytes: below this a snapshot costs more in per-message framing and round trips th...
Definition storage.h:197
Storage entry - represents a single key-value pair in the database.
Definition storage.h:107
uint64_t timestamp_ms
Definition storage.h:111
void serialize_into(std::vector< uint8_t > &out) const
Append the serialized entry to out.
StorageValueType type
Definition storage.h:109
std::vector< uint8_t > data
Definition storage.h:110
std::string key
Definition storage.h:108
static bool deserialize(const uint8_t *data, size_t size, size_t offset, StorageEntry &entry, size_t &bytes_read)
Deserialize one entry from data[offset..], setting bytes_read to the bytes it consumed.
bool verify_checksum() const
StorageEntry(const std::string &k, StorageValueType t, const std::vector< uint8_t > &d, uint64_t ts, const std::string &peer_id)
Definition storage.h:122
std::vector< uint8_t > serialize() const
std::string origin_peer_id
Definition storage.h:112
bool wins_over(const StorageEntry &other) const
static bool deserialize(const std::vector< uint8_t > &data, size_t offset, StorageEntry &entry, size_t &bytes_read)
Vector overload of the above; the wire path uses the pointer form to parse straight out of the receiv...
size_t serialized_size() const
Serialized size in bytes, without serializing.
Storage statistics.
Definition storage.h:228
std::chrono::steady_clock::time_point last_sync_time
Definition storage.h:240
uint64_t sync_requests_received
Definition storage.h:235
StorageSyncStatus sync_status
Definition storage.h:241