Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
94888d3
cache layer of new writeback scheme
rtjohnso Jul 31, 2026
bc9c1c1
Fix writeback races
rtjohnso Jul 31, 2026
d997a56
fix some error handling in writeback
rtjohnso Jul 31, 2026
1d4dd95
definitely die on io error in writeback
rtjohnso Jul 31, 2026
2e6fffa
start to handle writeback errors
rtjohnso Jul 31, 2026
0c54d7c
formatting
rtjohnso Jul 31, 2026
1ddcac3
more writeback error handling
rtjohnso Jul 31, 2026
19face6
formatting
rtjohnso Jul 31, 2026
6807f75
still working on writeback
rtjohnso Aug 1, 2026
a8b7a0b
add async_io_state pool
rtjohnso Aug 1, 2026
00e15e7
formatting
rtjohnso Aug 1, 2026
03bafcf
formatting
rtjohnso Aug 1, 2026
7552e2e
add buffering to the log
rtjohnso Aug 2, 2026
1d9500b
formatting
rtjohnso Aug 2, 2026
b4904dd
log groups
rtjohnso Aug 2, 2026
57c97e0
formatting
rtjohnso Aug 2, 2026
89533e7
fix annoying clang options warning/error on github
rtjohnso Aug 2, 2026
5e326b9
log cleanups
rtjohnso Aug 2, 2026
75d8a77
log bugfixes and playback end detection
rtjohnso Aug 2, 2026
d7db242
handle log sealing errors
rtjohnso Aug 2, 2026
822477d
formatting
rtjohnso Aug 2, 2026
46ce935
clean up unmount
rtjohnso Aug 2, 2026
fc77ea9
formatting
rtjohnso Aug 2, 2026
a978e0c
recovery space allocation work
rtjohnso Aug 3, 2026
6769a7f
complete recovery
rtjohnso Aug 3, 2026
e54edb9
formatting
rtjohnso Aug 3, 2026
20b9f69
minor cleanups
rtjohnso Aug 3, 2026
a417120
cleanup blob and log recovery
rtjohnso Aug 4, 2026
542e44f
cleanup rc_allocator and replay bug in core
rtjohnso Aug 4, 2026
fbd2591
make unmount return error only when there might be data loss
rtjohnso Aug 4, 2026
a3aa492
switch to io_range_is_readable
rtjohnso Aug 4, 2026
64b163b
remove live_head
rtjohnso Aug 4, 2026
8768092
tons of bugfixes
rtjohnso Aug 4, 2026
c18f782
continuing checkpoint refactoring
rtjohnso Aug 5, 2026
ea86fd1
blob cleanup
rtjohnso Aug 5, 2026
1e061ff
core durable
rtjohnso Aug 5, 2026
68a2ed5
group page combining
rtjohnso Aug 5, 2026
fa29090
cleaning up log.h
rtjohnso Aug 5, 2026
0211da7
make log more lock-free
rtjohnso Aug 6, 2026
e2e8d69
shard log cleanup
rtjohnso Aug 6, 2026
9cf5480
shard log cleanup
rtjohnso Aug 7, 2026
86aadd6
shard log cleanup
rtjohnso Aug 7, 2026
10787a2
shard log cleanup
rtjohnso Aug 7, 2026
cb785b2
shard log cleanups
rtjohnso Aug 8, 2026
f846351
shard log simplification
rtjohnso Aug 8, 2026
0e13b35
shard log simplification
rtjohnso Aug 9, 2026
ddc7c1d
get rid of durability status checks
rtjohnso Aug 12, 2026
cff043c
minor cleanups
rtjohnso Aug 12, 2026
89f2bbc
add log grace period to avoid forced rotations
rtjohnso Aug 12, 2026
780c12e
core cleanup
rtjohnso Aug 13, 2026
dfa03cf
formatting
rtjohnso Aug 18, 2026
922bf4f
formatting
rtjohnso Aug 18, 2026
3bb4cbe
add missing files
rtjohnso Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ CFLAGS += -DSPLINTERDB_PLATFORM_DIR=$(PLATFORM_DIR)
GIT_VERSION := "$(shell git describe --abbrev=8 --dirty --always --tags)"
GIT_VERSION_CFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\"

