Improve agent shutdown (#19775)
Enhance DBENGINE flushing mechanism to prioritize dirty page flushing first during shutdown
Stelios Fragkakis committed
Mar 5, 2025 at 23:26 UTC
f351681b04c1ce5df31ba8c5fa3f24a2092d91c6
11 files changed
+50
-12
src/daemon/daemon-shutdown.c
+8
-7
@@ -100,15 +100,16 @@ static void *rrdeng_exit_background(void *ptr) {
100
}
101
102
#ifdef ENABLE_DBENGINE
103
-static void rrdeng_flush_everything_and_wait(bool wait_flush, bool wait_collectors) {
103
+static void rrdeng_flush_everything_and_wait(bool wait_flush, bool wait_collectors, bool dirty_only)
104
+{
105
static size_t starting_size_to_flush = 0;
106
107
if(!pgc_hot_and_dirty_entries(main_cache))
108
return;
109
109
- nd_log(NDLS_DAEMON, NDLP_INFO, "Flushing DBENGINE hot & dirty pages...");
110
+ nd_log(NDLS_DAEMON, NDLP_INFO, "Flushing DBENGINE %s dirty pages...", dirty_only ? "only" : "hot &");
111
for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++)
111
- rrdeng_quiesce(multidb_ctx[tier]);
112
+ rrdeng_quiesce(multidb_ctx[tier], dirty_only);
113
114
struct pgc_statistics pgc_main_stats = pgc_get_statistics(main_cache);
115
size_t size_to_flush = pgc_main_stats.queues[PGC_QUEUE_HOT].size + pgc_main_stats.queues[PGC_QUEUE_DIRTY].size;
@@ -185,7 +186,7 @@ void netdata_cleanup_and_exit(EXIT_REASON reason, const char *action, const char
186
#ifdef ENABLE_DBENGINE
187
if(!ret && dbengine_enabled)
188
// flush all dirty pages asap
188
- rrdeng_flush_everything_and_wait(false, false);
189
+ rrdeng_flush_everything_and_wait(false, false, true);
190
#endif
191
192
// send the stat from our caller
@@ -219,7 +220,7 @@ void netdata_cleanup_and_exit(EXIT_REASON reason, const char *action, const char
220
#ifdef ENABLE_DBENGINE
221
if(!ret && dbengine_enabled)
222
// flush all dirty pages now that all collectors and streaming completed
222
- rrdeng_flush_everything_and_wait(false, false);
223
+ rrdeng_flush_everything_and_wait(false, false, false);
224
#endif
225
226
service_wait_exit(SERVICE_REPLICATION, 3 * USEC_PER_SEC);
@@ -263,7 +264,7 @@ void netdata_cleanup_and_exit(EXIT_REASON reason, const char *action, const char
264
#ifdef ENABLE_DBENGINE
265
if(dbengine_enabled) {
266
// flush anything remaining and wait for collectors to finish
266
- rrdeng_flush_everything_and_wait(true, true);
267
+ rrdeng_flush_everything_and_wait(true, true, false);
268
watcher_step_complete(WATCHER_STEP_ID_WAIT_FOR_DBENGINE_COLLECTORS_TO_FINISH);
269
270
ND_THREAD *th[nd_profile.storage_tiers];
@@ -271,7 +272,7 @@ void netdata_cleanup_and_exit(EXIT_REASON reason, const char *action, const char
272
th[tier] = nd_thread_create("rrdeng-exit", NETDATA_THREAD_OPTION_JOINABLE, rrdeng_exit_background, multidb_ctx[tier]);
273
274
// flush anything remaining again - just in case
274
- rrdeng_flush_everything_and_wait(true, false);
275
+ rrdeng_flush_everything_and_wait(true, false, false);
276
277
for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++)
278
nd_thread_join(th[tier]);
src/daemon/libuv_workers.c
+1
@@ -47,6 +47,7 @@ void register_libuv_worker_jobs() {
47
worker_register_job_name(UV_EVENT_DBENGINE_EVICT_OPEN_CACHE, "evict open");
48
worker_register_job_name(UV_EVENT_DBENGINE_EVICT_EXTENT_CACHE, "evict extent");
49
worker_register_job_name(UV_EVENT_DBENGINE_BUFFERS_CLEANUP, "dbengine buffers cleanup");
50
+ worker_register_job_name(UV_EVENT_DBENGINE_FLUSH_DIRTY, "dbengine flush dirty");
51
worker_register_job_name(UV_EVENT_DBENGINE_QUIESCE, "dbengine quiesce");
52
worker_register_job_name(UV_EVENT_DBENGINE_SHUTDOWN, "dbengine shutdown");
53
src/daemon/libuv_workers.h
+1
@@ -39,6 +39,7 @@ enum event_loop_job {
39
UV_EVENT_DBENGINE_EVICT_OPEN_CACHE,
40
UV_EVENT_DBENGINE_EVICT_EXTENT_CACHE,
41
UV_EVENT_DBENGINE_BUFFERS_CLEANUP,
42
+ UV_EVENT_DBENGINE_FLUSH_DIRTY,
43
UV_EVENT_DBENGINE_QUIESCE,
44
UV_EVENT_DBENGINE_SHUTDOWN,
45
src/database/engine/cache.c
+4
@@ -2075,6 +2075,10 @@ struct aral_statistics *pgc_aral_stats(void) {
2075
return &pgc_aral_statistics;
2076
}
2077
2078
+void pgc_flush_dirty_pages(PGC *cache, Word_t section) {
2079
+ flush_pages(cache, 0, section, true, true);
2080
+}
2081
+
2082
void pgc_flush_all_hot_and_dirty_pages(PGC *cache, Word_t section) {
2083
all_hot_pages_to_dirty(cache, section);
2084
src/database/engine/cache.h
+1
@@ -178,6 +178,7 @@ PGC *pgc_create(const char *name,
178
void pgc_destroy(PGC *cache);
179
180
#define PGC_SECTION_ALL ((Word_t)0)
181
+void pgc_flush_dirty_pages(PGC *cache, Word_t section);
182
void pgc_flush_all_hot_and_dirty_pages(PGC *cache, Word_t section);
183
184
// add a page to the cache and return a pointer to it
src/database/engine/dbengine-stresstest.c
+1
-1
@@ -447,7 +447,7 @@ void dbengine_stress_test(unsigned TEST_DURATION_SEC, unsigned DSET_CHARTS, unsi
447
}
448
freez(query_threads);
449
rrd_wrlock();
450
- rrdeng_quiesce((struct rrdengine_instance *)host->db[0].si);
450
+ rrdeng_quiesce((struct rrdengine_instance *)host->db[0].si, false);
451
rrdeng_exit((struct rrdengine_instance *)host->db[0].si);
452
rrdeng_enq_cmd(NULL, RRDENG_OPCODE_SHUTDOWN_EVLOOP, NULL, NULL, STORAGE_PRIORITY_BEST_EFFORT, NULL, NULL);
453
rrd_wrunlock();
src/database/engine/dbengine-unittest.c
+1
-1
@@ -408,7 +408,7 @@ int test_dbengine(void) {
408
}
409
410
rrd_wrlock();
411
- rrdeng_quiesce((struct rrdengine_instance *)host->db[0].si);
411
+ rrdeng_quiesce((struct rrdengine_instance *)host->db[0].si, false);
412
rrdeng_exit((struct rrdengine_instance *)host->db[0].si);
413
rrdeng_enq_cmd(NULL, RRDENG_OPCODE_SHUTDOWN_EVLOOP, NULL, NULL, STORAGE_PRIORITY_BEST_EFFORT, NULL, NULL);
414
rrd_wrunlock();
src/database/engine/rrdengine.c
+25
@@ -1325,6 +1325,21 @@ static void *flush_all_hot_and_dirty_pages_of_section_tp_worker(struct rrdengine
1325
return data;
1326
}
1327
1328
+static void after_flush_dirty_pages_of_section(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
1329
+ ;
1330
+}
1331
+
1332
+static void *flush_dirty_pages_of_section_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) {
1333
+ worker_is_busy(UV_EVENT_DBENGINE_FLUSH_DIRTY);
1334
+ pgc_flush_dirty_pages(main_cache, (Word_t)ctx);
1335
+
1336
+ for(size_t i = 0; i < pgc_max_flushers() ; i++)
1337
+ rrdeng_enq_cmd(NULL, RRDENG_OPCODE_FLUSH_MAIN, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
1338
+
1339
+ return data;
1340
+}
1341
+
1342
+
1343
static void after_populate_mrg(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) {
1344
;
1345
}
@@ -1873,6 +1888,7 @@ void dbengine_event_loop(void* arg) {
1888
worker_register_job_name(RRDENG_OPCODE_FLUSH_MAIN, "flush init");
1889
worker_register_job_name(RRDENG_OPCODE_EVICT_MAIN, "evict init");
1890
worker_register_job_name(RRDENG_OPCODE_CTX_SHUTDOWN, "ctx shutdown");
1891
+ worker_register_job_name(RRDENG_OPCODE_CTX_FLUSH_DIRTY, "ctx flush dirty");
1892
worker_register_job_name(RRDENG_OPCODE_CTX_QUIESCE, "ctx quiesce");
1893
worker_register_job_name(RRDENG_OPCODE_SHUTDOWN_EVLOOP, "dbengine shutdown");
1894
@@ -1886,6 +1902,7 @@ void dbengine_event_loop(void* arg) {
1902
worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_FLUSH_MAIN, "flush init cb");
1903
worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EVICT_MAIN, "evict init cb");
1904
worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_SHUTDOWN, "ctx shutdown cb");
1905
+ worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_FLUSH_DIRTY, "ctx flush dirty cb");
1906
worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_QUIESCE, "ctx quiesce cb");
1907
1908
// special jobs
@@ -2013,6 +2030,14 @@ void dbengine_event_loop(void* arg) {
2030
break;
2031
}
2032
2033
+ case RRDENG_OPCODE_CTX_FLUSH_DIRTY: {
2034
+ struct rrdengine_instance *ctx = cmd.ctx;
2035
+ work_dispatch(ctx, NULL, NULL, opcode,
2036
+ flush_dirty_pages_of_section_tp_worker,
2037
+ after_flush_dirty_pages_of_section);
2038
+ break;
2039
+ }
2040
+
2041
case RRDENG_OPCODE_CTX_QUIESCE: {
2042
// a ctx will shutdown shortly
2043
struct rrdengine_instance *ctx = cmd.ctx;
src/database/engine/rrdengine.h
+1
@@ -255,6 +255,7 @@ enum rrdeng_opcode {
255
RRDENG_OPCODE_EVICT_OPEN,
256
RRDENG_OPCODE_EVICT_EXTENT,
257
RRDENG_OPCODE_CTX_SHUTDOWN,
258
+ RRDENG_OPCODE_CTX_FLUSH_DIRTY,
259
RRDENG_OPCODE_CTX_QUIESCE,
260
RRDENG_OPCODE_CTX_POPULATE_MRG,
261
RRDENG_OPCODE_SHUTDOWN_EVLOOP,
src/database/engine/rrdengineapi.c
+6
-2
@@ -1294,14 +1294,18 @@ int rrdeng_exit(struct rrdengine_instance *ctx) {
1294
return 0;
1295
}
1296
1297
-void rrdeng_quiesce(struct rrdengine_instance *ctx) {
1297
+void rrdeng_quiesce(struct rrdengine_instance *ctx, bool dirty_only)
1298
+{
1299
if (NULL == ctx)
1300
return;
1301
1302
// FIXME - ktsaou - properly cleanup ctx
1303
// 1. make sure all collectors are stopped
1304
1304
- rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_QUIESCE, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
1305
+ if (dirty_only)
1306
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_FLUSH_DIRTY, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
1307
+ else
1308
+ rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_QUIESCE, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
1309
}
1310
1311
static void populate_v2_statistics(struct rrdengine_datafile *datafile, RRDENG_SIZE_STATS *stats)
src/database/engine/rrdengineapi.h
+1
-1
@@ -74,7 +74,7 @@ int rrdeng_init(
74
void rrdeng_readiness_wait(struct rrdengine_instance *ctx);
75
76
int rrdeng_exit(struct rrdengine_instance *ctx);
77
-void rrdeng_quiesce(struct rrdengine_instance *ctx);
77
+void rrdeng_quiesce(struct rrdengine_instance *ctx, bool dirty_only);
78
79
bool rrdeng_metric_retention_by_id(STORAGE_INSTANCE *si, UUIDMAP_ID id, time_t *first_entry_s, time_t *last_entry_s);
80
bool rrdeng_metric_retention_by_uuid(STORAGE_INSTANCE *si, nd_uuid_t *dim_uuid, time_t *first_entry_s, time_t *last_entry_s);