Skip to content

Commit 88f9665

Browse files
authored
detect if the disk cannot keep up with data collection (netdata#7139)
* Adjust dbengine flushing speed more dynamically * Added error tracking statistics for failure to flush events * Added alarm for dbengine flushing errors * Improved dbengine accounting for commited to be written pages
1 parent a6229b2 commit 88f9665

10 files changed

+122
-51
lines changed

configs.signatures

+1-1
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ declare -A configs_signatures=(
381381
['7deb236ec68a512b9bdd18e6a51d76f7']='python.d/mysql.conf'
382382
['7e5fc1644aa7a54f9dbb1bd102521b09']='health.d/memcached.conf'
383383
['7f13631183fbdf79c21c8e5a171e9b34']='health.d/zfs.conf'
384-
['e48b89d4a97b96acf9a88970ab858c3b']='health.d/dbengine.conf'
384+
['8edc8c73a8f3ca40b32e27fe452c70f3']='health.d/dbengine.conf'
385385
['7fb8184d56a27040e73261ed9c6fc76f']='health_alarm_notify.conf'
386386
['80266bddd3df374923c750a6de91d120']='health.d/apache.conf'
387387
['803a7f9dcb942eeac0fd764b9e3e38ca']='fping.conf'

daemon/global_statistics.c

+4-1
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ void global_statistics_charts(void) {
544544
if (host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) {
545545
++hosts_with_dbengine;
546546
/* get localhost's DB engine's statistics */
547-
rrdeng_get_33_statistics(host->rrdeng_ctx, local_stats_array);
547+
rrdeng_get_35_statistics(host->rrdeng_ctx, local_stats_array);
548548
for (i = 0 ; i < RRDENG_NR_STATS ; ++i) {
549549
/* aggregate statistics across hosts */
550550
stats_array[i] += local_stats_array[i];
@@ -775,6 +775,7 @@ void global_statistics_charts(void) {
775775
static RRDSET *st_errors = NULL;
776776
static RRDDIM *rd_fs_errors = NULL;
777777
static RRDDIM *rd_io_errors = NULL;
778+
static RRDDIM *rd_flushing_errors = NULL;
778779

779780
if (unlikely(!st_errors)) {
780781
st_errors = rrdset_create_localhost(
@@ -794,12 +795,14 @@ void global_statistics_charts(void) {
794795

795796
rd_io_errors = rrddim_add(st_errors, "I/O errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
796797
rd_fs_errors = rrddim_add(st_errors, "FS errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
798+
rd_flushing_errors = rrddim_add(st_errors, "flushing errors", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
797799
}
798800
else
799801
rrdset_next(st_errors);
800802

801803
rrddim_set_by_pointer(st_errors, rd_io_errors, (collected_number)stats_array[30]);
802804
rrddim_set_by_pointer(st_errors, rd_fs_errors, (collected_number)stats_array[31]);
805+
rrddim_set_by_pointer(st_errors, rd_flushing_errors, (collected_number)stats_array[34]);
803806
rrdset_done(st_errors);
804807
}
805808

database/engine/pagecache.c

+11-11
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ static void pg_cache_release_pages(struct rrdengine_instance *ctx, unsigned numb
214214
* This function returns the maximum number of pages allowed in the page cache.
215215
* The caller must hold the page cache lock.
216216
*/
217-
static inline unsigned long pg_cache_hard_limit(struct rrdengine_instance *ctx)
217+
unsigned long pg_cache_hard_limit(struct rrdengine_instance *ctx)
218218
{
219219
/* it's twice the number of producers since we pin 2 pages per producer */
220220
return ctx->max_cache_pages + 2 * (unsigned long)ctx->stats.metric_API_producers;
@@ -225,7 +225,7 @@ static inline unsigned long pg_cache_hard_limit(struct rrdengine_instance *ctx)
225225
* number of pages below that number.
226226
* The caller must hold the page cache lock.
227227
*/
228-
static inline unsigned long pg_cache_soft_limit(struct rrdengine_instance *ctx)
228+
unsigned long pg_cache_soft_limit(struct rrdengine_instance *ctx)
229229
{
230230
/* it's twice the number of producers since we pin 2 pages per producer */
231231
return ctx->cache_pages_low_watermark + 2 * (unsigned long)ctx->stats.metric_API_producers;
@@ -1029,14 +1029,14 @@ static void init_replaceQ(struct rrdengine_instance *ctx)
10291029
assert(0 == uv_rwlock_init(&pg_cache->replaceQ.lock));
10301030
}
10311031

1032-
static void init_commited_page_index(struct rrdengine_instance *ctx)
1032+
static void init_committed_page_index(struct rrdengine_instance *ctx)
10331033
{
10341034
struct page_cache *pg_cache = &ctx->pg_cache;
10351035

1036-
pg_cache->commited_page_index.JudyL_array = (Pvoid_t) NULL;
1037-
assert(0 == uv_rwlock_init(&pg_cache->commited_page_index.lock));
1038-
pg_cache->commited_page_index.latest_corr_id = 0;
1039-
pg_cache->commited_page_index.nr_commited_pages = 0;
1036+
pg_cache->committed_page_index.JudyL_array = (Pvoid_t) NULL;
1037+
assert(0 == uv_rwlock_init(&pg_cache->committed_page_index.lock));
1038+
pg_cache->committed_page_index.latest_corr_id = 0;
1039+
pg_cache->committed_page_index.nr_committed_pages = 0;
10401040
}
10411041

10421042
void init_page_cache(struct rrdengine_instance *ctx)
@@ -1049,7 +1049,7 @@ void init_page_cache(struct rrdengine_instance *ctx)
10491049

10501050
init_metrics_index(ctx);
10511051
init_replaceQ(ctx);
1052-
init_commited_page_index(ctx);
1052+
init_committed_page_index(ctx);
10531053
}
10541054

10551055
void free_page_cache(struct rrdengine_instance *ctx)
@@ -1062,9 +1062,9 @@ void free_page_cache(struct rrdengine_instance *ctx)
10621062
struct rrdeng_page_descr *descr;
10631063
struct page_cache_descr *pg_cache_descr;
10641064

1065-
/* Free commited page index */
1066-
ret_Judy = JudyLFreeArray(&pg_cache->commited_page_index.JudyL_array, PJE0);
1067-
assert(NULL == pg_cache->commited_page_index.JudyL_array);
1065+
/* Free committed page index */
1066+
ret_Judy = JudyLFreeArray(&pg_cache->committed_page_index.JudyL_array, PJE0);
1067+
assert(NULL == pg_cache->committed_page_index.JudyL_array);
10681068
bytes_freed += ret_Judy;
10691069

10701070
for (page_index = pg_cache->metrics_index.last_page_index ;

database/engine/pagecache.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct pg_cache_metrics_index {
110110
};
111111

112112
/* gathers dirty pages to be written on disk */
113-
struct pg_cache_commited_page_index {
113+
struct pg_cache_committed_page_index {
114114
uv_rwlock_t lock;
115115

116116
Pvoid_t JudyL_array;
@@ -122,7 +122,7 @@ struct pg_cache_commited_page_index {
122122
*/
123123
Word_t latest_corr_id;
124124

125-
unsigned nr_commited_pages;
125+
unsigned nr_committed_pages;
126126
};
127127

128128
/*
@@ -140,7 +140,7 @@ struct page_cache { /* TODO: add statistics */
140140
uv_rwlock_t pg_cache_rwlock; /* page cache lock */
141141

142142
struct pg_cache_metrics_index metrics_index;
143-
struct pg_cache_commited_page_index commited_page_index;
143+
struct pg_cache_committed_page_index committed_page_index;
144144
struct pg_cache_replaceQ replaceQ;
145145

146146
unsigned page_descriptors;
@@ -182,6 +182,8 @@ extern void init_page_cache(struct rrdengine_instance *ctx);
182182
extern void free_page_cache(struct rrdengine_instance *ctx);
183183
extern void pg_cache_add_new_metric_time(struct pg_cache_page_index *page_index, struct rrdeng_page_descr *descr);
184184
extern void pg_cache_update_metric_times(struct pg_cache_page_index *page_index);
185+
extern unsigned long pg_cache_hard_limit(struct rrdengine_instance *ctx);
186+
extern unsigned long pg_cache_soft_limit(struct rrdengine_instance *ctx);
185187

186188
static inline void
187189
pg_cache_atomic_get_pg_info(struct rrdeng_page_descr *descr, usec_t *end_timep, uint32_t *page_lengthp)

database/engine/rrdengine.c

+39-17
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55

66
rrdeng_stats_t global_io_errors = 0;
77
rrdeng_stats_t global_fs_errors = 0;
8-
rrdeng_stats_t global_pg_cache_warnings = 0;
9-
rrdeng_stats_t global_pg_cache_errors = 0;
108
rrdeng_stats_t rrdeng_reserved_file_descriptors = 0;
9+
rrdeng_stats_t global_flushing_errors = 0;
1110

1211
void sanity_check(void)
1312
{
@@ -253,6 +252,7 @@ void flush_pages_cb(uv_fs_t* req)
253252
{
254253
struct rrdengine_worker_config* wc = req->loop->data;
255254
struct rrdengine_instance *ctx = wc->ctx;
255+
struct page_cache *pg_cache = &ctx->pg_cache;
256256
struct extent_io_descriptor *xt_io_descr;
257257
struct rrdeng_page_descr *descr;
258258
struct page_cache_descr *pg_cache_descr;
@@ -290,6 +290,10 @@ void flush_pages_cb(uv_fs_t* req)
290290
uv_fs_req_cleanup(req);
291291
free(xt_io_descr->buf);
292292
freez(xt_io_descr);
293+
294+
uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
295+
pg_cache->committed_page_index.nr_committed_pages -= count;
296+
uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
293297
}
294298

295299
/*
@@ -323,14 +327,14 @@ static int do_flush_pages(struct rrdengine_worker_config* wc, int force, struct
323327
if (force) {
324328
debug(D_RRDENGINE, "Asynchronous flushing of extent has been forced by page pressure.");
325329
}
326-
uv_rwlock_wrlock(&pg_cache->commited_page_index.lock);
330+
uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
327331
for (Index = 0, count = 0, uncompressed_payload_length = 0,
328-
PValue = JudyLFirst(pg_cache->commited_page_index.JudyL_array, &Index, PJE0),
332+
PValue = JudyLFirst(pg_cache->committed_page_index.JudyL_array, &Index, PJE0),
329333
descr = unlikely(NULL == PValue) ? NULL : *PValue ;
330334

331335
descr != NULL && count != MAX_PAGES_PER_EXTENT ;
332336

333-
PValue = JudyLNext(pg_cache->commited_page_index.JudyL_array, &Index, PJE0),
337+
PValue = JudyLNext(pg_cache->committed_page_index.JudyL_array, &Index, PJE0),
334338
descr = unlikely(NULL == PValue) ? NULL : *PValue) {
335339
uint8_t page_write_pending;
336340

@@ -350,12 +354,11 @@ static int do_flush_pages(struct rrdengine_worker_config* wc, int force, struct
350354
rrdeng_page_descr_mutex_unlock(ctx, descr);
351355

352356
if (page_write_pending) {
353-
ret = JudyLDel(&pg_cache->commited_page_index.JudyL_array, Index, PJE0);
357+
ret = JudyLDel(&pg_cache->committed_page_index.JudyL_array, Index, PJE0);
354358
assert(1 == ret);
355-
--pg_cache->commited_page_index.nr_commited_pages;
356359
}
357360
}
358-
uv_rwlock_wrunlock(&pg_cache->commited_page_index.lock);
361+
uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
359362

360363
if (!count) {
361364
debug(D_RRDENGINE, "%s: no pages eligible for flushing.", __func__);
@@ -648,6 +651,9 @@ void async_cb(uv_async_t *handle)
648651
debug(D_RRDENGINE, "%s called, active=%d.", __func__, uv_is_active((uv_handle_t *)handle));
649652
}
650653

654+
/* Flushes dirty pages when timer expires */
655+
#define TIMER_PERIOD_MS (1000)
656+
651657
void timer_cb(uv_timer_t* handle)
652658
{
653659
struct rrdengine_worker_config* wc = handle->data;
@@ -657,12 +663,31 @@ void timer_cb(uv_timer_t* handle)
657663
rrdeng_test_quota(wc);
658664
debug(D_RRDENGINE, "%s: timeout reached.", __func__);
659665
if (likely(!wc->now_deleting.data)) {
660-
unsigned total_bytes, bytes_written;
661-
662666
/* There is free space so we can write to disk */
667+
struct rrdengine_instance *ctx = wc->ctx;
668+
struct page_cache *pg_cache = &ctx->pg_cache;
669+
unsigned long total_bytes, bytes_written, nr_committed_pages, bytes_to_write = 0, producers, low_watermark,
670+
high_watermark;
671+
672+
uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
673+
nr_committed_pages = pg_cache->committed_page_index.nr_committed_pages;
674+
uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
675+
producers = ctx->stats.metric_API_producers;
676+
/* are flushable pages more than 25% of the maximum page cache size */
677+
high_watermark = (ctx->max_cache_pages * 25LLU) / 100;
678+
low_watermark = (ctx->max_cache_pages * 5LLU) / 100; /* 5%, must be smaller than high_watermark */
679+
680+
if (nr_committed_pages > producers &&
681+
/* committed to be written pages are more than the produced number */
682+
nr_committed_pages - producers > high_watermark) {
683+
/* Flushing speed must increase to stop page cache from filling with dirty pages */
684+
bytes_to_write = (nr_committed_pages - producers - low_watermark) * RRDENG_BLOCK_SIZE;
685+
}
686+
bytes_to_write = MAX(DATAFILE_IDEAL_IO_SIZE, bytes_to_write);
687+
663688
debug(D_RRDENGINE, "Flushing pages to disk.");
664689
for (total_bytes = bytes_written = do_flush_pages(wc, 0, NULL) ;
665-
bytes_written && (total_bytes < DATAFILE_IDEAL_IO_SIZE) ;
690+
bytes_written && (total_bytes < bytes_to_write) ;
666691
total_bytes += bytes_written) {
667692
bytes_written = do_flush_pages(wc, 0, NULL);
668693
}
@@ -675,9 +700,6 @@ void timer_cb(uv_timer_t* handle)
675700
#endif
676701
}
677702

678-
/* Flushes dirty pages when timer expires */
679-
#define TIMER_PERIOD_MS (1000)
680-
681703
#define MAX_CMD_BATCH_SIZE (256)
682704

683705
void rrdeng_worker(void* arg)
@@ -771,8 +793,8 @@ void rrdeng_worker(void* arg)
771793
/* First I/O should be enough to call completion */
772794
bytes_written = do_flush_pages(wc, 1, cmd.completion);
773795
if (bytes_written) {
774-
while (do_flush_pages(wc, 1, NULL)) {
775-
; /* Force flushing of all commited pages. */
796+
while (do_flush_pages(wc, 1, NULL) && likely(!wc->now_deleting.data)) {
797+
; /* Force flushing of all committed pages if there is free space. */
776798
}
777799
}
778800
break;
@@ -789,7 +811,7 @@ void rrdeng_worker(void* arg)
789811
}
790812
info("Shutting down RRD engine event loop.");
791813
while (do_flush_pages(wc, 1, NULL)) {
792-
; /* Force flushing of all commited pages. */
814+
; /* Force flushing of all committed pages. */
793815
}
794816
wal_flush_transaction_buffer(wc);
795817
uv_run(loop, UV_RUN_DEFAULT);

database/engine/rrdengine.h

+3
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ struct rrdengine_statistics {
148148
rrdeng_stats_t page_cache_descriptors;
149149
rrdeng_stats_t io_errors;
150150
rrdeng_stats_t fs_errors;
151+
rrdeng_stats_t flushing_errors;
151152
};
152153

153154
/* I/O errors global counter */
@@ -156,6 +157,8 @@ extern rrdeng_stats_t global_io_errors;
156157
extern rrdeng_stats_t global_fs_errors;
157158
/* number of File-Descriptors that have been reserved by dbengine */
158159
extern rrdeng_stats_t rrdeng_reserved_file_descriptors;
160+
/* inability to flush global counter */
161+
extern rrdeng_stats_t global_flushing_errors;
159162

160163
struct rrdengine_instance {
161164
struct rrdengine_worker_config worker_config;

database/engine/rrdengineapi.c

+26-11
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ void rrdeng_store_metric_next(RRDDIM *rd, usec_t point_in_time, storage_number n
173173

174174
handle->descr = descr;
175175

176-
uv_rwlock_wrlock(&pg_cache->commited_page_index.lock);
177-
handle->page_correlation_id = pg_cache->commited_page_index.latest_corr_id++;
178-
uv_rwlock_wrunlock(&pg_cache->commited_page_index.lock);
176+
uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
177+
handle->page_correlation_id = pg_cache->committed_page_index.latest_corr_id++;
178+
uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
179179

180180
if (0 == rd->rrdset->rrddim_page_alignment) {
181181
/* this is the leading dimension that defines chart alignment */
@@ -614,18 +614,31 @@ void rrdeng_commit_page(struct rrdengine_instance *ctx, struct rrdeng_page_descr
614614
{
615615
struct page_cache *pg_cache = &ctx->pg_cache;
616616
Pvoid_t *PValue;
617+
unsigned nr_committed_pages;
617618

618619
if (unlikely(NULL == descr)) {
619-
debug(D_RRDENGINE, "%s: page descriptor is NULL, page has already been force-commited.", __func__);
620+
debug(D_RRDENGINE, "%s: page descriptor is NULL, page has already been force-committed.", __func__);
620621
return;
621622
}
622623
assert(descr->page_length);
623624

624-
uv_rwlock_wrlock(&pg_cache->commited_page_index.lock);
625-
PValue = JudyLIns(&pg_cache->commited_page_index.JudyL_array, page_correlation_id, PJE0);
625+
uv_rwlock_wrlock(&pg_cache->committed_page_index.lock);
626+
PValue = JudyLIns(&pg_cache->committed_page_index.JudyL_array, page_correlation_id, PJE0);
626627
*PValue = descr;
627-
++pg_cache->commited_page_index.nr_commited_pages;
628-
uv_rwlock_wrunlock(&pg_cache->commited_page_index.lock);
628+
nr_committed_pages = ++pg_cache->committed_page_index.nr_committed_pages;
629+
uv_rwlock_wrunlock(&pg_cache->committed_page_index.lock);
630+
631+
if (nr_committed_pages >= (pg_cache_hard_limit(ctx) - (unsigned long)ctx->stats.metric_API_producers) / 2) {
632+
/* 50% of pages have not been committed yet */
633+
if (0 == (unsigned long)ctx->stats.flushing_errors) {
634+
/* only print the first time */
635+
error("Failed to flush quickly enough in dbengine instance \"%s\""
636+
". Metric data will not be stored in the database"
637+
", please reduce disk load or use a faster disk.", ctx->dbfiles_path);
638+
}
639+
rrd_stat_atomic_add(&ctx->stats.flushing_errors, 1);
640+
rrd_stat_atomic_add(&global_flushing_errors, 1);
641+
}
629642

630643
pg_cache_put(ctx, descr);
631644
}
@@ -674,15 +687,15 @@ void *rrdeng_get_page(struct rrdengine_instance *ctx, uuid_t *id, usec_t point_i
674687
* You must not change the indices of the statistics or user code will break.
675688
* You must not exceed RRDENG_NR_STATS or it will crash.
676689
*/
677-
void rrdeng_get_33_statistics(struct rrdengine_instance *ctx, unsigned long long *array)
690+
void rrdeng_get_35_statistics(struct rrdengine_instance *ctx, unsigned long long *array)
678691
{
679692
struct page_cache *pg_cache = &ctx->pg_cache;
680693

681694
array[0] = (uint64_t)ctx->stats.metric_API_producers;
682695
array[1] = (uint64_t)ctx->stats.metric_API_consumers;
683696
array[2] = (uint64_t)pg_cache->page_descriptors;
684697
array[3] = (uint64_t)pg_cache->populated_pages;
685-
array[4] = (uint64_t)pg_cache->commited_page_index.nr_commited_pages;
698+
array[4] = (uint64_t)pg_cache->committed_page_index.nr_committed_pages;
686699
array[5] = (uint64_t)ctx->stats.pg_cache_insertions;
687700
array[6] = (uint64_t)ctx->stats.pg_cache_deletions;
688701
array[7] = (uint64_t)ctx->stats.pg_cache_hits;
@@ -711,7 +724,9 @@ void rrdeng_get_33_statistics(struct rrdengine_instance *ctx, unsigned long long
711724
array[30] = (uint64_t)global_io_errors;
712725
array[31] = (uint64_t)global_fs_errors;
713726
array[32] = (uint64_t)rrdeng_reserved_file_descriptors;
714-
assert(RRDENG_NR_STATS == 33);
727+
array[33] = (uint64_t)ctx->stats.flushing_errors;
728+
array[34] = (uint64_t)global_flushing_errors;
729+
assert(RRDENG_NR_STATS == 35);
715730
}
716731

717732
/* Releases reference to page */

database/engine/rrdengineapi.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#define RRDENG_MIN_PAGE_CACHE_SIZE_MB (8)
99
#define RRDENG_MIN_DISK_SPACE_MB (256)
1010

11-
#define RRDENG_NR_STATS (33)
11+
#define RRDENG_NR_STATS (35)
1212

1313
#define RRDENG_FD_BUDGET_PER_INSTANCE (50)
1414

@@ -41,7 +41,7 @@ extern int rrdeng_load_metric_is_finished(struct rrddim_query_handle *rrdimm_han
4141
extern void rrdeng_load_metric_finalize(struct rrddim_query_handle *rrdimm_handle);
4242
extern time_t rrdeng_metric_latest_time(RRDDIM *rd);
4343
extern time_t rrdeng_metric_oldest_time(RRDDIM *rd);
44-
extern void rrdeng_get_33_statistics(struct rrdengine_instance *ctx, unsigned long long *array);
44+
extern void rrdeng_get_35_statistics(struct rrdengine_instance *ctx, unsigned long long *array);
4545

4646
/* must call once before using anything */
4747
extern int rrdeng_init(struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb,

0 commit comments

Comments
 (0)