# Fix annoying warning about avx-256 vs avx-512
ifeq "$(findstring clang, $(CC))" "clang"
CFLAGS += -Wno-invalid-feature-combination
endif

cpu_arch := $(shell uname -p)
ifeq ($(cpu_arch),x86_64)
# not supported on ARM64
Expand Down Expand Up @@ -427,11 +432,12 @@ PLATFORM_IO_SYS = $(OBJDIR)/$(SRCDIR)/$(PLATFORM_DIR)/platform_io.o \

UTIL_SYS = $(OBJDIR)/$(SRCDIR)/util.o $(PLATFORM_SYS)

CLOCKCACHE_SYS = $(OBJDIR)/$(SRCDIR)/clockcache.o \
$(OBJDIR)/$(SRCDIR)/allocator.o \
$(OBJDIR)/$(SRCDIR)/rc_allocator.o \
$(OBJDIR)/$(SRCDIR)/task.o \
$(UTIL_SYS) \
CLOCKCACHE_SYS = $(OBJDIR)/$(SRCDIR)/clockcache.o \
$(OBJDIR)/$(SRCDIR)/allocator.o \
$(OBJDIR)/$(SRCDIR)/rc_allocator.o \
$(OBJDIR)/$(SRCDIR)/task.o \
$(OBJDIR)/$(SRCDIR)/writeback_set.o \
$(UTIL_SYS) \
$(PLATFORM_IO_SYS)

BTREE_SYS = $(OBJDIR)/$(SRCDIR)/btree.o \
Expand Down Expand Up @@ -468,6 +474,14 @@ $(BINDIR)/$(UNITDIR)/btree_stress_test: $(OBJDIR)/$(UNIT_TESTSDIR)/btree_test_co
$(COMMON_UNIT_TESTOBJ) \
$(BTREE_SYS)

# Uses btree_test_common only for its init_*_config_from_master_config()
# helpers, which is why it pulls BTREE_SYS rather than just CLOCKCACHE_SYS.
$(BINDIR)/$(UNITDIR)/writeback_set_test: $(OBJDIR)/$(UNIT_TESTSDIR)/btree_test_common.o \
$(OBJDIR)/$(TESTS_DIR)/config.o \
$(OBJDIR)/$(TESTS_DIR)/test_data.o \
$(COMMON_UNIT_TESTOBJ) \
$(BTREE_SYS)

