Stress test insertions into dbengine and bugfixes (#6814)
* Fix memory corruption during deallocation of page cache * Refactored dataset generator in order to support the upcoming self-validating stress test and multithreading. * Fix starvation in database engine loop when the command queues are continuously populated * Fixing disk quota limits for dbengine dataset generator
Markos Fountoulakis committed
Sep 12, 2019 at 13:39 UTC
4c9d6a37138d95495e2dba20eed1931556d4252d
3 files changed
+110
-32
daemon/unit_test.c
+85
-28
@@ -1928,42 +1928,54 @@ error_out:
1928
return errors;
1929
}
1930
1931
-void generate_dbengine_dataset(unsigned history_seconds)
1931
+struct dbengine_chart_thread {
1932
+ uv_thread_t thread;
1933
+ RRDHOST *host;
1934
+ char *chartname; /* Will be prefixed by type, e.g. "example_local1.", "example_local2." etc */
1935
+ int dset_charts; /* number of charts */
1936
+ int dset_dims; /* dimensions per chart */
1937
+ int chart_i; /* current chart offset */
1938
+ time_t time_present; /* current virtual time of the benchmark */
1939
+ unsigned history_seconds; /* how far back in the past to go */
1940
+};
1941
+
1942
+collected_number generate_dbengine_chart_value(struct dbengine_chart_thread *thread_info, int dim_i,
1943
+ time_t time_current)
1944
{
1933
- const int DSET_DIMS = 128;
1934
- const uint64_t EXPECTED_COMPRESSION_RATIO = 94;
1935
- int j, update_every = 1;
1936
- RRDHOST *host = NULL;
1937
- RRDSET *st;
1938
- RRDDIM *rd[DSET_DIMS];
1939
- char name[101];
1940
- time_t time_current, time_present;
1945
+ collected_number value;
1946
1942
- default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE;
1943
- default_rrdeng_page_cache_mb = 128;
1944
- // Worst case for uncompressible data
1945
- default_rrdeng_disk_quota_mb = (((uint64_t)DSET_DIMS) * sizeof(storage_number) * history_seconds) / (1024 * 1024);
1946
- default_rrdeng_disk_quota_mb -= default_rrdeng_disk_quota_mb * EXPECTED_COMPRESSION_RATIO / 100;
1947
+ value = ((collected_number)time_current) * thread_info->chart_i;
1948
+ value += ((collected_number)time_current) * dim_i;
1949
+ value %= 1024LLU;
1950
1948
- error_log_limit_unlimited();
1949
- debug(D_RRDHOST, "Initializing localhost with hostname 'dbengine-dataset'");
1951
+ return value;
1952
+}
1953
1951
- host = dbengine_rrdhost_find_or_create("dbengine-dataset");
1952
- if (NULL == host)
1953
- return;
1954
+static void generate_dbengine_chart(void *arg)
1955
+{
1956
+ struct dbengine_chart_thread *thread_info = (struct dbengine_chart_thread *)arg;
1957
+ RRDHOST *host = thread_info->host;
1958
+ char *chartname = thread_info->chartname;
1959
+ const int DSET_DIMS = thread_info->dset_dims;
1960
+ unsigned history_seconds = thread_info->history_seconds;
1961
+ time_t time_present = thread_info->time_present;
1962
1955
- fprintf(stderr, "\nRunning DB-engine workload generator\n");
1963
+ int j, update_every = 1;
1964
+ RRDSET *st;
1965
+ RRDDIM *rd[DSET_DIMS];
1966
+ char name[RRD_ID_LENGTH_MAX + 1];
1967
+ time_t time_current;
1968
1969
// create the chart
1958
- st = rrdset_create(host, "example", "random", "random", "example", NULL, "random", "random", "random",
1959
- NULL, 1, update_every, RRDSET_TYPE_LINE);
1970
+ snprintfz(name, RRD_ID_LENGTH_MAX, "example_local%d", thread_info->chart_i + 1);
1971
+ st = rrdset_create(host, name, chartname, chartname, "example", NULL, chartname, chartname, chartname, NULL, 1,
1972
+ update_every, RRDSET_TYPE_LINE);
1973
for (j = 0 ; j < DSET_DIMS ; ++j) {
1961
- snprintfz(name, 100, "random%d", j);
1974
+ snprintfz(name, RRD_ID_LENGTH_MAX, "%s%d", chartname, j);
1975
1976
rd[j] = rrddim_add(st, name, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
1977
}
1978
1966
- time_present = now_realtime_sec();
1979
// feed it with the test data
1980
time_current = time_present - history_seconds;
1981
for (j = 0 ; j < DSET_DIMS ; ++j) {
@@ -1972,17 +1984,62 @@ void generate_dbengine_dataset(unsigned history_seconds)
1984
rd[j]->last_collected_time.tv_usec =
1985
st->last_collected_time.tv_usec = st->last_updated.tv_usec = 0;
1986
}
1975
- for( ; time_current < time_present; ++time_current) {
1987
+ for( ; time_current < time_present ; ++time_current) {
1988
st->usec_since_last_update = USEC_PER_SEC;
1989
1990
for (j = 0; j < DSET_DIMS; ++j) {
1979
- rrddim_set_by_pointer_fake_time(rd[j], (time_current + j) % 128, time_current);
1991
+ collected_number value;
1992
+
1993
+ value = generate_dbengine_chart_value(thread_info, j, time_current);
1994
+ rrddim_set_by_pointer_fake_time(rd[j], value, time_current);
1995
}
1996
rrdset_done(st);
1997
}
1998
+}
1999
+
2000
+void generate_dbengine_dataset(unsigned history_seconds)
2001
+{
2002
+ const int DSET_CHARTS = 16;
2003
+ const int DSET_DIMS = 128;
2004
+ const uint64_t EXPECTED_COMPRESSION_RATIO = 20;
2005
+ RRDHOST *host = NULL;
2006
+ struct dbengine_chart_thread thread_info[DSET_CHARTS];
2007
+ int i;
2008
+ time_t time_present;
2009
+
2010
+ default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE;
2011
+ default_rrdeng_page_cache_mb = 128;
2012
+ // Worst case for uncompressible data
2013
+ default_rrdeng_disk_quota_mb = (((uint64_t)DSET_DIMS * DSET_CHARTS) * sizeof(storage_number) * history_seconds) /
2014
+ (1024 * 1024);
2015
+ default_rrdeng_disk_quota_mb -= default_rrdeng_disk_quota_mb * EXPECTED_COMPRESSION_RATIO / 100;
2016
+
2017
+ error_log_limit_unlimited();
2018
+ debug(D_RRDHOST, "Initializing localhost with hostname 'dbengine-dataset'");
2019
+
2020
+ host = dbengine_rrdhost_find_or_create("dbengine-dataset");
2021
+ if (NULL == host)
2022
+ return;
2023
+
2024
+ fprintf(stderr, "\nRunning DB-engine workload generator\n");
2025
+
2026
+ time_present = now_realtime_sec();
2027
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
2028
+ thread_info[i].host = host;
2029
+ thread_info[i].chartname = "random";
2030
+ thread_info[i].dset_charts = DSET_CHARTS;
2031
+ thread_info[i].chart_i = i;
2032
+ thread_info[i].dset_dims = DSET_DIMS;
2033
+ thread_info[i].history_seconds = history_seconds;
2034
+ thread_info[i].time_present = time_present;
2035
+ assert(0 == uv_thread_create(&thread_info[i].thread, generate_dbengine_chart, &thread_info[i]));
2036
+ }
2037
+ for (i = 0 ; i < DSET_CHARTS ; ++i) {
2038
+ assert(0 == uv_thread_join(&thread_info[i].thread));
2039
+ }
2040
+
2041
rrd_wrlock();
2042
rrdhost_free(host);
2043
rrd_unlock();
1986
-
2044
}
1988
-#endif
\ No newline at end of file
2045
+#endif
database/engine/pagecache.c
+13
-3
@@ -216,6 +216,9 @@ static void pg_cache_release_pages(struct rrdengine_instance *ctx, unsigned numb
216
static void pg_cache_reserve_pages(struct rrdengine_instance *ctx, unsigned number)
217
{
218
struct page_cache *pg_cache = &ctx->pg_cache;
219
+ unsigned failures = 0;
220
+ const unsigned FAILURES_CEILING = 10; /* truncates exponential backoff to (2^FAILURES_CEILING x slot) */
221
+ unsigned long exp_backoff_slot_usec = USEC_PER_MS * 10;
222
223
assert(number < ctx->max_cache_pages);
224
@@ -224,11 +227,13 @@ static void pg_cache_reserve_pages(struct rrdengine_instance *ctx, unsigned numb
227
debug(D_RRDENGINE, "==Page cache full. Reserving %u pages.==",
228
number);
229
while (pg_cache->populated_pages + number >= ctx->max_cache_pages + 1) {
230
+
231
if (!pg_cache_try_evict_one_page_unsafe(ctx)) {
232
/* failed to evict */
233
struct completion compl;
234
struct rrdeng_cmd cmd;
235
236
+ ++failures;
237
uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
238
239
init_completion(&compl);
@@ -240,6 +245,12 @@ static void pg_cache_reserve_pages(struct rrdengine_instance *ctx, unsigned numb
245
wait_for_completion(&compl);
246
destroy_completion(&compl);
247
248
+ if (unlikely(failures > 1)) {
249
+ unsigned long slots;
250
+ /* exponential backoff */
251
+ slots = random() % (2LU << MIN(failures, FAILURES_CEILING));
252
+ (void)sleep_usec(slots * exp_backoff_slot_usec);
253
+ }
254
uv_rwlock_wrlock(&pg_cache->pg_cache_rwlock);
255
}
256
}
@@ -1042,9 +1053,8 @@ void free_page_cache(struct rrdengine_instance *ctx)
1053
/* Find first page in range */
1054
Index = (Word_t) 0;
1055
PValue = JudyLFirst(page_index->JudyL_array, &Index, PJE0);
1045
- if (likely(NULL != PValue)) {
1046
- descr = *PValue;
1047
- }
1056
+ descr = unlikely(NULL == PValue) ? NULL : *PValue;
1057
+
1058
while (descr != NULL) {
1059
/* Iterate all page descriptors of this metric */
1060
database/engine/rrdengine.c
+12
-1
@@ -676,7 +676,7 @@ void timer_cb(uv_timer_t* handle)
676
/* Flushes dirty pages when timer expires */
677
#define TIMER_PERIOD_MS (1000)
678
679
-#define CMD_BATCH_SIZE (256)
679
+#define MAX_CMD_BATCH_SIZE (256)
680
681
void rrdeng_worker(void* arg)
682
{
@@ -687,6 +687,7 @@ void rrdeng_worker(void* arg)
687
enum rrdeng_opcode opcode;
688
uv_timer_t timer_req;
689
struct rrdeng_cmd cmd;
690
+ unsigned cmd_batch_size;
691
692
rrdeng_init_cmd_queue(wc);
693
@@ -723,10 +724,20 @@ void rrdeng_worker(void* arg)
724
shutdown = 0;
725
while (shutdown == 0 || uv_loop_alive(loop)) {
726
uv_run(loop, UV_RUN_DEFAULT);
727
+
728
/* wait for commands */
729
+ cmd_batch_size = 0;
730
do {
731
+ /*
732
+ * Avoid starving the loop when there are too many commands coming in.
733
+ * timer_cb will interrupt the loop again to allow serving more commands.
734
+ */
735
+ if (unlikely(cmd_batch_size >= MAX_CMD_BATCH_SIZE))
736
+ break;
737
+
738
cmd = rrdeng_deq_cmd(wc);
739
opcode = cmd.opcode;
740
+ ++cmd_batch_size;
741
742
switch (opcode) {
743
case RRDENG_NOOP: