database_service.cpp 18 KB

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