Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,11 @@ $(BINDIR)/$(UNITDIR)/splinterdb_stress_test: $(COMMON_TESTOBJ)
$(OBJDIR)/$(FUNCTIONAL_TESTSDIR)/test_async.o \
$(LIBDIR)/libsplinterdb.so

$(BINDIR)/$(UNITDIR)/splinterdb_optimize_test: $(COMMON_TESTOBJ) \
$(COMMON_UNIT_TESTOBJ) \
$(OBJDIR)/$(FUNCTIONAL_TESTSDIR)/test_async.o \
$(LIBDIR)/libsplinterdb.so

$(BINDIR)/$(UNITDIR)/writable_buffer_test: $(COMMON_TESTOBJ) \
$(COMMON_UNIT_TESTOBJ) \
$(OBJDIR)/$(FUNCTIONAL_TESTSDIR)/test_async.o \
Expand Down Expand Up @@ -541,6 +546,7 @@ unit/btree_stress_test: $(BINDIR)/$(UNITDIR)/btree_stress_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
unit/splinterdb_optimize_test: $(BINDIR)/$(UNITDIR)/splinterdb_optimize_test
unit/writable_buffer_test: $(BINDIR)/$(UNITDIR)/writable_buffer_test
unit/config_parse_test: $(BINDIR)/$(UNITDIR)/config_parse_test
unit/limitations_test: $(BINDIR)/$(UNITDIR)/limitations_test
Expand All @@ -565,6 +571,9 @@ $(BINDIR)/$(EXAMPLES_DIR)/splinterdb_wide_values_example: $(OBJDIR)/$(EXAMPLES_D
$(BINDIR)/$(EXAMPLES_DIR)/splinterdb_iterators_example: $(OBJDIR)/$(EXAMPLES_DIR)/splinterdb_iterators_example.o \
$(LIBDIR)/libsplinterdb.so

$(BINDIR)/$(EXAMPLES_DIR)/splinterdb_optimize_example: $(OBJDIR)/$(EXAMPLES_DIR)/splinterdb_optimize_example.o \
$(LIBDIR)/libsplinterdb.so

$(BINDIR)/$(EXAMPLES_DIR)/splinterdb_custom_ipv4_addr_sortcmp_example: $(OBJDIR)/$(EXAMPLES_DIR)/splinterdb_custom_ipv4_addr_sortcmp_example.o \
$(LIBDIR)/libsplinterdb.so

Expand Down
223 changes: 223 additions & 0 deletions examples/splinterdb_optimize_example.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright 2026 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

/*
* Open an existing SplinterDB disk image and optimize a key range.
*
* This is intended as a small utility for preparing a database image before
* point/range-query benchmarking.
*/

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "splinterdb/default_data_config.h"
#include "splinterdb/splinterdb.h"

#define DEFAULT_CACHE_SIZE_MIB 1024
#define DEFAULT_NORMAL_BG_THREADS 2
#define DEFAULT_MEMTABLE_BG_THREADS 1
#define BYTES_PER_MIB (1024ULL * 1024ULL)
#define OPTIMIZE_HELP_REQUESTED (-1)

typedef struct optimize_options {
const char *filename;
const char *min_key;
const char *max_key;
uint64 cache_size;
uint64 disk_size;
uint64 num_normal_bg_threads;
uint64 num_memtable_bg_threads;
_Bool use_direct_io;
_Bool full_leaf_compactions;
} optimize_options;

static void
usage(const char *progname);

static int
parse_uint64(const char *arg, uint64 *result);

static int
parse_options(int argc, char **argv, optimize_options *opts);

int
main(int argc, char **argv)
{
optimize_options opts = {
.cache_size = DEFAULT_CACHE_SIZE_MIB * BYTES_PER_MIB,
.num_normal_bg_threads = DEFAULT_NORMAL_BG_THREADS,
.num_memtable_bg_threads = DEFAULT_MEMTABLE_BG_THREADS,
.full_leaf_compactions = TRUE,
};

int rc = parse_options(argc, argv, &opts);
if (rc == OPTIMIZE_HELP_REQUESTED) {
return 0;
}
if (rc != 0) {
return rc;
}

data_config data_cfg;
default_data_config_init(&data_cfg);

splinterdb_config splinterdb_cfg;
memset(&splinterdb_cfg, 0, sizeof(splinterdb_cfg));
splinterdb_cfg.filename = opts.filename;
splinterdb_cfg.cache_size = opts.cache_size;
splinterdb_cfg.disk_size = opts.disk_size;
splinterdb_cfg.data_cfg = &data_cfg;
splinterdb_cfg.num_normal_bg_threads = opts.num_normal_bg_threads;
splinterdb_cfg.num_memtable_bg_threads = opts.num_memtable_bg_threads;
if (opts.use_direct_io) {
splinterdb_cfg.io_flags = O_RDWR | O_DIRECT;
}

splinterdb *spl = NULL;
rc = splinterdb_open(&splinterdb_cfg, &spl);
if (rc != 0) {
fprintf(stderr, "splinterdb_open(%s) failed: %d\n", opts.filename, rc);
return rc;
}

slice min_key = opts.min_key == NULL
? NULL_SLICE
: slice_create(strlen(opts.min_key), opts.min_key);
slice max_key = opts.max_key == NULL
? NULL_SLICE
: slice_create(strlen(opts.max_key), opts.max_key);

splinterdb_notification notification;
splinterdb_notification_init_blocking(&notification);

printf("Optimizing %s in [%s, %s)...\n",
opts.filename,
opts.min_key == NULL ? "-infinity" : opts.min_key,
opts.max_key == NULL ? "+infinity" : opts.max_key);

rc = splinterdb_optimize(
spl, min_key, max_key, opts.full_leaf_compactions, &notification);
splinterdb_notification_deinit(&notification);

if (rc == 0) {
printf("Optimize completed successfully.\n");
} else {
fprintf(stderr, "splinterdb_optimize failed: %d\n", rc);
}

splinterdb_close(&spl);
return rc;
}

static void
usage(const char *progname)
{
fprintf(stderr,
"Usage: %s [options] <splinterdb-image>\n"
"\n"
"Options:\n"
" --min-key <key> Inclusive lower bound\n"
" --max-key <key> Exclusive upper bound\n"
" --cache-mib <mib> Cache size in MiB (default %u)\n"
" --disk-size-mib <mib> Check image size in MiB\n"
" --set-O_DIRECT Open the image with O_DIRECT\n"
" --no-full-leaf-compactions Skip full leaf compactions\n"
" --normal-bg-threads <count> Normal task threads (default %u)\n"
" --memtable-bg-threads <cnt> Memtable task threads (default %u)\n"
" --help Show this help\n",
progname,
DEFAULT_CACHE_SIZE_MIB,
DEFAULT_NORMAL_BG_THREADS,
DEFAULT_MEMTABLE_BG_THREADS);
}

static int
parse_uint64(const char *arg, uint64 *result)
{
char *end = NULL;

errno = 0;
*result = strtoull(arg, &end, 10);
if (errno != 0 || end == arg || *end != '\0') {
return EINVAL;
}

return 0;
}

static int
parse_options(int argc, char **argv, optimize_options *opts)
{
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0) {
usage(argv[0]);
return OPTIMIZE_HELP_REQUESTED;
} else if (strcmp(argv[i], "--min-key") == 0) {
if (++i == argc) {
usage(argv[0]);
return EINVAL;
}
opts->min_key = argv[i];
} else if (strcmp(argv[i], "--max-key") == 0) {
if (++i == argc) {
usage(argv[0]);
return EINVAL;
}
opts->max_key = argv[i];
} else if (strcmp(argv[i], "--cache-mib") == 0) {
uint64 mib;
if (++i == argc || parse_uint64(argv[i], &mib) != 0) {
usage(argv[0]);
return EINVAL;
}
opts->cache_size = mib * BYTES_PER_MIB;
} else if (strcmp(argv[i], "--disk-size-mib") == 0) {
uint64 mib;
if (++i == argc || parse_uint64(argv[i], &mib) != 0) {
usage(argv[0]);
return EINVAL;
}
opts->disk_size = mib * BYTES_PER_MIB;
} else if (strcmp(argv[i], "--set-O_DIRECT") == 0) {
opts->use_direct_io = TRUE;
} else if (strcmp(argv[i], "--no-full-leaf-compactions") == 0) {
opts->full_leaf_compactions = FALSE;
} else if (strcmp(argv[i], "--normal-bg-threads") == 0) {
if (++i == argc
|| parse_uint64(argv[i], &opts->num_normal_bg_threads) != 0)
{
usage(argv[0]);
return EINVAL;
}
} else if (strcmp(argv[i], "--memtable-bg-threads") == 0) {
if (++i == argc
|| parse_uint64(argv[i], &opts->num_memtable_bg_threads) != 0)
{
usage(argv[0]);
return EINVAL;
}
} else if (opts->filename == NULL) {
opts->filename = argv[i];
} else {
usage(argv[0]);
return EINVAL;
}
}

