@cryptotaxi247 / netdata-1 / commits / b7e7983d2

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 }