151
152
#define SQL_DELETE_HOST_LABELS "DELETE FROM host_label WHERE host_id = @uuid"
153
154
-#define STORE_HOST_LABEL \
154
+#define SQL_STORE_HOST_LABEL \
155
"INSERT INTO host_label (host_id, source_type, label_key, label_value, date_created) VALUES "
156
157
-#define STORE_CHART_LABEL \
157
+#define SQL_STORE_HOST_LABEL_CONFLICT " ON CONFLICT (host_id, label_key) " \
158
+ "DO UPDATE SET source_type = excluded.source_type, label_value = excluded.label_value, date_created = UNIXEPOCH()"
159
+
160
+#define SQL_STORE_CHART_LABEL \
161
"INSERT INTO chart_label (chart_id, source_type, label_key, label_value, date_created) VALUES "
162
160
-#define STORE_HOST_OR_CHART_LABEL_VALUE "(u2h('%s'), %d,'%s','%s', unixepoch())"
163
+#define SQL_STORE_CHART_LABEL_CONFLICT " ON CONFLICT (chart_id, label_key) " \
164
+ "DO UPDATE SET source_type = excluded.source_type, label_value = excluded.label_value, date_created = UNIXEPOCH()"
165
166
#define DELETE_DIMENSION_UUID "DELETE FROM dimension WHERE dim_id = @uuid"
167
820
821
// Metadata functions
822
819
-struct query_build {
820
- BUFFER *sql;
821
- int count;
822
- char uuid_str[UUID_STR_LEN];
823
+// Label storage types
824
+typedef enum {
825
+ STORE_HOST_LABELS,
826
+ STORE_CHART_LABELS,
827
+} label_store_type_t;
828
+
829
+// Structure to hold a single label entry for collection
830
+struct label_entry {
831
+ char *name;
832
+ char *value;
833
+ RRDLABEL_SRC ls;
834
};
835
825
-static int host_label_store_to_sql_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) {
826
- struct query_build *lb = data;
827
- if (unlikely(!lb->count))
828
- buffer_sprintf(lb->sql, STORE_HOST_LABEL);
829
- else
830
- buffer_strcat(lb->sql, ", ");
831
- buffer_sprintf(lb->sql, STORE_HOST_OR_CHART_LABEL_VALUE, lb->uuid_str, (int) (ls & ~(RRDLABEL_FLAG_INTERNAL)), name, value);
832
- lb->count++;
836
+// Context for label collection callback (no I/O, just data collection)
837
+struct label_collect_ctx {
838
+ struct label_entry *entries;
839
+ size_t count;
840
+ size_t capacity;
841
+};
842
+
843
+#define LABEL_COLLECT_INITIAL_CAPACITY 64
844
+
845
+// Max labels per batch: SQLite default SQLITE_MAX_VARIABLE_NUMBER is 32766 in the version we are using
846
+#define LABEL_BATCH_SIZE (1024)
847
+
848
+// Callback to collect labels into an array (no SQLite I/O while spinlock is held)
849
+static int collect_label_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data)
850
+{
851
+ struct label_collect_ctx *ctx = data;
852
+
853
+ if (unlikely(!name || !value))
854
+ return 1;
855
+
856
+ // Grow array if needed
857
+ if (ctx->count >= ctx->capacity) {
858
+ ctx->capacity *= 2;
859
+ ctx->entries = reallocz(ctx->entries, ctx->capacity * sizeof(*ctx->entries));
860
+ }
861
+
862
+ // Copy label data
863
+ ctx->entries[ctx->count].name = strdupz(name);
864
+ ctx->entries[ctx->count].value = strdupz(value);
865
+ ctx->entries[ctx->count].ls = ls;
866
+ ctx->count++;
867
+
868
return 1;
869
}
870
836
-static int chart_label_store_to_sql_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) {
837
- struct query_build *lb = data;
838
- if (unlikely(!lb->count))
839
- buffer_sprintf(lb->sql, STORE_CHART_LABEL);
871
+// Execute a batch of label inserts using a single multi-row INSERT statement
872
+// Returns number of errors
873
+static int store_label_batch(
874
+ nd_uuid_t *uuid,
875
+ struct label_entry *entries,
876
+ size_t count,
877
+ label_store_type_t type,
878
+ BUFFER *sql)
879
+{
880
+ if (count == 0)
881
+ return 0;
882
+
883
+ // Build SQL: INSERT INTO table (...) VALUES (?,?,?,?,UNIXEPOCH()), (?,?,?,?,UNIXEPOCH()), ... ON CONFLICT ...
884
+ buffer_flush(sql);
885
+
886
+ if (type == STORE_HOST_LABELS)
887
+ buffer_strcat(sql, SQL_STORE_HOST_LABEL);
888
else
841
- buffer_strcat(lb->sql, ", ");
842
- buffer_sprintf(lb->sql, STORE_HOST_OR_CHART_LABEL_VALUE, lb->uuid_str, (int) (ls & ~(RRDLABEL_FLAG_INTERNAL)), name, value);
843
- lb->count++;
844
- return 1;
889
+ buffer_strcat(sql, SQL_STORE_CHART_LABEL);
890
+
891
+ for (size_t i = 0; i < count; i++) {
892
+ if (i > 0)
893
+ buffer_strcat(sql, ", ");
894
+ buffer_strcat(sql, "(?, ?, ?, ?, UNIXEPOCH())");
895
+ }
896
+
897
+ if (type == STORE_HOST_LABELS)
898
+ buffer_strcat(sql, SQL_STORE_HOST_LABEL_CONFLICT);
899
+ else
900
+ buffer_strcat(sql, SQL_STORE_CHART_LABEL_CONFLICT);
901
+
902
+ sqlite3_stmt *stmt = NULL;
903
+
904
+ if (!PREPARE_STATEMENT(db_meta, buffer_tostring(sql), &stmt))
905
+ return 1;
906
+
907
+ int errors = 0;
908
+
909
+ // Bind all parameters: 4 per label (uuid, source, key, value)
910
+ int param = 0;
911
+ for (size_t i = 0; i < count; i++) {
912
+ SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_blob(stmt, ++param, uuid, sizeof(*uuid), SQLITE_STATIC));
913
+ SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int(stmt, ++param, (int)(entries[i].ls & ~(RRDLABEL_FLAG_INTERNAL))));
914
+ SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_text(stmt, ++param, entries[i].name, -1, SQLITE_STATIC));
915
+ SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_text(stmt, ++param, entries[i].value, -1, SQLITE_STATIC));
916
+ }
917
+ param = 0;
918
+
919
+ int rc = sqlite3_step_monitored(stmt);
920
+ if (unlikely(rc != SQLITE_DONE)) {
921
+ errors = 1;
922
+ error_report("Failed to store label batch, rc = %d", rc);
923
+ }
924
+
925
+bind_fail:
926
+ if (param)
927
+ errors = 1;
928
+ REPORT_BIND_FAIL(stmt, param);
929
+ SQLITE_FINALIZE(stmt);
930
+
931
+ return errors;
932
+}
933
+
934
+// Store labels for a host or chart using batched prepared statements
935
+// Two-phase approach: collect labels first (while spinlock held), then execute SQLite (after spinlock released)
936
+static int store_labels(nd_uuid_t *uuid, RRDLABELS *labels, label_store_type_t type, BUFFER *work_buffer)
937
+{
938
+ if (unlikely(!uuid || !labels))
939
+ return 0;
940
+
941
+ // Phase 1: Collect labels while spinlock is held (fast, no I/O)
942
+ struct label_collect_ctx collect_ctx = {
943
+ .entries = mallocz(LABEL_COLLECT_INITIAL_CAPACITY * sizeof(*collect_ctx.entries)),
944
+ .count = 0,
945
+ .capacity = LABEL_COLLECT_INITIAL_CAPACITY,
946
+ };
947
+
948
+ rrdlabels_walkthrough_read(labels, collect_label_callback, &collect_ctx);
949
+ // Spinlock is now released
950
+
951
+ // Phase 2: Execute SQLite operations in batches (no spinlock held)
952
+ int errors = 0;
953
+
954
+ if (collect_ctx.count > 0) {
955
+ bool free_buffer = false;
956
+ if (!work_buffer) {
957
+ work_buffer = buffer_create(256 + collect_ctx.count * 30, NULL);
958
+ free_buffer = true;
959
+ }
960
+
961
+ size_t remaining = collect_ctx.count;
962
+ size_t offset = 0;
963
+
964
+ while (remaining > 0) {
965
+ size_t batch_count = (remaining > LABEL_BATCH_SIZE) ? LABEL_BATCH_SIZE : remaining;
966
+ errors += store_label_batch(uuid, &collect_ctx.entries[offset], batch_count, type, work_buffer);
967
+ offset += batch_count;
968
+ remaining -= batch_count;
969
+ }
970
+
971
+ if (free_buffer)
972
+ buffer_free(work_buffer);
973
+ }
974
+
975
+ // Cleanup collected labels
976
+ for (size_t i = 0; i < collect_ctx.count; i++) {
977
+ freez(collect_ctx.entries[i].name);
978
+ freez(collect_ctx.entries[i].value);
979
+ }
980
+ freez(collect_ctx.entries);
981
+
982
+ return errors ? 1 : 0;
983
}
984
985
static int check_and_update_chart_labels(RRDSET *st, BUFFER *work_buffer)
990
if (new_version == old_version)
991
return 0;
992
855
- struct query_build tmp = {.sql = work_buffer, .count = 0};
856
- uuid_unparse_lower(st->chart_uuid, tmp.uuid_str);
857
- rrdlabels_walkthrough_read(st->rrdlabels, chart_label_store_to_sql_callback, &tmp);
858
- buffer_strcat(work_buffer, " ON CONFLICT (chart_id, label_key) DO UPDATE SET source_type = excluded.source_type, label_value=excluded.label_value, date_created=UNIXEPOCH()");
859
- int rc = db_execute(db_meta, buffer_tostring(work_buffer), NULL);
993
+ int rc = store_labels(&st->chart_uuid, st->rrdlabels, STORE_CHART_LABELS, work_buffer);
994
if (likely(!rc))
995
st->rrdlabels_last_saved_version = new_version;
996
2162
}
2163
#endif
2164
2031
-static void metadata_scan_host(struct meta_config_s *config, RRDHOST *host, BUFFER *work_buffer, bool is_worker)
2165
+static void metadata_scan_host(struct meta_config_s *config, RRDHOST *host, bool is_worker, BUFFER *work_buffer)
2166
{
2167
static bool skip_models = false;
2168
RRDSET *st;
2186
2187
rrdset_flag_clear(st, RRDSET_FLAG_METADATA_UPDATE);
2188
2055
- buffer_flush(work_buffer);
2056
-
2189
if (is_worker)
2190
worker_is_busy(UV_EVENT_STORE_CHART);
2191
2459
{
2460
rrdhost_flag_clear(host, RRDHOST_FLAG_METADATA_LABELS);
2461
2462
+ // Delete existing labels first to handle label removal
2463
int rc = exec_statement_with_uuid(SQL_DELETE_HOST_LABELS, &host->host_id.uuid);
2464
if (unlikely(rc)) {
2465
error_report("METADATA: 'host:%s': failed to delete old host labels", rrdhost_hostname(host));
2467
return;
2468
}
2469
2337
- buffer_flush(work_buffer);
2338
-
2339
- struct query_build tmp = {.sql = work_buffer, .count = 0};
2340
- uuid_unparse_lower(host->host_id.uuid, tmp.uuid_str);
2341
- rrdlabels_walkthrough_read(host->rrdlabels, host_label_store_to_sql_callback, &tmp);
2342
- buffer_strcat(
2343
- work_buffer,
2344
- " ON CONFLICT (host_id, label_key) DO UPDATE SET source_type = excluded.source_type, label_value=excluded.label_value, date_created=UNIXEPOCH()");
2345
- rc = db_execute(db_meta, buffer_tostring(work_buffer), NULL);
2346
-
2470
+ // Store all current labels using prepared statements
2471
+ rc = store_labels(&host->host_id.uuid, host->rrdlabels, STORE_HOST_LABELS, work_buffer);
2472
if (unlikely(rc)) {
2348
- error_report("METADATA: 'host:%s': failed to update metadata host labels", rrdhost_hostname(host));
2473
+ error_report("METADATA: 'host:%s': failed to store host labels", rrdhost_hostname(host));
2474
rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE);
2475
}
2476
}
2489
rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_CLAIMID | RRDHOST_FLAG_METADATA_UPDATE);
2490
}
2491
2367
-void store_host_info_and_metadata(RRDHOST *host, BUFFER *work_buffer)
2492
+static void store_host_info_and_metadata_with_buffer(RRDHOST *host, BUFFER *work_buffer)
2493
{
2494
// Store labels (if needed)
2495
if (unlikely(rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_LABELS)))
2504
store_host_and_system_info(host);
2505
}
2506
2382
-static void store_hosts_metadata(struct meta_config_s *config, BUFFER *work_buffer, bool is_worker)
2507
+// Public API - creates buffer internally if needed
2508
+void store_host_info_and_metadata(RRDHOST *host)
2509
+{
2510
+ store_host_info_and_metadata_with_buffer(host, NULL);
2511
+}
2512
+
2513
+static void store_hosts_metadata(struct meta_config_s *config, bool is_worker)
2514
{
2515
RRDHOST *host;
2516
size_t host_count = 0;
2524
host_count = 1; // avoid division by zero
2525
}
2526
2527
+ // Reusable buffer for building SQL statements
2528
+ BUFFER *work_buffer = buffer_create(1024, NULL);
2529
+
2530
size_t count = 0;
2531
dfe_start_reentrant(rrdhost_root_index, host)
2532
{
2543
worker_is_busy(UV_EVENT_STORE_HOST);
2544
2545
// store labels, claim_id, host and system info (if needed)
2412
- store_host_info_and_metadata(host, work_buffer);
2546
+ store_host_info_and_metadata_with_buffer(host, work_buffer);
2547
2548
if (is_worker)
2549
worker_is_idle();
2550
2417
- metadata_scan_host(config, host, work_buffer, is_worker);
2551
+ metadata_scan_host(config, host, is_worker, work_buffer);
2552
2553
if (!is_worker)
2554
nd_log_daemon(NDLP_INFO, "METADATA: Progress of metadata storage: %6.2f%% completed", (100.0 * count / host_count));
2555
}
2556
dfe_done(host);
2557
2558
+ buffer_free(work_buffer);
2559
+
2560
if (!is_worker) {
2561
COMPUTE_DURATION(report_duration, "us", started_ut, now_monotonic_usec());
2562
nd_log_daemon(
2585
worker_data_t *worker = req->data;
2586
struct meta_config_s *config = worker->config;
2587
2452
- BUFFER *work_buffer = worker->work_buffer;
2588
usec_t all_started_ut = now_monotonic_usec();
2589
2590
store_sql_statements((struct judy_list_t *)worker->pending_sql_statement, true, false);
2596
2597
worker_is_busy(UV_EVENT_METADATA_STORE);
2598
2464
- store_hosts_metadata(config, work_buffer, true);
2599
+ store_hosts_metadata(config, true);
2600
2601
COMPUTE_DURATION(report_duration, "us", all_started_ut, now_monotonic_usec());
2602
nd_log_daemon(NDLP_DEBUG, "Checking all hosts completed in %s", report_duration);
2647
nd_log(NDLS_DAEMON, NDLP_DEBUG, "Starting metadata sync thread");
2648
config->metadata_check_after = now_realtime_sec() + METADATA_HOST_CHECK_FIRST_CHECK;
2649
2515
- BUFFER *work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
2650
worker_data_t *worker;
2651
Pvoid_t *Pvalue;
2652
struct judy_list_t *pending_alert_list = NULL;
2732
worker->pending_ctx_cleanup_list = pending_ctx_cleanup_list;
2733
worker->pending_uuid_deletion = pending_uuid_deletion;
2734
worker->pending_sql_statement = pending_sql_statement;
2601
-
2602
- worker->work_buffer = work_buffer;
2735
pending_alert_list = NULL;
2736
pending_ctx_cleanup_list = NULL;
2737
pending_uuid_deletion = NULL;
2866
freez(pending_uuid_deletion);
2867
}
2868
2737
- buffer_free(work_buffer);
2869
release_cmd_pool(&config->cmd_pool);
2870
worker_unregister();
2871
service_exits();