if (opts->filename == NULL) {
usage(argv[0]);
return EINVAL;
}
if (opts->num_normal_bg_threads == 0) {
fprintf(stderr,
"--normal-bg-threads must be greater than 0 for blocking "
"optimize completion.\n");
return EINVAL;
}

return 0;
}
69 changes: 67 additions & 2 deletions include/splinterdb/splinterdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ typedef struct splinterdb_config {
// required configuration
const char *filename;
uint64 cache_size;
uint64 disk_size;

// Required for splinterdb_create(). For splinterdb_open(), zero means read
// the value from the on-disk superblock; nonzero values are checked against
// the superblock.
uint64 disk_size;

// data_config is a required field that defines how your data should be
// read and written in SplinterDB. See data.h for details.
Expand All @@ -58,6 +62,8 @@ typedef struct splinterdb_config {
uint64 shmem_size;
_Bool use_shmem; // Default is FALSE.

// For splinterdb_open(), zero means read the value from the on-disk
// superblock; nonzero values are checked against the superblock.
uint64 page_size;
uint64 extent_size;

Expand Down Expand Up @@ -160,7 +166,10 @@ typedef struct splinterdb splinterdb;
int
splinterdb_create(const splinterdb_config *cfg, splinterdb **kvs);

// Open an existing splinterdb from a file/device on disk
// Open an existing splinterdb from a file/device on disk.
//
// disk_size, page_size, and extent_size are read from the on-disk superblock
// when left unset. If they are set, they must match the superblock.
//
// The library will allocate and own the memory for splinterdb
// and will free it on splinterdb_close().
Expand All @@ -178,6 +187,49 @@ void
splinterdb_close(splinterdb **kvs);


////////////////////////////////////
// Notifications
////////////////////////////////////

// Size of opaque data required to hold a notification.
#define SPLINTERDB_NOTIFICATION_BUFSIZE (32 * sizeof(void *))

typedef struct splinterdb_notification {
char opaque[SPLINTERDB_NOTIFICATION_BUFSIZE];
} __attribute__((__aligned__(8))) splinterdb_notification;

typedef void (*splinterdb_notification_callback)(
splinterdb_notification *notification);

// The caller owns notification storage and must keep it alive until completion.
// After completion, call splinterdb_notification_deinit before reusing or
// destroying the notification.
void
splinterdb_notification_init_blocking(splinterdb_notification *notification);

void
splinterdb_notification_init_polling(splinterdb_notification *notification,
void *user_data);

void
splinterdb_notification_init_callback(splinterdb_notification *notification,
splinterdb_notification_callback callback,
void *user_data);

void
splinterdb_notification_deinit(splinterdb_notification *notification);

_Bool
splinterdb_notification_poll(const splinterdb_notification *notification,
int *status);

int
splinterdb_notification_wait(splinterdb_notification *notification);

void *
splinterdb_notification_user_data(const splinterdb_notification *notification);


////////////////////////////////////
// Lookups
////////////////////////////////////
Expand Down Expand Up @@ -290,6 +342,19 @@ splinterdb_update(splinterdb *kvsb,
slice delta,
splinterdb_lookup_result *old_result);

// Optimize already-incorporated trunk data in [min_key, max_key). A null
// min_key or max_key means the range is unbounded on that side. If
// full_leaf_compactions is true, enqueue full compactions for leaves in the
// range after flushing. Passing a NULL notification makes this fire-and-forget.
// Blocking notifications wait before this function returns; polling and
// callback notifications complete later.
int
splinterdb_optimize(splinterdb *kvs,
slice min_key,
slice max_key,
_Bool full_leaf_compactions,
splinterdb_notification *notification);

/*
Iterator API (range query)

Expand Down
6 changes: 6 additions & 0 deletions src/allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ typedef struct allocator_config {
uint64 extent_mask;
} allocator_config;

typedef struct disk_geometry {
uint64 disk_size;
uint64 page_size;
uint64 extent_size;
} disk_geometry;

/*
*-----------------------------------------------------------------------------
* allocator_config_init
Expand Down
Loading
Loading