Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implent snapshots support #313

Merged
merged 67 commits into from
Apr 9, 2023
Merged
Show file tree
Hide file tree
Changes from 63 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
cdf7e55
Add a configuration example for the snapshots
danielealbano Mar 24, 2023
15c9ab2
Add snapshots config data structures
danielealbano Mar 24, 2023
8f998fd
Add schema parsing
danielealbano Mar 24, 2023
b138e5f
Implement a function to parse time strings
danielealbano Mar 24, 2023
c7370ea
Add the snapshots section to the config file schema
danielealbano Mar 24, 2023
d8e6c8f
Drop function signature declaration from test
danielealbano Mar 24, 2023
b7f8d86
Add the string to numeric value conversion for the snapshots interval…
danielealbano Mar 24, 2023
405399f
Implement the database snapshots config validation
danielealbano Mar 24, 2023
59b736f
Fix data structs for config->database->snapshots
danielealbano Mar 24, 2023
5fd07f8
Expose config database snapshots validation
danielealbano Mar 24, 2023
cd94467
Fix schema for min_keys_changed, no reason to have it as string
danielealbano Mar 24, 2023
2a4a128
Update config tests to validate the snapshots section
danielealbano Mar 24, 2023
b63ed51
Merge branch 'main' into feat-impl-snapshots-support
danielealbano Mar 28, 2023
8e6d17b
Merge branch 'main' into feat-impl-snapshots-support
danielealbano Apr 5, 2023
2e0f378
Add the rdb extension to the default path of the snapshots
danielealbano Apr 5, 2023
3153acd
Merge branch 'main' into feat-impl-snapshots-support
danielealbano Apr 8, 2023
1b105ed
Fix type module_redis_snapshot_value_type_t name
danielealbano Apr 9, 2023
ef0b9ac
Ensure the path is always freed
danielealbano Apr 9, 2023
ddc1b85
Expose a number of new information in the storage db config and the s…
danielealbano Apr 9, 2023
8bdbef7
Fix a bug in how the expired keys were handled
danielealbano Apr 9, 2023
7f6ab28
Expose a new function to prep for read the entry indexes without taki…
danielealbano Apr 9, 2023
41e44a1
Clarify the expiration cleanup logic
danielealbano Apr 9, 2023
1cbb4ef
Headers cleanup
danielealbano Apr 9, 2023
dfee360
Expose a new internal api call to open storage channels from file des…
danielealbano Apr 9, 2023
4ef06f9
Initial implementation of the storage_rdb_snapshot
danielealbano Apr 9, 2023
d1477f1
Initial implementation of the worker fiber to run the snapshots
danielealbano Apr 9, 2023
9955af2
Set the timestamp for next time to run the snapshot once the storage …
danielealbano Apr 9, 2023
d2e77b3
Move the logic to ensure that the snapshot parent folder exists and i…
danielealbano Apr 9, 2023
fda9b8a
Update tests after refactoring
danielealbano Apr 9, 2023
5f4e072
Fix the export of the fiber entry point
danielealbano Apr 9, 2023
42a42ff
Register the new fiber at the start
danielealbano Apr 9, 2023
b8f950c
Implement an utility to convert a timespan in ms to an human readable…
danielealbano Apr 9, 2023
d3ffc8c
Ensure that the max files for the snapshots rotation, if present, is …
danielealbano Apr 9, 2023
4838f48
Use the same file name for the snapshot expected by redis
danielealbano Apr 9, 2023
ad46a27
Add some some checks in case of double deletes by index or similar
danielealbano Apr 9, 2023
edd6b1d
Modify hashtable_mpmc_op_set to do not free the key and instead just …
danielealbano Apr 9, 2023
bd47974
Implement a mechanism that allows the storage db to request specific …
danielealbano Apr 9, 2023
d722f71
Expose storage_db_snapshot functions
danielealbano Apr 9, 2023
7c0dd94
Move function to the header for inlining
danielealbano Apr 9, 2023
b0d1f01
Use the new mechanism to queue storage db entry indexes that should b…
danielealbano Apr 9, 2023
6b0b1a9
Fix a bug to catch buckets selected twice for eviction, it's an edge …
danielealbano Apr 9, 2023
63855d7
Initialize the required fields for the storage db to request on deman…
danielealbano Apr 9, 2023
0d5c3a0
Implement the high level mechanism for the snapshotting
danielealbano Apr 9, 2023
7a12a01
Ensure that the io_uring support for the storage is always initialized
danielealbano Apr 9, 2023
f0ccaa0
Currently only strings are supported
danielealbano Apr 9, 2023
fc0454c
Implement files rotation
danielealbano Apr 9, 2023
bc14a51
Implement reporting
danielealbano Apr 9, 2023
11e0b02
Various fixes and minor improvements
danielealbano Apr 9, 2023
92af2e3
Update tests after refactoring
danielealbano Apr 9, 2023
afe9fb1
Drop comments & commented out code
danielealbano Apr 9, 2023
2d64c62
Run the garbage collection more often
danielealbano Apr 9, 2023
9d2d559
Setup the snapshot parameters for the storage db config
danielealbano Apr 9, 2023
30e9a2a
Avoid purging too many entries altogether if the list is too long
danielealbano Apr 9, 2023
9a9bbd4
Fix storage_db_snapshot_rdb_write_value_header to accept the key from…
danielealbano Apr 9, 2023
322952f
Disable the LZF compression code, it's causing segfaults
danielealbano Apr 9, 2023
e38df6e
Only strings supported
danielealbano Apr 9, 2023
536ebf3
Ensure that if there are no data changed as requested by the configur…
danielealbano Apr 9, 2023
66a4a52
min_keys_changed shouldn't be set to zero
danielealbano Apr 9, 2023
94fe2cf
Useless check, the parameter is an unsigned int
danielealbano Apr 9, 2023
4a76057
Drop some logging that is not necessary
danielealbano Apr 9, 2023
c61550d
Update a comment in cachegrand.skel.yaml
danielealbano Apr 9, 2023
f1c8f83
Fix test
danielealbano Apr 9, 2023
2897401
Fix security warnings reported by codeql
danielealbano Apr 9, 2023
634e3a9
Fix the iouring storage backend test
danielealbano Apr 9, 2023
543736c
Ensure zero termination
danielealbano Apr 9, 2023
e3ac6cf
Fix test
danielealbano Apr 9, 2023
ad61102
Minor style fixes
danielealbano Apr 9, 2023
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
17 changes: 17 additions & 0 deletions etc/cachegrand.yaml.skel
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ database:
# # When database.limit.hard.max_memory_usage is set to 0, this value will be ignored
# max_keys: 999999