$(BINDIR)/$(UNITDIR)/splinter_test: $(COMMON_TESTOBJ) \
$(COMMON_UNIT_TESTOBJ) \
$(OBJDIR)/$(FUNCTIONAL_TESTSDIR)/test_async.o \
Expand Down Expand Up @@ -548,6 +562,7 @@ unit/misc_test: $(BINDIR)/$(UNITDIR)/misc_test
unit/platform_threads_test: $(BINDIR)/$(UNITDIR)/platform_threads_test
unit/btree_test: $(BINDIR)/$(UNITDIR)/btree_test
unit/btree_stress_test: $(BINDIR)/$(UNITDIR)/btree_stress_test
unit/writeback_set_test: $(BINDIR)/$(UNITDIR)/writeback_set_test
unit/splinter_test: $(BINDIR)/$(UNITDIR)/splinter_test
unit/splinterdb_quick_test: $(BINDIR)/$(UNITDIR)/splinterdb_quick_test
unit/splinterdb_stress_test: $(BINDIR)/$(UNITDIR)/splinterdb_stress_test
Expand Down
1 change: 0 additions & 1 deletion docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ Thus, SplinterDB is provided *as-is* given the following limitations and missing
* SplinterDB on-disk format is not versioned (Data may not survive software upgrades.)
* Single 4KiB page size, with fixed extent size of 32 pages/extent.
* Key and value size need to be less than the page size.
* SplinterDB does not expose an API to force the latest write to be durable (e.g., fsync/commit.)
* SplinterDB disk size cannot be changed once configured.
* SplinterDB does not have a public API for the experimental async features.
* SplinterDB does not retain configuration parameters and metadata. (These cannot
Expand Down
1 change: 0 additions & 1 deletion docs/site/content/docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Thus, SplinterDB is provided *as-is* given the following limitations and missing
between 8 to 105 bytes. Support for smaller key-sizes is experimental.
* The application must specify the minimum and maximum of the key range.
* SplinterDB on-disk size is fixed at compile time.
* SplinterDB does not expose an API to force the latest write to be durable (e.g., fsync/commit.)
* SplinterDB disk size cannot be changed once configured.
* SplinterDB does not have a public API for the experimental async features.
* SplinterDB does not retain configuration parameters and metadata. (These cannot
Expand Down
2 changes: 1 addition & 1 deletion examples/splinterdb_custom_ipv4_addr_sortcmp_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ main()
start_key = "100.101.102.103";
do_iterate_from(spl_handle, start_key);

splinterdb_close(&spl_handle);
splinterdb_close(&spl_handle, FALSE);
printf("Shutdown SplinterDB instance, dbname '%s'.\n\n", DB_FILE_NAME);

return rc;
Expand Down
10 changes: 8 additions & 2 deletions examples/splinterdb_intro_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,13 @@ main()
printf("\n");

printf("Shutdown and reopen SplinterDB instance ...\n");
splinterdb_close(&spl_handle);
rc = splinterdb_close(&spl_handle, FALSE);
if (rc) {
printf("Error shutting down SplinterDB instance, dbname '%s' (rc=%d).\n",
DB_FILE_NAME,
rc);
return rc;
}

rc = splinterdb_open(&splinterdb_cfg, &spl_handle);
if (rc) {
Expand Down Expand Up @@ -129,7 +135,7 @@ main()

printf("Found %d key-value pairs\n\n", i);

splinterdb_close(&spl_handle);
splinterdb_close(&spl_handle, FALSE);
printf("Shutdown SplinterDB instance, dbname '%s'.\n\n", DB_FILE_NAME);

return rc;
Expand Down
2 changes: 1 addition & 1 deletion examples/splinterdb_iterators_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ main()
start_key = "www.twitter.com";
do_iterate_from(spl_handle, start_key);

splinterdb_close(&spl_handle);
splinterdb_close(&spl_handle, FALSE);
printf("Shutdown SplinterDB instance, dbname '%s'.\n\n", DB_FILE_NAME);

return rc;
Expand Down
2 changes: 1 addition & 1 deletion examples/splinterdb_optimize_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ main(int argc, char **argv)
fprintf(stderr, "splinterdb_optimize failed: %d\n", rc);
}

splinterdb_close(&spl);
splinterdb_close(&spl, FALSE);
return rc;
}

Expand Down
2 changes: 1 addition & 1 deletion examples/splinterdb_wide_values_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ main()
}
splinterdb_lookup_result_deinit(&result);

splinterdb_close(&spl_handle);
splinterdb_close(&spl_handle, FALSE);
printf("Shutdown SplinterDB instance, dbname '%s'.\n\n", DB_FILE_NAME);

