Skip to content

Commit c831cfe

Browse files
committed
align with tdb 920 with tombstone density capability, and range compaction
1 parent 7abf577 commit c831cfe

3 files changed

Lines changed: 225 additions & 17 deletions

File tree

src/tidesdb.lua

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ ffi.cdef[[
8080
uint64_t min_disk_space;
8181
int l1_file_count_trigger;
8282
int l0_queue_stall_threshold;
83+
double tombstone_density_trigger;
84+
uint64_t tombstone_density_min_entries;
8385
int use_btree;
8486
tidesdb_commit_hook_fn commit_hook_fn;
8587
void *commit_hook_ctx;
@@ -126,6 +128,7 @@ ffi.cdef[[
126128
uint64_t unified_memtable_sync_interval_us;
127129
void* object_store;
128130
tidesdb_objstore_config_t* object_store_config;
131+
int max_concurrent_flushes;
129132
} tidesdb_config_t;
130133

131134
typedef struct {
@@ -145,6 +148,11 @@ ffi.cdef[[
145148
uint64_t btree_total_nodes;
146149
uint32_t btree_max_height;
147150
double btree_avg_height;
151+
uint64_t total_tombstones;
152+
double tombstone_ratio;
153+
uint64_t* level_tombstone_counts;
154+
double max_sst_density;
155+
int max_sst_density_level;
148156
} tidesdb_stats_t;
149157

150158
typedef struct {
@@ -206,6 +214,8 @@ ffi.cdef[[
206214

207215
// Column family operations
208216
int tidesdb_compact(void* cf);
217+
int tidesdb_compact_range(void* cf, const uint8_t* start_key, size_t start_key_size,
218+
const uint8_t* end_key, size_t end_key_size);
209219
int tidesdb_flush_memtable(void* cf);
210220
int tidesdb_is_flushing(void* cf);
211221
int tidesdb_is_compacting(void* cf);
@@ -442,22 +452,24 @@ end
442452

443453
-- Default configurations
444454
function tidesdb.default_config()
455+
local c_config = lib.tidesdb_default_config()
445456
return {
446457
db_path = "",
447-
num_flush_threads = 2,
448-
num_compaction_threads = 2,
449-
log_level = tidesdb.LogLevel.LOG_INFO,
450-
block_cache_size = 64 * 1024 * 1024,
451-
max_open_sstables = 256,
452-
log_to_file = false,
453-
log_truncation_at = 24 * 1024 * 1024,
454-
max_memory_usage = 0,
455-
unified_memtable = false,
456-
unified_memtable_write_buffer_size = 64 * 1024 * 1024,
457-
unified_memtable_skip_list_max_level = 12,
458-
unified_memtable_skip_list_probability = 0.25,
459-
unified_memtable_sync_mode = tidesdb.SyncMode.SYNC_INTERVAL,
460-
unified_memtable_sync_interval_us = 128000,
458+
num_flush_threads = c_config.num_flush_threads,
459+
num_compaction_threads = c_config.num_compaction_threads,
460+
log_level = c_config.log_level,
461+
block_cache_size = tonumber(c_config.block_cache_size),
462+
max_open_sstables = tonumber(c_config.max_open_sstables),
463+
log_to_file = c_config.log_to_file ~= 0,
464+
log_truncation_at = tonumber(c_config.log_truncation_at),
465+
max_memory_usage = tonumber(c_config.max_memory_usage),
466+
unified_memtable = c_config.unified_memtable ~= 0,
467+
unified_memtable_write_buffer_size = tonumber(c_config.unified_memtable_write_buffer_size),
468+
unified_memtable_skip_list_max_level = c_config.unified_memtable_skip_list_max_level,
469+
unified_memtable_skip_list_probability = c_config.unified_memtable_skip_list_probability,
470+
unified_memtable_sync_mode = c_config.unified_memtable_sync_mode,
471+
unified_memtable_sync_interval_us = tonumber(c_config.unified_memtable_sync_interval_us),
472+
max_concurrent_flushes = c_config.max_concurrent_flushes,
461473
}
462474
end
463475

@@ -484,6 +496,8 @@ function tidesdb.default_column_family_config()
484496
min_disk_space = tonumber(c_config.min_disk_space),
485497
l1_file_count_trigger = c_config.l1_file_count_trigger,
486498
l0_queue_stall_threshold = c_config.l0_queue_stall_threshold,
499+
tombstone_density_trigger = c_config.tombstone_density_trigger,
500+
tombstone_density_min_entries = tonumber(c_config.tombstone_density_min_entries),
487501
use_btree = c_config.use_btree ~= 0,
488502
object_lazy_compaction = c_config.object_lazy_compaction ~= 0,
489503
object_prefetch_compaction = c_config.object_prefetch_compaction ~= 0,
@@ -573,6 +587,8 @@ local function config_to_c_struct(config, cf_name)
573587
c_config.min_disk_space = config.min_disk_space or 100 * 1024 * 1024
574588
c_config.l1_file_count_trigger = config.l1_file_count_trigger or 4
575589
c_config.l0_queue_stall_threshold = config.l0_queue_stall_threshold or 20
590+
c_config.tombstone_density_trigger = config.tombstone_density_trigger or 0.0
591+
c_config.tombstone_density_min_entries = config.tombstone_density_min_entries or 1024
576592
c_config.use_btree = config.use_btree and 1 or 0
577593
c_config.object_lazy_compaction = config.object_lazy_compaction and 1 or 0
578594
c_config.object_prefetch_compaction = config.object_prefetch_compaction and 1 or 0
@@ -715,6 +731,21 @@ function ColumnFamily:compact()
715731
check_result(result, "failed to compact column family")
716732
end
717733

734+
function ColumnFamily:compact_range(start_key, end_key)
735+
local start_ptr, start_len = nil, 0
736+
if start_key ~= nil and #start_key > 0 then
737+
start_ptr = start_key
738+
start_len = #start_key
739+
end
740+
local end_ptr, end_len = nil, 0
741+
if end_key ~= nil and #end_key > 0 then
742+
end_ptr = end_key
743+
end_len = #end_key
744+
end
745+
local result = lib.tidesdb_compact_range(self._cf, start_ptr, start_len, end_ptr, end_len)
746+
check_result(result, "failed to compact range")
747+
end
748+
718749
function ColumnFamily:flush_memtable()
719750
local result = lib.tidesdb_flush_memtable(self._cf)
720751
check_result(result, "failed to flush memtable")
@@ -810,6 +841,8 @@ function ColumnFamily:get_stats()
810841
min_disk_space = tonumber(c_cfg.min_disk_space),
811842
l1_file_count_trigger = c_cfg.l1_file_count_trigger,
812843
l0_queue_stall_threshold = c_cfg.l0_queue_stall_threshold,
844+
tombstone_density_trigger = c_cfg.tombstone_density_trigger,
845+
tombstone_density_min_entries = tonumber(c_cfg.tombstone_density_min_entries),
813846
use_btree = c_cfg.use_btree ~= 0,
814847
}
815848
end
@@ -821,6 +854,13 @@ function ColumnFamily:get_stats()
821854
end
822855
end
823856

857+
local level_tombstone_counts = {}
858+
if c_stats.num_levels > 0 and c_stats.level_tombstone_counts ~= nil then
859+
for i = 0, c_stats.num_levels - 1 do
860+
table.insert(level_tombstone_counts, tonumber(c_stats.level_tombstone_counts[i]))
861+
end
862+
end
863+
824864
local stats = {
825865
num_levels = c_stats.num_levels,
826866
memtable_size = tonumber(c_stats.memtable_size),
@@ -838,6 +878,11 @@ function ColumnFamily:get_stats()
838878
btree_total_nodes = tonumber(c_stats.btree_total_nodes),
839879
btree_max_height = c_stats.btree_max_height,
840880
btree_avg_height = c_stats.btree_avg_height,
881+
total_tombstones = tonumber(c_stats.total_tombstones),
882+
tombstone_ratio = c_stats.tombstone_ratio,
883+
level_tombstone_counts = level_tombstone_counts,
884+
max_sst_density = c_stats.max_sst_density,
885+
max_sst_density_level = c_stats.max_sst_density_level,
841886
}
842887

843888
lib.tidesdb_free_stats(stats_ptr[0])
@@ -1045,6 +1090,7 @@ function TidesDB.new(config)
10451090
c_config.unified_memtable_skip_list_probability = config.unified_memtable_skip_list_probability or 0.25
10461091
c_config.unified_memtable_sync_mode = config.unified_memtable_sync_mode or tidesdb.SyncMode.SYNC_INTERVAL
10471092
c_config.unified_memtable_sync_interval_us = config.unified_memtable_sync_interval_us or 128000
1093+
c_config.max_concurrent_flushes = config.max_concurrent_flushes or 0
10481094

10491095
-- Object store configuration
10501096
if config.object_store then
@@ -1083,6 +1129,7 @@ function TidesDB.open(path, options)
10831129
unified_memtable_skip_list_probability = options.unified_memtable_skip_list_probability,
10841130
unified_memtable_sync_mode = options.unified_memtable_sync_mode,
10851131
unified_memtable_sync_interval_us = options.unified_memtable_sync_interval_us,
1132+
max_concurrent_flushes = options.max_concurrent_flushes,
10861133
object_store = options.object_store,
10871134
object_store_config = options.object_store_config,
10881135
}
@@ -1365,6 +1412,8 @@ function tidesdb.load_config_from_ini(ini_file, section_name)
13651412
min_disk_space = tonumber(c_config.min_disk_space),
13661413
l1_file_count_trigger = c_config.l1_file_count_trigger,
13671414
l0_queue_stall_threshold = c_config.l0_queue_stall_threshold,
1415+
tombstone_density_trigger = c_config.tombstone_density_trigger,
1416+
tombstone_density_min_entries = tonumber(c_config.tombstone_density_min_entries),
13681417
use_btree = c_config.use_btree ~= 0,
13691418
object_lazy_compaction = c_config.object_lazy_compaction ~= 0,
13701419
object_prefetch_compaction = c_config.object_prefetch_compaction ~= 0,
@@ -1378,6 +1427,6 @@ function tidesdb.save_config_to_ini(ini_file, section_name, config)
13781427
end
13791428

13801429
-- Version
1381-
tidesdb._VERSION = "0.6.0"
1430+
tidesdb._VERSION = "0.7.0"
13821431

13831432
return tidesdb

tests/test_tidesdb.lua

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,6 +1390,165 @@ function tests.test_txn_single_delete()
13901390
print("PASS: test_txn_single_delete")
13911391
end
13921392

1393+
function tests.test_tombstone_cf_config_roundtrip()
1394+
local path = "./test_db_tombstone_cfg"
1395+
cleanup_db(path)
1396+
1397+
-- Defaults from C should be sensible
1398+
local defaults = tidesdb.default_column_family_config()
1399+
assert_true(defaults.tombstone_density_trigger ~= nil, "tombstone_density_trigger should exist in defaults")
1400+
assert_true(defaults.tombstone_density_min_entries ~= nil, "tombstone_density_min_entries should exist in defaults")
1401+
assert_true(defaults.tombstone_density_min_entries >= 1, "tombstone_density_min_entries default should be >= 1")
1402+
1403+
local db = tidesdb.TidesDB.open(path, { log_level = tidesdb.LogLevel.LOG_WARN })
1404+
local cf_config = tidesdb.default_column_family_config()
1405+
cf_config.tombstone_density_trigger = 0.5
1406+
cf_config.tombstone_density_min_entries = 256
1407+
db:create_column_family("ts_cf", cf_config)
1408+
1409+
local cf = db:get_column_family("ts_cf")
1410+
local stats = cf:get_stats()
1411+
assert_true(stats.config ~= nil, "stats.config should exist")
1412+
assert_eq(stats.config.tombstone_density_trigger, 0.5, "tombstone_density_trigger round-trip")
1413+
assert_eq(stats.config.tombstone_density_min_entries, 256, "tombstone_density_min_entries round-trip")
1414+
1415+
db:drop_column_family("ts_cf")
1416+
db:close()
1417+
cleanup_db(path)
1418+
print("PASS: test_tombstone_cf_config_roundtrip")
1419+
end
1420+
1421+
function tests.test_tombstone_stats_after_deletes()
1422+
local path = "./test_db_tombstone_stats"
1423+
cleanup_db(path)
1424+
1425+
local db = tidesdb.TidesDB.open(path, { log_level = tidesdb.LogLevel.LOG_WARN })
1426+
db:create_column_family("ts_cf")
1427+
local cf = db:get_column_family("ts_cf")
1428+
1429+
-- Insert 100 keys, flush, delete half, flush
1430+
local n = 100
1431+
local insert_txn = db:begin_txn()
1432+
for i = 1, n do
1433+
insert_txn:put(cf, string.format("key:%04d", i), string.format("value:%04d", i))
1434+
end
1435+
insert_txn:commit()
1436+
insert_txn:free()
1437+
cf:flush_memtable()
1438+
1439+
local del_txn = db:begin_txn()
1440+
for i = 1, n / 2 do
1441+
del_txn:delete(cf, string.format("key:%04d", i))
1442+
end
1443+
del_txn:commit()
1444+
del_txn:free()
1445+
cf:flush_memtable()
1446+
1447+
-- Brief wait for flush to land
1448+
local deadline = os.time() + 5
1449+
while cf:is_flushing() and os.time() < deadline do
1450+
os.execute("sleep 0.1")
1451+
end
1452+
1453+
local stats = cf:get_stats()
1454+
assert_true(stats.total_tombstones ~= nil, "total_tombstones should exist")
1455+
assert_true(stats.total_tombstones > 0, "total_tombstones should be > 0 after deletes")
1456+
assert_true(stats.tombstone_ratio ~= nil, "tombstone_ratio should exist")
1457+
assert_true(stats.tombstone_ratio >= 0 and stats.tombstone_ratio <= 1, "tombstone_ratio in [0, 1]")
1458+
assert_true(stats.max_sst_density ~= nil, "max_sst_density should exist")
1459+
assert_true(stats.max_sst_density >= 0 and stats.max_sst_density <= 1, "max_sst_density in [0, 1]")
1460+
assert_true(stats.max_sst_density_level ~= nil, "max_sst_density_level should exist")
1461+
assert_true(stats.level_tombstone_counts ~= nil, "level_tombstone_counts should exist")
1462+
assert_eq(#stats.level_tombstone_counts, stats.num_levels, "level_tombstone_counts length should match num_levels")
1463+
1464+
db:drop_column_family("ts_cf")
1465+
db:close()
1466+
cleanup_db(path)
1467+
print("PASS: test_tombstone_stats_after_deletes")
1468+
end
1469+
1470+
function tests.test_compact_range()
1471+
local path = "./test_db_compact_range"
1472+
cleanup_db(path)
1473+
1474+
local db = tidesdb.TidesDB.open(path, { log_level = tidesdb.LogLevel.LOG_WARN })
1475+
db:create_column_family("cr_cf")
1476+
local cf = db:get_column_family("cr_cf")
1477+
1478+
-- Insert several batches and flush each to create multiple SSTables
1479+
for batch = 1, 4 do
1480+
local txn = db:begin_txn()
1481+
for i = 1, 50 do
1482+
local k = string.format("key:%02d:%04d", batch, i)
1483+
txn:put(cf, k, string.format("v:%d", i))
1484+
end
1485+
txn:commit()
1486+
txn:free()
1487+
cf:flush_memtable()
1488+
end
1489+
1490+
-- Wait briefly for flushes to settle
1491+
local deadline = os.time() + 5
1492+
while cf:is_flushing() and os.time() < deadline do
1493+
os.execute("sleep 0.1")
1494+
end
1495+
1496+
-- Narrow range compaction succeeds
1497+
cf:compact_range("key:01:0001", "key:02:0050")
1498+
1499+
-- A key outside the range should still be readable and unchanged
1500+
local read_txn = db:begin_txn()
1501+
local v = read_txn:get(cf, "key:04:0010")
1502+
assert_eq(v, "v:10", "key outside compacted range should be unchanged")
1503+
read_txn:free()
1504+
1505+
-- Both endpoints empty/nil should be rejected with INVALID_ARGS
1506+
local err = assert_error(function()
1507+
cf:compact_range(nil, nil)
1508+
end, "both nil endpoints should fail")
1509+
err = assert_error(function()
1510+
cf:compact_range("", "")
1511+
end, "both empty endpoints should fail")
1512+
1513+
-- Unbounded one side should be accepted
1514+
cf:compact_range(nil, "key:01:0050")
1515+
cf:compact_range("key:04:0001", nil)
1516+
1517+
db:drop_column_family("cr_cf")
1518+
db:close()
1519+
cleanup_db(path)
1520+
print("PASS: test_compact_range")
1521+
end
1522+
1523+
function tests.test_max_concurrent_flushes()
1524+
local path = "./test_db_max_flushes"
1525+
cleanup_db(path)
1526+
1527+
-- default_config() should source from C, so max_concurrent_flushes should be non-zero
1528+
local defaults = tidesdb.default_config()
1529+
assert_true(defaults.max_concurrent_flushes ~= nil, "max_concurrent_flushes should exist in default_config")
1530+
assert_true(defaults.max_concurrent_flushes > 0, "default max_concurrent_flushes should be > 0")
1531+
1532+
-- Open with MaxConcurrentFlushes = 1; basic put + flush should work
1533+
local db = tidesdb.TidesDB.open(path, {
1534+
log_level = tidesdb.LogLevel.LOG_WARN,
1535+
max_concurrent_flushes = 1,
1536+
})
1537+
db:create_column_family("mcf_cf")
1538+
local cf = db:get_column_family("mcf_cf")
1539+
1540+
local txn = db:begin_txn()
1541+
txn:put(cf, "k", "v")
1542+
txn:commit()
1543+
txn:free()
1544+
cf:flush_memtable()
1545+
1546+
db:drop_column_family("mcf_cf")
1547+
db:close()
1548+
cleanup_db(path)
1549+
print("PASS: test_max_concurrent_flushes")
1550+
end
1551+
13931552
-- Run all tests
13941553
local function run_tests()
13951554
print("Running TidesDB Lua tests...")
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package = "tidesdb"
2-
version = "0.6.0-1"
2+
version = "0.7.0-1"
33
source = {
44
url = "git://github.com/tidesdb/tidesdb-lua.git",
5-
tag = "v0.6.0"
5+
tag = "v0.7.0"
66
}
77
description = {
88
summary = "Official Lua bindings for TidesDB - A high-performance embedded key-value storage engine",

0 commit comments

Comments
 (0)