# The snapshot settings, optional, if missing the snapshots will be disabled. Snapshots are compatible with the Redis
# RDB format and can be used to restore the database state or import it from/to another instance.
snapshots:
# The path where the snapshot file will be stored, if the rotation is enabled, the path will be used as prefix and
# the timestamp of the start of the snapshot will be appended to the file name.
path: /var/lib/cachegrand/dump.rdb
# The interval between the snapshots, the allowed units are s, m, h, if not specified the default is seconds.
interval: 5m
# The number of keys that must be changed before a snapshot is taken, 0 means no limit
min_keys_changed: 1000
# The amount of data that must be changed before a snapshot is taken, the allowed units are b, k, m, g, if not
min_data_changed: 100mb
# Rotation settings, optional, if missing the snapshots rotation will be disabled
rotation:
# The max number of snapshots files to keep, minimum 2
max_files: 10

backend: memory
memory:
# Limits:
Expand Down
37 changes: 36 additions & 1 deletion src/clock.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,39 @@ int64_t clock_realtime_coarse_get_resolution_ms() {

int64_t clock_monotonic_coarse_get_resolution_ms() {
return 1;
}
}

char *clock_timespan_human_readable(
uint64_t timespan_ms,
char *buffer,
size_t buffer_length) {
assert(buffer_length >= CLOCK_TIMESPAN_MIN_LENGTH);

size_t offset = 0;
uint64_t days = timespan_ms / (1000 * 60 * 60 * 24);
uint64_t hours = (timespan_ms / (1000 * 60 * 60)) % 24;
uint64_t minutes = (timespan_ms / (1000 * 60)) % 60;
uint64_t seconds = (timespan_ms / 1000) % 60;

if (days > 0) {
offset += snprintf(buffer + offset, buffer_length - offset, "%lu days", days);
}

if (hours > 0) {
offset += snprintf(buffer + offset, buffer_length - offset, "%s%lu hours", offset > 0 ? " " : "", hours);
}

if (minutes > 0) {
offset += snprintf(buffer + offset, buffer_length - offset, "%s%lu minutes", offset > 0 ? " " : "", minutes);
}

if (seconds > 0) {
offset += snprintf(buffer + offset, buffer_length - offset, "%s%lu seconds", offset > 0 ? " " : "", seconds);
}

if (offset == 0) {
snprintf(buffer + offset, buffer_length - offset, "0 seconds");
}

return buffer;
}
8 changes: 8 additions & 0 deletions src/clock.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,20 @@ extern "C" {
#include "misc.h"
#include "fatal.h"

#define CLOCK_TIMESPAN_MIN_LENGTH (38)
#define CLOCK_TIMESPAN_MAX_LENGTH (64)

typedef struct timespec timespec_t;

int64_t clock_monotonic_coarse_get_resolution_ms();

int64_t clock_realtime_coarse_get_resolution_ms();

char *clock_timespan_human_readable(
uint64_t timespan_ms,
char *buffer,
size_t buffer_length);

static inline __attribute__((always_inline)) int64_t clock_timespec_to_int64_ms(
timespec_t *timespec) {
time_t s = timespec->tv_sec * 1000;
Expand Down
167 changes: 167 additions & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <sys/statvfs.h>
#include <sys/sysinfo.h>
#include <cyaml/cyaml.h>
#include <limits.h>

#include "exttypes.h"
#include "misc.h"
Expand Down Expand Up @@ -105,6 +106,98 @@ cyaml_err_t config_internal_cyaml_load(
NULL);
}

bool config_parse_string_time(
char *string,
size_t string_len,
bool allow_negative,
bool allow_zero,
bool allow_time_suffix,
int64_t *return_value) {
bool result = false;
char *string_end;
size_t string_end_len;
int64_t string_value;

// Skip any leading space
while (isspace(string[0]) && string_len > 0) {
string++;
string_len--;
}

// Skip any trailing space
while (isspace(string[string_len - 1]) && string_len > 0) {
string_len--;
}

// If there are only spaces, skip them
if (string_len == 0) {
return false;
}

// As strtoll doesn't support non-null terminated strings, duplicate the string and null terminate it using strndup
string = strndup(string, string_len);
if (string == NULL) {
return false;
}

// Try to parse the number
string_value = strtoll(string, &string_end, 10);
string_end_len = strlen(string_end);

// Skip any leading space after parsing string_end
while (isspace(string_end[0]) && string_end_len > 0) {
string_end++;
string_end_len--;
}

// Check if the end of the string matches the beginning of the string, if's true then the string is not a number
if (string_end == string) {
goto end;
}

// Check if string_value is negative
if (!allow_negative && string_value < 0) {
goto end;
}

// Check if string_value is zero
if (!allow_zero && string_value == 0) {
goto end;
}

if (!allow_time_suffix && string_end_len == 0) {
*return_value = string_value;
result = true;
goto end;
}

// Check if the string is a size suffix
if (allow_time_suffix && string_end_len == 1) {
switch (string_end[0]) {
case 's':
string_value *= 1;
break;
case 'm':
string_value *= 60;
break;
case 'h':
string_value *= 60 * 60;
break;
case 'd':
string_value *= 60 * 60 * 24;
break;
}

*return_value = string_value;
result = true;
goto end;
}

end:
free(string);
return result;
}

bool config_parse_string_absolute_or_percent(
char *string,
size_t string_len,
Expand Down Expand Up @@ -341,6 +434,38 @@ bool config_validate_after_load_database_backend_memory(
return return_result;
}

bool config_validate_after_load_database_snapshots(
config_t* config) {
bool return_result = true;

if (!config->database->snapshots) {
return return_result;
}

// Ensure that the path is not longer than PATH_MAX
if (strlen(config->database->snapshots->path) > PATH_MAX) {
LOG_E(TAG, "The path for the snapshots is too long");
return_result = false;
}

// Check that the maximum allowed interval is 7 days
if (config->database->snapshots->interval_ms > 7 * 24 * 60 * 60 * 1000) {
LOG_E(TAG, "The maximum allowed interval for the snapshots is <7> days");
return_result = false;
}

// Ensure that if rotation is enabled, the maximum number of max_files is equal or greater than 0 and smaller than
// uint16_t
if (config->database->snapshots->rotation && (
config->database->snapshots->rotation->max_files < 2 ||
config->database->snapshots->rotation->max_files > 65535)) {
LOG_E(TAG, "The maximum number of files for the snapshots rotation must be between <2> and <65535>");
return_result = false;
}

return return_result;
}

bool config_validate_after_load_database_limits(
config_t* config) {
bool return_result = true;
Expand Down Expand Up @@ -529,6 +654,7 @@ bool config_validate_after_load(
if (config_validate_after_load_cpus(config) == false
|| config_validate_after_load_database_backend_file(config) == false
|| config_validate_after_load_database_backend_memory(config) == false
|| config_validate_after_load_database_snapshots(config) == false
|| config_validate_after_load_database_limits(config) == false
|| config_validate_after_load_database_keys_eviction(config) == false
|| config_validate_after_load_modules(config) == false
Expand Down Expand Up @@ -779,6 +905,47 @@ bool config_process_string_values(
config_t *config) {
config_parse_string_absolute_or_percent_return_value_t return_value_type;

// Check if snapshots are enabled
if (config->database->snapshots) {
config->database->snapshots->min_data_changed = 0;

bool result = config_parse_string_time(
config->database->snapshots->interval_str,
strlen(config->database->snapshots->interval_str),
false,
false,
true,
&config->database->snapshots->interval_ms);

// The returned time is in seconds, so multiply by 1000 to convert to ms
config->database->snapshots->interval_ms *= 1000;

if (!result) {
LOG_E(TAG, "Failed to parse the snapshot interval");
config_free(config);
return NULL;
}

if (config->database->snapshots->min_data_changed_str) {
result = config_parse_string_absolute_or_percent(
config->database->snapshots->min_data_changed_str,
strlen(config->database->snapshots->min_data_changed_str),
false,
false,
false,
true,
true,
&config->database->snapshots->min_data_changed,
&return_value_type);

if (!result) {
LOG_E(TAG, "Failed to parse the snapshot minimum data changed");
config_free(config);
return NULL;
}
}
}

// Check if the memory backend for the database is defined in the config, if yes process the hard and soft memory
// limits, the latter is optional
if (config->database->memory) {
Expand Down
28 changes: 28 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,27 @@ struct config_database_limits {
};
typedef struct config_database_limits config_database_limits_t;

struct config_database_snapshots_rotation {
int64_t max_files;
};
typedef struct config_database_snapshots_rotation config_database_snapshots_rotation_t;

struct config_database_snapshots {
char *path;
char *interval_str;
char *min_data_changed_str;
int64_t interval_ms;
int64_t min_keys_changed;
int64_t min_data_changed;
config_database_snapshots_rotation_t *rotation;
};
typedef struct config_database_snapshots config_database_snapshots_t;

struct config_database {
config_database_limits_t *limits;
config_database_keys_eviction_t *keys_eviction;
config_database_backend_t backend;
config_database_snapshots_t *snapshots;
config_database_file_t *file;
config_database_memory_t *memory;
};
Expand Down Expand Up @@ -277,6 +294,14 @@ enum config_cpus_validate_error {
};
typedef enum config_cpus_validate_error config_cpus_validate_error_t;

bool config_parse_string_time(
char *string,
size_t string_len,
bool allow_negative,
bool allow_zero,
bool allow_time_suffix,
int64_t *return_value);

bool config_parse_string_absolute_or_percent(
char *string,
size_t string_len,
Expand All @@ -297,6 +322,9 @@ void config_free(
bool config_validate_after_load_cpus(
config_t* config);

bool config_validate_after_load_database_snapshots(
config_t* config);

bool config_validate_after_load_database_backend_file(
config_t* config);

Expand Down
31 changes: 31 additions & 0 deletions src/config_cyaml_schema.c
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,42 @@ const cyaml_schema_field_t config_database_limits_schema[] = {
CYAML_FIELD_END
};

// Schema for config -> database -> snapshots -> rotation
const cyaml_schema_field_t config_database_snapshots_rotation_schema[] = {
CYAML_FIELD_UINT(
"max_files", CYAML_FLAG_DEFAULT | CYAML_FLAG_OPTIONAL,
config_database_snapshots_rotation_t, max_files),
CYAML_FIELD_END
};

// Schema for config -> database -> snapshots
const cyaml_schema_field_t config_database_snapshots_schema[] = {
CYAML_FIELD_STRING_PTR(
"path", CYAML_FLAG_DEFAULT,
config_database_snapshots_t, path, 0, CYAML_UNLIMITED),
CYAML_FIELD_STRING_PTR(
"interval", CYAML_FLAG_DEFAULT,
config_database_snapshots_t, interval_str, 0, 20),
CYAML_FIELD_UINT(
"min_keys_changed", CYAML_FLAG_DEFAULT | CYAML_FLAG_OPTIONAL,
config_database_snapshots_t, min_keys_changed),
CYAML_FIELD_STRING_PTR(
"min_data_changed", CYAML_FLAG_DEFAULT | CYAML_FLAG_OPTIONAL,
config_database_snapshots_t, min_data_changed_str, 0, 20),
CYAML_FIELD_MAPPING_PTR(
"rotation", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
config_database_snapshots_t, rotation, config_database_snapshots_rotation_schema),
CYAML_FIELD_END
};

// Schema for config -> database
const cyaml_schema_field_t config_database_schema[] = {
CYAML_FIELD_MAPPING_PTR(
"limits", CYAML_FLAG_POINTER,
config_database_t, limits, config_database_limits_schema),
CYAML_FIELD_MAPPING_PTR(
"snapshots", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
config_database_t, snapshots, config_database_snapshots_schema),
CYAML_FIELD_MAPPING_PTR(
"keys_eviction", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
config_database_t, keys_eviction, config_database_keys_eviction_schema),
Expand Down
Loading