return rc;
Expand Down
67 changes: 61 additions & 6 deletions include/splinterdb/splinterdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,10 @@ typedef struct splinterdb_config {
_Bool use_log;

// Automatic checkpoints: once the write-ahead log has grown by this many
// bytes, SplinterDB takes a checkpoint, which folds the logged updates into
// the durable tree and reclaims that log's space. This bounds both how much
// log a crash has to replay and how much space the log occupies.
// bytes, SplinterDB arms a checkpoint. The next natural memtable rotation
// cuts the log, after which the checkpoint folds the logged updates into the
// durable tree and reclaims that log's space. This bounds both how much log
// a crash has to replay and how much space the log occupies.
//
// The trigger is sized in log bytes rather than in updates because the two
// are independent: a workload that repeatedly overwrites the same keys grows
Expand All @@ -117,6 +118,13 @@ typedef struct splinterdb_config {
// very large (UINT64_MAX).
uint64 checkpoint_log_size_bytes;

// Once an automatic checkpoint is armed, allow the live log to grow by this
// many additional bytes while waiting for a natural memtable rotation. If
// the memtable has not rotated by then, SplinterDB forces a rotation. Zero
// selects a default of twice the memtable capacity; UINT64_MAX effectively
// disables forced rotation while retaining the soft checkpoint trigger.
uint64 checkpoint_log_grace_bytes;

// splinter
uint64 memtable_capacity;
uint64 fanout;
Expand Down Expand Up @@ -205,9 +213,30 @@ splinterdb_open(const splinterdb_config *cfg, splinterdb **kvs);

// Close a splinterdb
//
// This will flush all data to disk and release all resources
void
splinterdb_close(splinterdb **kvs);
// A completed close makes all acknowledged data recoverable and releases all
// resources.
//
// STATUS_OK means all acknowledged data is recoverable. Recovery may still
// need to replay a durable log or rebuild allocator state; that is not a close
// failure.
//
// Without force, any error means shutdown was refused before destructive
// teardown. The database remains open and *kvs is unchanged, so the caller can
// retry or investigate.
//
// With force, teardown always completes. An error means data preservation
// could not be guaranteed; it does not prove that data was actually lost.
//
// force | return code | database closed? | meaning
// ---------------------------------------------------------------
// FALSE | STATUS_OK | YES | acknowledged data is recoverable
// FALSE | any error | NO | shutdown was refused
// TRUE | STATUS_OK | YES | acknowledged data is recoverable
// TRUE | any error | YES | preservation cannot be guaranteed
//
// After STATUS_OK or any forced close, *kvs is freed and set to NULL.
int
splinterdb_close(splinterdb **kvs, bool32 force);


////////////////////////////////////
Expand Down Expand Up @@ -333,6 +362,9 @@ splinterdb_lookup(splinterdb *kvs, // IN
// Updates
/////////////////////////////////

// A successful update is visible to subsequent operations, but is not
// necessarily durable. Use splinterdb_durable_barrier() to establish crash
// durability for a prefix of updates.

// Insert a key and value. Overwrites any previous value associated with the
// key.
Expand Down Expand Up @@ -372,6 +404,29 @@ splinterdb_optimize(splinterdb *kvs,
_Bool full_leaf_compactions,
splinterdb_notification *notification);

/////////////////////////////////
// Durability
/////////////////////////////////

// Establish a durability barrier.
//
// On success, every update to kvs that linearized before this call began is
// recoverable after a crash or power loss. This includes every successful
// insert, update, or delete that returned before the call began. Updates may
// proceed concurrently; an update overlapping the call may or may not be
// covered.
//
// This establishes durability only. With the write-ahead log enabled, it does
// not promise a checkpoint, log reclamation, a clean cache, or recovery without
// log replay. Without the write-ahead log, SplinterDB obtains the same
// guarantee by checkpointing the tree, which may be substantially more
// expensive.
//
// Returns 0 on success. A nonzero return means the guarantee was not
// established; some or all updates may nevertheless already be durable.
int
splinterdb_durable_barrier(splinterdb *kvs);

/*
Iterator API (range query)

Expand Down
13 changes: 13 additions & 0 deletions src/allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,19 @@ allocator_get_refcount(allocator *al, uint64 addr)
return al->ops->get_ref(al, addr);
}

/*
* Discard whatever the refcount map holds and start a rebuild: afterwards only
* the reserved extents are referenced, and
* allocator_recovery_record_reference() supplies the rest until
* allocator_recovery_finish() declares the map usable.
*
* Repeatable, deliberately. Crash recovery rebuilds twice: once counting the
* logs, so that replay is never handed an extent a record it has not reached
* yet depends on, and again from the durable root alone once replay has
* finished and been folded in. The second rebuild is what frees the logs --
* their extents are simply absent from it -- which is why nothing has to
* enumerate them a second time in order to release them.
*/
static inline platform_status
allocator_recovery_begin(allocator *al)
{
Expand Down
Loading
Loading