diff --git a/CMakeLists.txt b/CMakeLists.txt index cbf81e6..2be95c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(tidesdb_cpp VERSION 2.3.4 LANGUAGES CXX) +project(tidesdb_cpp VERSION 2.3.5 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/include/tidesdb/tidesdb.hpp b/include/tidesdb/tidesdb.hpp index e910e32..b35356d 100644 --- a/include/tidesdb/tidesdb.hpp +++ b/include/tidesdb/tidesdb.hpp @@ -256,6 +256,28 @@ struct Stats double btreeAvgHeight = 0.0; ///< Average tree height across all SSTables }; +/** + * @brief Database-level statistics + */ +struct DbStats +{ + int numColumnFamilies = 0; + std::uint64_t totalMemory = 0; + std::uint64_t availableMemory = 0; + std::size_t resolvedMemoryLimit = 0; + int memoryPressureLevel = 0; + int flushPendingCount = 0; + std::int64_t totalMemtableBytes = 0; + int totalImmutableCount = 0; + int totalSstableCount = 0; + std::uint64_t totalDataSizeBytes = 0; + int numOpenSstables = 0; + std::uint64_t globalSeq = 0; + std::int64_t txnMemoryBytes = 0; + std::size_t compactionQueueSize = 0; + std::size_t flushQueueSize = 0; +}; + /** * @brief Block cache statistics */ @@ -306,6 +328,14 @@ class ColumnFamily */ void purge(); + /** + * @brief Force an immediate fsync of the active write-ahead log + * + * Useful for explicit durability control when using SyncMode::None or + * SyncMode::Interval. Thread-safe. + */ + void syncWal(); + /** * @brief Check if a flush operation is in progress * @return true if flushing, false otherwise @@ -596,6 +626,12 @@ class TidesDB */ [[nodiscard]] Transaction beginTransaction(IsolationLevel isolation); + /** + * @brief Get database-level statistics + * @return Aggregate statistics across the entire database instance + */ + [[nodiscard]] DbStats getDbStats(); + /** * @brief Get block cache statistics * @return Cache statistics diff --git a/src/tidesdb.cpp b/src/tidesdb.cpp index 52be007..93959e3 100644 --- a/src/tidesdb.cpp +++ b/src/tidesdb.cpp @@ -271,6 +271,12 @@ void ColumnFamily::purge() checkResult(result, "failed to purge column family"); } +void ColumnFamily::syncWal() +{ + int result = tidesdb_sync_wal(cf_); + checkResult(result, "failed to sync WAL"); +} + bool ColumnFamily::isFlushing() const { return tidesdb_is_flushing(cf_) != 0; @@ -738,6 +744,32 @@ Transaction TidesDB::beginTransaction(IsolationLevel isolation) return Transaction(txn); } +DbStats TidesDB::getDbStats() +{ + tidesdb_db_stats_t cStats; + int result = tidesdb_get_db_stats(db_, &cStats); + checkResult(result, "failed to get database stats"); + + DbStats stats; + stats.numColumnFamilies = cStats.num_column_families; + stats.totalMemory = cStats.total_memory; + stats.availableMemory = cStats.available_memory; + stats.resolvedMemoryLimit = cStats.resolved_memory_limit; + stats.memoryPressureLevel = cStats.memory_pressure_level; + stats.flushPendingCount = cStats.flush_pending_count; + stats.totalMemtableBytes = cStats.total_memtable_bytes; + stats.totalImmutableCount = cStats.total_immutable_count; + stats.totalSstableCount = cStats.total_sstable_count; + stats.totalDataSizeBytes = cStats.total_data_size_bytes; + stats.numOpenSstables = cStats.num_open_sstables; + stats.globalSeq = cStats.global_seq; + stats.txnMemoryBytes = cStats.txn_memory_bytes; + stats.compactionQueueSize = cStats.compaction_queue_size; + stats.flushQueueSize = cStats.flush_queue_size; + + return stats; +} + CacheStats TidesDB::getCacheStats() { tidesdb_cache_stats_t cStats; diff --git a/tests/tidesdb_test.cpp b/tests/tidesdb_test.cpp index e0a603d..33d98c5 100644 --- a/tests/tidesdb_test.cpp +++ b/tests/tidesdb_test.cpp @@ -1188,6 +1188,100 @@ TEST_F(TidesDBTest, TransactionResetAfterRollback) } } +TEST_F(TidesDBTest, SyncWal) +{ + tidesdb::TidesDB db(getConfig()); + + auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig(); + cfConfig.syncMode = tidesdb::SyncMode::None; + db.createColumnFamily("test_cf", cfConfig); + + auto cf = db.getColumnFamily("test_cf"); + + // Write some data + { + auto txn = db.beginTransaction(); + for (int i = 0; i < 10; ++i) + { + std::string key = "key" + std::to_string(i); + std::string value = "value" + std::to_string(i); + txn.put(cf, key, value, -1); + } + txn.commit(); + } + + // Force WAL sync -- should not throw + ASSERT_NO_THROW(cf.syncWal()); +} + +TEST_F(TidesDBTest, SyncWalWithIntervalMode) +{ + tidesdb::TidesDB db(getConfig()); + + auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig(); + cfConfig.syncMode = tidesdb::SyncMode::Interval; + cfConfig.syncIntervalUs = 1000000; // 1 second + db.createColumnFamily("test_cf", cfConfig); + + auto cf = db.getColumnFamily("test_cf"); + + // Write data + { + auto txn = db.beginTransaction(); + txn.put(cf, "key1", "value1", -1); + txn.commit(); + } + + // Explicit sync before the interval fires + ASSERT_NO_THROW(cf.syncWal()); +} + +TEST_F(TidesDBTest, GetDbStats) +{ + tidesdb::TidesDB db(getConfig()); + + auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig(); + db.createColumnFamily("cf1", cfConfig); + db.createColumnFamily("cf2", cfConfig); + + auto cf1 = db.getColumnFamily("cf1"); + auto cf2 = db.getColumnFamily("cf2"); + + // Write some data + { + auto txn = db.beginTransaction(); + for (int i = 0; i < 20; ++i) + { + txn.put(cf1, "k1_" + std::to_string(i), "v1_" + std::to_string(i), -1); + txn.put(cf2, "k2_" + std::to_string(i), "v2_" + std::to_string(i), -1); + } + txn.commit(); + } + + auto dbStats = db.getDbStats(); + + ASSERT_EQ(dbStats.numColumnFamilies, 2); + ASSERT_GT(dbStats.totalMemory, 0u); + ASSERT_GE(dbStats.resolvedMemoryLimit, 0u); + ASSERT_GE(dbStats.memoryPressureLevel, 0); + ASSERT_GE(dbStats.globalSeq, 0u); + ASSERT_GE(dbStats.flushQueueSize, 0u); + ASSERT_GE(dbStats.compactionQueueSize, 0u); +} + +TEST_F(TidesDBTest, GetDbStatsEmpty) +{ + tidesdb::TidesDB db(getConfig()); + + auto cfConfig = tidesdb::ColumnFamilyConfig::defaultConfig(); + db.createColumnFamily("empty_cf", cfConfig); + + auto dbStats = db.getDbStats(); + + ASSERT_EQ(dbStats.numColumnFamilies, 1); + ASSERT_GT(dbStats.totalMemory, 0u); +} + // Commit hook test helpers struct HookTestCtx {