Improve metadata storage stability (#21468)
Refactor SQLite metadata handling: use `rrdhost_find_and_acquire()` for safer host lookup and memory management. Enhance JudyL error handling, refactor database vacuum logic, and fix metadata memory management issues.
Stelios Fragkakis committed
Dec 17, 2025 at 21:53 UTC
b7e7983d26a339b738b0447edd33aeb326f4d48f
3 files changed
+37
-31
src/database/sqlite/sqlite_metadata.c
+34
-29
@@ -332,10 +332,10 @@ static void ctx_get_context_list_to_cleanup(nd_uuid_t *host_uuid, void (*cleanup
332
context = (char *) sqlite3_column_text(res, 0);
333
STRING *ctx = string_strdupz(context);
334
Pvalue = JudyLIns(&CTX_JudyL, (Word_t) ctx, PJE0);
335
- if (*Pvalue)
335
+ if (Pvalue == PJERR || *Pvalue)
336
string_freez(ctx);
337
else
338
- *(int *)Pvalue = 1;
338
+ *Pvalue = (void *)1;
339
}
340
341
if (CTX_JudyL) {
@@ -394,7 +394,7 @@ bool sql_set_host_label(nd_uuid_t *host_id, const char *label_key, const char *l
394
return false;
395
396
if (!PREPARE_STATEMENT(db_meta, SQL_SET_HOST_LABEL, &res))
397
- return 1;
397
+ return false;
398
399
int param = 0;
400
SQLITE_BIND_FAIL(done, sqlite3_bind_blob(res, ++param, host_id, sizeof(*host_id), SQLITE_STATIC));
@@ -419,14 +419,17 @@ void sql_update_node_id(nd_uuid_t *host_id, nd_uuid_t *node_id)
419
{
420
sqlite3_stmt *res = NULL;
421
RRDHOST *host = NULL;
422
+ RRDHOST_ACQUIRED *acquired_host = NULL;
423
423
- char host_guid[GUID_LEN + 1];
424
+ char host_guid[UUID_STR_LEN];
425
uuid_unparse_lower(*host_id, host_guid);
425
- rrd_wrlock();
426
- host = rrdhost_find_by_guid(host_guid);
427
- if (likely(host))
428
- set_host_node_id(host, node_id);
429
- rrd_wrunlock();
426
+
427
+ acquired_host = rrdhost_find_and_acquire(host_guid);
428
+ if (acquired_host) {
429
+ if ((host = rrdhost_acquired_to_rrdhost(acquired_host)))
430
+ set_host_node_id(host, node_id);
431
+ rrdhost_acquired_release(acquired_host);
432
+ }
433
434
if (!PREPARE_STATEMENT(db_meta, SQL_UPDATE_NODE_ID, &res))
435
return;
@@ -1558,15 +1561,14 @@ static void timer_cb(uv_timer_t *handle)
1561
config->store_metadata = true;
1562
}
1563
1561
-void vacuum_database(sqlite3 *database, const char *db_alias, int threshold, int vacuum_pc)
1564
+void vacuum_database(sqlite3 *database, const char *db_alias, int threshold, int vacuum_pc, time_t *next_run)
1565
{
1563
- static time_t next_run = 0;
1564
-
1566
time_t now = now_realtime_sec();
1566
- if (next_run > now)
1567
+ if (next_run && *next_run > now)
1568
return;
1569
1569
- next_run = now + DATABASE_VACUUM_FREQUENCY_SECONDS;
1570
+ if (next_run)
1571
+ *next_run = now + DATABASE_VACUUM_FREQUENCY_SECONDS;
1572
1573
int free_pages = get_free_page_count(database);
1574
int total_pages = get_database_page_count(database);
@@ -1692,6 +1694,7 @@ done:
1694
void run_metadata_cleanup(struct meta_config_s *config)
1695
{
1696
static time_t next_context_list_cleanup = 0;
1697
+ static time_t next_vacuum_run = 0;
1698
1699
time_t now = now_realtime_sec();
1700
@@ -1723,7 +1726,7 @@ void run_metadata_cleanup(struct meta_config_s *config)
1726
if (unlikely(SHUTDOWN_REQUESTED(config)))
1727
return;
1728
1726
- vacuum_database(db_meta, "METADATA", DATABASE_FREE_PAGES_THRESHOLD_PC, DATABASE_FREE_PAGES_VACUUM_PC);
1729
+ vacuum_database(db_meta, "METADATA", DATABASE_FREE_PAGES_THRESHOLD_PC, DATABASE_FREE_PAGES_VACUUM_PC, &next_vacuum_run);
1730
1731
(void) sqlite3_wal_checkpoint(db_meta, NULL);
1732
}
@@ -2557,13 +2560,13 @@ static void metadata_event_loop(void *arg)
2560
pending_uuid_deletion = callocz(1, sizeof(*pending_uuid_deletion));
2561
2562
Pvalue = JudyLIns(&pending_uuid_deletion->JudyL, ++pending_uuid_deletion->count, PJE0);
2560
- if (Pvalue != PJERR)
2561
- *Pvalue = uuid;
2562
- else {
2563
+ if (unlikely(Pvalue == PJERR)) {
2564
// Failure in Judy, attempt to continue running anyway
2565
// ignore uuid, global cleanup will take care of it
2566
freez(uuid);
2567
}
2568
+ else
2569
+ *Pvalue = uuid;
2570
break;
2571
case METADATA_STORE_CLAIM_ID:
2572
store_claim_id((nd_uuid_t *)cmd.param[0], (nd_uuid_t *)cmd.param[1]);
@@ -2577,14 +2580,14 @@ static void metadata_event_loop(void *arg)
2580
2581
struct host_ctx_cleanup_s *ctx_cleanup = (struct host_ctx_cleanup_s *)cmd.param[0];
2582
Pvalue = JudyLIns(&pending_ctx_cleanup_list->JudyL, ++pending_ctx_cleanup_list->count, PJE0);
2580
- if (Pvalue && Pvalue != PJERR)
2581
- *Pvalue = ctx_cleanup;
2582
- else {
2583
+ if (unlikely(Pvalue == PJERR)) {
2584
// Failure in Judy, attempt to continue running anyway
2585
// Cleanup structure
2586
string_freez(ctx_cleanup->context);
2587
freez(ctx_cleanup);
2588
}
2589
+ else
2590
+ *Pvalue = ctx_cleanup;
2591
break;
2592
case METADATA_STORE:
2593
if (config->metadata_running || unittest_running)
@@ -2634,17 +2637,19 @@ static void metadata_event_loop(void *arg)
2637
pending_alert_list = callocz(1, sizeof(*pending_alert_list));
2638
2639
Pvalue = JudyLIns(&pending_alert_list->JudyL, ++pending_alert_list->count, PJE0);
2637
- if (!Pvalue || Pvalue == PJERR)
2638
- fatal("METASYNC: Corrupted pending_alert_list Judy array");
2640
+ if (unlikely(Pvalue == PJERR))
2641
+ fatal("METASYNC: Failed to insert into pending_alert_list Judy array");
2642
*Pvalue = (void *)host;
2643
2644
Pvalue = JudyLIns(&pending_alert_list->JudyL, ++pending_alert_list->count, PJE0);
2642
- if (!Pvalue || Pvalue == PJERR)
2643
- fatal("METASYNC: Corrupted pending_alert_list Judy array");
2645
+ if (unlikely(Pvalue == PJERR))
2646
+ fatal("METASYNC: Failed to insert into pending_alert_list Judy array");
2647
*Pvalue = (void *)ae;
2648
break;
2649
case METADATA_DEL_HOST_AE:
2647
- (void)JudyLIns(&config->ae_DelJudyL, (Word_t)(void *)cmd.param[0], PJE0);
2650
+ Pvalue = JudyLIns(&config->ae_DelJudyL, (Word_t)(void *)cmd.param[0], PJE0);
2651
+ if (Pvalue == PJERR)
2652
+ nd_log_daemon(NDLP_ERR, "METADATA: Failed to track alert entry for deletion");
2653
break;
2654
case METADATA_EXECUTE_STORE_STATEMENT:
2655
stmt = (sqlite3_stmt *)cmd.param[0];
@@ -2652,12 +2657,12 @@ static void metadata_event_loop(void *arg)
2657
pending_sql_statement = callocz(1, sizeof(*pending_sql_statement));
2658
2659
Pvalue = JudyLIns(&pending_sql_statement->JudyL, ++pending_sql_statement->count, PJE0);
2655
- if (Pvalue && Pvalue != PJERR)
2656
- *Pvalue = (void *)stmt;
2657
- else {
2660
+ if (unlikely(Pvalue == PJERR)) {
2661
// Fallback execute immediately
2662
execute_statement(stmt, false);
2663
}
2664
+ else
2665
+ *Pvalue = (void *)stmt;
2666
break;
2667
case METADATA_SYNC_SHUTDOWN:
2668
__atomic_store_n(&config->shutdown_requested, true, __ATOMIC_RELAXED);
src/database/sqlite/sqlite_metadata.h
+1
-1
@@ -33,7 +33,7 @@ void metaqueue_ml_load_models(RRDDIM *rd);
33
void detect_machine_guid_change(nd_uuid_t *host_uuid);
34
bool metadata_queue_load_host_context();
35
void reset_host_context_load_flag();
36
-void vacuum_database(sqlite3 *database, const char *db_alias, int threshold, int vacuum_pc);
36
+void vacuum_database(sqlite3 *database, const char *db_alias, int threshold, int vacuum_pc, time_t *next_run);
37
38
int sql_metadata_cache_stats(int op);
39
src/ml/ml.cc
+2
-1
@@ -1042,6 +1042,7 @@ void ml_detect_main(void *arg)
1042
}
1043
1044
static void ml_flush_pending_models(ml_worker_t *worker) {
1045
+ static time_t next_vacuum_run = 0;
1046
int op_no = 1;
1047
1048
// begin transaction
@@ -1089,7 +1090,7 @@ static void ml_flush_pending_models(ml_worker_t *worker) {
1090
worker->num_models_to_prune += worker->pending_model_info.size();
1091
}
1092
1092
- vacuum_database(ml_db, "ML", 0, 0);
1093
+ vacuum_database(ml_db, "ML", 0, 0, &next_vacuum_run);
1094
1095
worker->pending_model_info.clear();
1096
}