database_service.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. #include "database_service.hpp"
  2. #include <grpcpp/grpcpp.h>
  3. #include <spdlog/spdlog.h>
  4. #include <fstream>
  5. namespace smartbotic::database {
  6. DatabaseService::DatabaseService(Config config)
  7. : config_(std::move(config))
  8. {
  9. }
  10. DatabaseService::~DatabaseService() {
  11. stop();
  12. }
  13. bool DatabaseService::initialize() {
  14. spdlog::info("Initializing database service (node: {})", config_.nodeId);
  15. try {
  16. // Create data directory if needed
  17. std::error_code ec;
  18. std::filesystem::create_directories(config_.dataDirectory, ec);
  19. if (ec) {
  20. spdlog::error("Failed to create data directory: {}", ec.message());
  21. return false;
  22. }
  23. setupComponents();
  24. // Initialize encryption
  25. if (config_.encryptionEnabled) {
  26. if (!encryption_->initialize()) {
  27. spdlog::error("Failed to initialize encryption");
  28. return false;
  29. }
  30. }
  31. // Recover from persistence
  32. persistence_->recover(*store_);
  33. // Set replication sequence after recovery (WAL sequence is now known)
  34. replication_->setSequence(persistence_->currentWalSequence());
  35. // Run migrations if enabled
  36. if (config_.migrations.enabled && !config_.migrations.directory.empty()) {
  37. if (!runMigrations()) {
  38. if (config_.migrations.failOnError) {
  39. spdlog::error("Failed to run migrations");
  40. return false;
  41. }
  42. spdlog::warn("Some migrations failed, continuing anyway");
  43. }
  44. }
  45. spdlog::info("Database service initialized successfully");
  46. return true;
  47. } catch (const std::exception& e) {
  48. spdlog::error("Failed to initialize database service: {}", e.what());
  49. return false;
  50. }
  51. }
  52. bool DatabaseService::runMigrations() {
  53. if (!config_.migrations.enabled) {
  54. return true;
  55. }
  56. MigrationRunner::Config migrationConfig;
  57. migrationConfig.directory = config_.migrations.directory;
  58. migrationConfig.autoApply = config_.migrations.autoApply;
  59. migrationConfig.failOnError = config_.migrations.failOnError;
  60. migrationRunner_ = std::make_unique<MigrationRunner>(*store_, migrationConfig);
  61. return migrationRunner_->runMigrations();
  62. }
  63. void DatabaseService::start() {
  64. if (running_.exchange(true)) {
  65. return;
  66. }
  67. spdlog::info("Starting database service on {}:{}", config_.bindAddress, config_.rpcPort);
  68. // Start components
  69. store_->start();
  70. persistence_->start();
  71. events_->start();
  72. files_->start();
  73. if (config_.replicationEnabled) {
  74. replication_->start();
  75. // Set initial local collections for discovery
  76. replication_->setLocalCollections(store_->listCollections());
  77. }
  78. // Start gRPC server
  79. startGrpcServer();
  80. spdlog::info("Database service started");
  81. }
  82. void DatabaseService::stop() {
  83. if (!running_.exchange(false)) {
  84. return;
  85. }
  86. spdlog::info("Stopping database service...");
  87. // Stop gRPC server first
  88. stopGrpcServer();
  89. // Stop components in reverse order
  90. if (replication_) {
  91. replication_->stop();
  92. }
  93. if (files_) {
  94. files_->stop();
  95. }
  96. if (events_) {
  97. events_->stop();
  98. }
  99. if (persistence_) {
  100. persistence_->stop();
  101. }
  102. if (store_) {
  103. store_->stop();
  104. }
  105. spdlog::info("Database service stopped");
  106. }
  107. void DatabaseService::wait() {
  108. if (serverThread_.joinable()) {
  109. serverThread_.join();
  110. }
  111. }
  112. void DatabaseService::signalStop() {
  113. stopRequested_ = true;
  114. stop();
  115. }
  116. nlohmann::json DatabaseService::getStats() const {
  117. nlohmann::json stats;
  118. if (store_) {
  119. auto storeStats = store_->getStats();
  120. stats["documents"] = storeStats.totalDocuments;
  121. stats["collections"] = storeStats.totalCollections;
  122. stats["memory_bytes"] = storeStats.estimatedMemoryBytes;
  123. stats["inserts"] = storeStats.insertCount;
  124. stats["updates"] = storeStats.updateCount;
  125. stats["deletes"] = storeStats.deleteCount;
  126. stats["queries"] = storeStats.queryCount;
  127. }
  128. if (persistence_) {
  129. auto persistStats = persistence_->getStats();
  130. stats["wal_sequence"] = persistStats.walSequence;
  131. stats["wal_size_bytes"] = persistStats.walSizeBytes;
  132. stats["snapshot_count"] = persistStats.snapshotCount;
  133. stats["last_snapshot_sequence"] = persistStats.lastSnapshotSequence;
  134. }
  135. if (events_) {
  136. stats["subscriptions"] = events_->subscriptionCount();
  137. }
  138. return stats;
  139. }
  140. DatabaseService::Config DatabaseService::loadConfig(const std::filesystem::path& configPath) {
  141. std::ifstream file(configPath);
  142. if (!file) {
  143. throw std::runtime_error("Failed to open config file: " + configPath.string());
  144. }
  145. nlohmann::json json;
  146. file >> json;
  147. Config config;
  148. // Check for both "storage" and "database" keys for backward compatibility
  149. nlohmann::json* dbConfig = nullptr;
  150. if (json.contains("database")) {
  151. dbConfig = &json["database"];
  152. } else if (json.contains("storage")) {
  153. dbConfig = &json["storage"];
  154. }
  155. if (dbConfig) {
  156. auto& db = *dbConfig;
  157. config.nodeId = db.value("node_id", config.nodeId);
  158. config.bindAddress = db.value("bind_address", config.bindAddress);
  159. config.rpcPort = db.value("rpc_port", config.rpcPort);
  160. // Expand environment variables in data directory
  161. std::string dataDir = db.value("data_directory", "");
  162. if (dataDir.find("${HOME}") != std::string::npos) {
  163. const char* home = std::getenv("HOME");
  164. if (home) {
  165. size_t pos = dataDir.find("${HOME}");
  166. dataDir.replace(pos, 7, home);
  167. }
  168. }
  169. config.dataDirectory = dataDir;
  170. // Migrations settings
  171. if (db.contains("migrations")) {
  172. auto& migrations = db["migrations"];
  173. config.migrations.enabled = migrations.value("enabled", config.migrations.enabled);
  174. config.migrations.autoApply = migrations.value("auto_apply", config.migrations.autoApply);
  175. config.migrations.failOnError = migrations.value("fail_on_error", config.migrations.failOnError);
  176. std::string migDir = migrations.value("directory", "");
  177. if (migDir.find("${HOME}") != std::string::npos) {
  178. const char* home = std::getenv("HOME");
  179. if (home) {
  180. size_t pos = migDir.find("${HOME}");
  181. migDir.replace(pos, 7, home);
  182. }
  183. }
  184. config.migrations.directory = migDir;
  185. }
  186. // Memory eviction settings
  187. if (db.contains("memory")) {
  188. auto& memory = db["memory"];
  189. config.maxMemoryMb = memory.value("max_memory_mb", config.maxMemoryMb);
  190. config.evictionThresholdPercent = memory.value("eviction_threshold_percent", config.evictionThresholdPercent);
  191. config.evictionTargetPercent = memory.value("eviction_target_percent", config.evictionTargetPercent);
  192. config.evictionCheckIntervalMs = memory.value("eviction_check_interval_ms", config.evictionCheckIntervalMs);
  193. }
  194. // Persistence settings
  195. if (db.contains("persistence")) {
  196. auto& persistence = db["persistence"];
  197. config.walSyncIntervalMs = persistence.value("wal_sync_interval_ms", config.walSyncIntervalMs);
  198. config.snapshotIntervalSec = persistence.value("snapshot_interval_sec", config.snapshotIntervalSec);
  199. config.compressionEnabled = persistence.value("compression", "lz4") != "none";
  200. }
  201. // File settings
  202. if (db.contains("files")) {
  203. auto& files = db["files"];
  204. config.maxFileSizeMb = files.value("max_file_size_mb", config.maxFileSizeMb);
  205. config.fileCleanupIntervalSec = files.value("cleanup_orphans_interval_sec", config.fileCleanupIntervalSec);
  206. if (files.contains("allowed_types")) {
  207. for (const auto& type : files["allowed_types"]) {
  208. config.allowedFileTypes.push_back(type.get<std::string>());
  209. }
  210. }
  211. }
  212. // Encryption settings
  213. if (db.contains("encryption")) {
  214. auto& encryption = db["encryption"];
  215. config.encryptionEnabled = encryption.value("enabled", config.encryptionEnabled);
  216. config.autoGenerateKey = encryption.value("auto_generate_key", config.autoGenerateKey);
  217. std::string keyFile = encryption.value("key_file", "");
  218. if (keyFile.find("${HOME}") != std::string::npos) {
  219. const char* home = std::getenv("HOME");
  220. if (home) {
  221. size_t pos = keyFile.find("${HOME}");
  222. keyFile.replace(pos, 7, home);
  223. }
  224. }
  225. config.keyFilePath = keyFile;
  226. }
  227. // Replication settings
  228. if (db.contains("replication")) {
  229. auto& replication = db["replication"];
  230. config.replicationEnabled = replication.value("enabled", config.replicationEnabled);
  231. config.conflictResolution = replication.value("conflict_resolution", config.conflictResolution);
  232. if (replication.contains("peers")) {
  233. for (const auto& peer : replication["peers"]) {
  234. config.peerAddresses.push_back(peer.get<std::string>());
  235. }
  236. }
  237. }
  238. }
  239. return config;
  240. }
  241. void DatabaseService::setupComponents() {
  242. // Create memory store with eviction config
  243. MemoryStore::Config storeConfig;
  244. storeConfig.nodeId = config_.nodeId;
  245. storeConfig.maxMemoryBytes = config_.maxMemoryMb * 1024ULL * 1024ULL;
  246. storeConfig.evictionThresholdPercent = config_.evictionThresholdPercent;
  247. storeConfig.evictionTargetPercent = config_.evictionTargetPercent;
  248. storeConfig.evictionCheckIntervalMs = config_.evictionCheckIntervalMs;
  249. store_ = std::make_unique<MemoryStore>(storeConfig);
  250. // Create persistence manager
  251. PersistenceManager::Config persistConfig;
  252. persistConfig.dataDir = config_.dataDirectory;
  253. persistConfig.walSyncIntervalMs = config_.walSyncIntervalMs;
  254. persistConfig.snapshotIntervalSec = config_.snapshotIntervalSec;
  255. persistConfig.compressionEnabled = config_.compressionEnabled;
  256. persistence_ = std::make_unique<PersistenceManager>(persistConfig);
  257. // Connect store callbacks to persistence - uses setPersistCallback for WAL logging
  258. store_->setPersistCallback([this](const std::string& collection, const std::string& id,
  259. const std::optional<Document>& doc, EventType eventType) {
  260. // Log to WAL based on event type
  261. switch (eventType) {
  262. case EventType::INSERT:
  263. if (doc) persistence_->logInsert(collection, *doc);
  264. break;
  265. case EventType::UPDATE:
  266. if (doc) persistence_->logUpdate(collection, *doc);
  267. break;
  268. case EventType::DELETE:
  269. persistence_->logDelete(collection, id);
  270. break;
  271. default:
  272. break;
  273. }
  274. // Queue for replication broadcast
  275. if (replication_ && config_.replicationEnabled) {
  276. databasepb::ReplicationEntry entry;
  277. entry.set_collection(collection);
  278. entry.set_document_id(id);
  279. entry.set_global_timestamp(
  280. std::chrono::duration_cast<std::chrono::milliseconds>(
  281. std::chrono::system_clock::now().time_since_epoch()
  282. ).count());
  283. switch (eventType) {
  284. case EventType::INSERT:
  285. entry.set_op(databasepb::OP_INSERT);
  286. if (doc) entry.set_data(doc->data.dump());
  287. break;
  288. case EventType::UPDATE:
  289. entry.set_op(databasepb::OP_UPDATE);
  290. if (doc) entry.set_data(doc->data.dump());
  291. break;
  292. case EventType::DELETE:
  293. entry.set_op(databasepb::OP_DELETE);
  294. break;
  295. default:
  296. break;
  297. }
  298. replication_->queueForReplication(entry);
  299. }
  300. // Publish event
  301. if (events_) {
  302. DatabaseEvent event;
  303. event.type = eventType;
  304. event.collection = collection;
  305. event.documentId = id;
  306. event.timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
  307. std::chrono::system_clock::now().time_since_epoch()
  308. ).count();
  309. event.nodeId = config_.nodeId;
  310. if (doc) {
  311. event.data = doc->data;
  312. }
  313. events_->publish(event);
  314. }
  315. });
  316. // Set up document load callback for LRU eviction recovery
  317. store_->setDocumentLoadCallback([this](const std::string& collection,
  318. const std::string& id,
  319. uint64_t walSequence) -> std::optional<Document> {
  320. // Load document from WAL via persistence manager
  321. return persistence_->loadDocument(collection, id, 0); // Search from beginning
  322. });
  323. // Create encryption manager
  324. EncryptionManager::Config encryptConfig;
  325. encryptConfig.enabled = config_.encryptionEnabled;
  326. encryptConfig.keyFilePath = config_.keyFilePath;
  327. encryptConfig.autoGenerateKey = config_.autoGenerateKey;
  328. encryption_ = std::make_unique<EncryptionManager>(encryptConfig);
  329. // Create event manager
  330. EventManager::Config eventConfig;
  331. eventConfig.nodeId = config_.nodeId;
  332. events_ = std::make_unique<EventManager>(eventConfig);
  333. // Create file manager
  334. FileManager::Config fileConfig;
  335. fileConfig.filesDir = config_.dataDirectory / "files";
  336. fileConfig.maxFileSizeMb = config_.maxFileSizeMb;
  337. fileConfig.allowedMimeTypes = config_.allowedFileTypes;
  338. fileConfig.cleanupIntervalSec = config_.fileCleanupIntervalSec;
  339. files_ = std::make_unique<FileManager>(fileConfig);
  340. // Create replication manager
  341. ReplicationManager::Config replConfig;
  342. replConfig.nodeId = config_.nodeId;
  343. replConfig.peerAddresses = config_.peerAddresses;
  344. replConfig.conflictResolution = config_.conflictResolution;
  345. replication_ = std::make_unique<ReplicationManager>(replConfig);
  346. // Wire replication entry apply callback
  347. replication_->setEntryApplyCallback([this](const databasepb::ReplicationEntry& entry) {
  348. applyReplicatedEntry(entry);
  349. });
  350. // Create gRPC implementations
  351. storageImpl_ = std::make_unique<DatabaseGrpcImpl>(
  352. *store_, *persistence_, *events_, *files_, *encryption_
  353. );
  354. replicationImpl_ = std::make_unique<DatabaseReplicationGrpcImpl>(
  355. *store_, *replication_, *persistence_
  356. );
  357. }
  358. void DatabaseService::applyReplicatedEntry(const databasepb::ReplicationEntry& entry) {
  359. try {
  360. switch (entry.op()) {
  361. case databasepb::OP_INSERT:
  362. case databasepb::OP_UPDATE:
  363. case databasepb::OP_UPSERT: {
  364. if (entry.data().empty()) {
  365. spdlog::warn("Replication entry has no data for op {}", static_cast<int>(entry.op()));
  366. return;
  367. }
  368. auto json = nlohmann::json::parse(entry.data());
  369. Document doc = Document::fromJson(json);
  370. doc.id = entry.document_id();
  371. doc.nodeId = entry.node_id();
  372. // Use loadDocument to bypass normal callbacks (avoid re-replication)
  373. store_->loadDocument(entry.collection(), doc);
  374. spdlog::trace("Applied replicated {} to {}/{} from {}",
  375. entry.op() == databasepb::OP_INSERT ? "insert" :
  376. entry.op() == databasepb::OP_UPDATE ? "update" : "upsert",
  377. entry.collection(), entry.document_id(), entry.node_id());
  378. break;
  379. }
  380. case databasepb::OP_DELETE: {
  381. store_->remove(entry.collection(), entry.document_id());
  382. spdlog::trace("Applied replicated delete to {}/{} from {}",
  383. entry.collection(), entry.document_id(), entry.node_id());
  384. break;
  385. }
  386. case databasepb::OP_CREATE_COLLECTION: {
  387. CollectionOptions options;
  388. store_->createCollection(entry.collection(), options);
  389. spdlog::trace("Applied replicated create collection {} from {}",
  390. entry.collection(), entry.node_id());
  391. break;
  392. }
  393. case databasepb::OP_DROP_COLLECTION: {
  394. store_->dropCollection(entry.collection());
  395. spdlog::trace("Applied replicated drop collection {} from {}",
  396. entry.collection(), entry.node_id());
  397. break;
  398. }
  399. default:
  400. spdlog::warn("Unknown replication operation type: {}", static_cast<int>(entry.op()));
  401. break;
  402. }
  403. } catch (const std::exception& e) {
  404. spdlog::error("Failed to apply replicated entry: {}", e.what());
  405. }
  406. }
  407. void DatabaseService::startGrpcServer() {
  408. std::string serverAddress = config_.bindAddress + ":" + std::to_string(config_.rpcPort);
  409. grpc::ServerBuilder builder;
  410. builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials());
  411. builder.RegisterService(storageImpl_.get());
  412. builder.RegisterService(replicationImpl_.get());
  413. // Configure server
  414. builder.SetMaxReceiveMessageSize(100 * 1024 * 1024); // 100MB for file uploads
  415. builder.SetMaxSendMessageSize(100 * 1024 * 1024);
  416. grpcServer_ = builder.BuildAndStart();
  417. if (!grpcServer_) {
  418. throw std::runtime_error("Failed to start gRPC server on " + serverAddress);
  419. }
  420. spdlog::info("gRPC server listening on {}", serverAddress);
  421. }
  422. void DatabaseService::stopGrpcServer() {
  423. if (grpcServer_) {
  424. grpcServer_->Shutdown();
  425. grpcServer_.reset();
  426. }
  427. }
  428. } // namespace smartbotic::database