2
3
#include "internal.h"
4
5
+static struct {
6
+ bool enabled;
7
+ size_t db_rotations;
8
+ size_t instances_count;
9
+ size_t active_vs_archived_percentage;
10
+} extreme_cardinality = {
11
+ .enabled = true, // this value is ignored - there is a dynamic condition to enable it
12
+ .db_rotations = 0,
13
+ .instances_count = 1000,
14
+ .active_vs_archived_percentage = 50,
15
+};
16
+
17
static uint64_t rrdcontext_get_next_version(RRDCONTEXT *rc);
18
19
static bool check_if_cloud_version_changed_unsafe(RRDCONTEXT *rc, bool sending __maybe_unused);
21
static void rrdcontext_delete_from_sql_unsafe(RRDCONTEXT *rc);
22
23
static void rrdcontext_dequeue_from_post_processing(RRDCONTEXT *rc);
12
-static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs);
24
+static bool rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs);
25
26
static void rrdcontext_garbage_collect_for_all_hosts(void);
27
112
}
113
114
void rrdcontext_recalculate_context_retention(RRDCONTEXT *rc, RRD_FLAGS reason, bool worker_jobs) {
103
- rrdcontext_post_process_updates(rc, true, reason, worker_jobs);
115
+ bool forcefully_removed_instances = false;
116
+ do {
117
+ forcefully_removed_instances = rrdcontext_post_process_updates(rc, true, reason, worker_jobs);
118
+ } while(forcefully_removed_instances);
119
}
120
121
void rrdcontext_recalculate_host_retention(RRDHOST *host, RRD_FLAGS reason, bool worker_jobs) {
152
// ----------------------------------------------------------------------------
153
// garbage collector
154
140
-void get_metric_retention_by_id(RRDHOST *host, UUIDMAP_ID id, time_t *min_first_time_t, time_t *max_last_time_t) {
155
+void get_metric_retention_by_id(RRDHOST *host, UUIDMAP_ID id, time_t *min_first_time_t, time_t *max_last_time_t, bool *tier0_retention) {
156
*min_first_time_t = LONG_MAX;
157
*max_last_time_t = 0;
158
167
if (last_time_t > *max_last_time_t)
168
*max_last_time_t = last_time_t;
169
}
170
+
171
+ if(tier == 0 && tier0_retention)
172
+ *tier0_retention = first_time_t || last_time_t;
173
}
174
}
175
179
if(rm->rrddim) {
180
min_first_time_t = rrddim_first_entry_s(rm->rrddim);
181
max_last_time_t = rrddim_last_entry_s(rm->rrddim);
182
+ rrd_flag_clear(rm, RRD_FLAG_NO_TIER0_RETENTION);
183
}
165
- else
166
- get_metric_retention_by_id(rm->ri->rc->rrdhost, rm->uuid, &min_first_time_t, &max_last_time_t);
184
+ else {
185
+ bool tier0_retention;
186
+ get_metric_retention_by_id(rm->ri->rc->rrdhost, rm->uuid, &min_first_time_t, &max_last_time_t, &tier0_retention);
187
168
- if((min_first_time_t == LONG_MAX || min_first_time_t == 0) && max_last_time_t == 0)
169
- return false;
188
+ if(tier0_retention)
189
+ rrd_flag_clear(rm, RRD_FLAG_NO_TIER0_RETENTION);
190
+ else
191
+ rrd_flag_set(rm, RRD_FLAG_NO_TIER0_RETENTION);
192
+ }
193
194
if(min_first_time_t == LONG_MAX)
195
min_first_time_t = 0;
196
197
if(min_first_time_t > max_last_time_t) {
198
internal_error(true, "RRDMETRIC: retention of '%s' is flipped, first_time_t = %ld, last_time_t = %ld", string2str(rm->id), min_first_time_t, max_last_time_t);
176
- time_t tmp = min_first_time_t;
177
- min_first_time_t = max_last_time_t;
178
- max_last_time_t = tmp;
199
+ SWAP(min_first_time_t, max_last_time_t);
200
}
201
202
// check if retention changed
423
worker_is_busy(WORKER_JOB_PP_INSTANCE);
424
425
time_t min_first_time_t = LONG_MAX, max_last_time_t = 0;
405
- size_t metrics_active = 0, metrics_deleted = 0;
426
+ size_t metrics_active = 0, metrics_deleted = 0, metrics_no_tier0 = 0;
427
bool live_retention = true, currently_collected = false;
428
if(dictionary_entries(ri->rrdmetrics) > 0) {
429
RRDMETRIC *rm;
439
if(unlikely(!rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION)))
440
live_retention = false;
441
442
+ if(unlikely(rrd_flag_check(rm, RRD_FLAG_NO_TIER0_RETENTION)))
443
+ metrics_no_tier0++;
444
+
445
if (unlikely((rrdmetric_should_be_deleted(rm)))) {
446
metrics_deleted++;
447
continue;
461
dfe_done(rm);
462
}
463
464
+ if(metrics_no_tier0 && metrics_no_tier0 == metrics_active)
465
+ rrd_flag_set(ri, RRD_FLAG_NO_TIER0_RETENTION);
466
+ else
467
+ rrd_flag_clear(ri, RRD_FLAG_NO_TIER0_RETENTION);
468
+
469
if(unlikely(live_retention && !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)))
470
rrd_flag_set(ri, RRD_FLAG_LIVE_RETENTION);
471
else if(unlikely(!live_retention && rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)))
529
rrd_flag_unset_updated(ri);
530
}
531
503
-static void rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs) {
532
+static bool rrdinstance_forcefully_clear_retention(RRDCONTEXT *rc, size_t count, const char *descr) {
533
+ if(!count) return false;
534
+
535
+ RRDHOST *host = rc->rrdhost;
536
+
537
+ time_t from_s = LONG_MAX;
538
+ time_t to_s = 0;
539
+
540
+ size_t instances_deleted = 0;
541
+ size_t metrics_deleted = 0;
542
+ RRDINSTANCE *ri;
543
+ dfe_start_read(rc->rrdinstances, ri) {
544
+ if(!rrd_flag_check(ri, RRD_FLAG_NO_TIER0_RETENTION) || rrd_flag_is_collected(ri) || ri->rrdset)
545
+ continue;
546
+
547
+ size_t metrics_cleared = 0;
548
+ RRDMETRIC *rm;
549
+ dfe_start_read(ri->rrdmetrics, rm) {
550
+ if(!rrd_flag_check(rm, RRD_FLAG_NO_TIER0_RETENTION) || rrd_flag_is_collected(rm) || rm->rrddim)
551
+ continue;
552
+
553
+ rrdmetric_update_retention(rm);
554
+
555
+ if(rm->first_time_s < from_s)
556
+ from_s = rm->first_time_s;
557
+
558
+ if(rm->last_time_s > to_s)
559
+ to_s = rm->last_time_s;
560
+
561
+ for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
562
+ STORAGE_ENGINE *eng = host->db[tier].eng;
563
+ eng->api.metric_retention_delete_by_id(host->db[tier].si, rm->uuid);
564
+ }
565
+
566
+ metrics_cleared++;
567
+ metrics_deleted++;
568
+ rrdmetric_update_retention(rm);
569
+ rrdmetric_trigger_updates(rm, __FUNCTION__ );
570
+ }
571
+ dfe_done(rm);
572
+
573
+ if(metrics_cleared) {
574
+ rrdinstance_trigger_updates(ri, __FUNCTION__ );
575
+ instances_deleted++;
576
+
577
+ if(--count == 0)
578
+ break;
579
+ }
580
+ }
581
+ dfe_done(ri);
582
+
583
+ if(metrics_deleted) {
584
+ char from_txt[128], to_txt[128];
585
+
586
+ if(!from_s || from_s == LONG_MAX)
587
+ snprintfz(from_txt, sizeof(from_txt), "%s", "NONE");
588
+ else
589
+ rfc3339_datetime_ut(from_txt, sizeof(from_txt), from_s * USEC_PER_SEC, 0, true);
590
+
591
+ if(!to_s)
592
+ snprintfz(to_txt, sizeof(to_txt), "%s", "NONE");
593
+ else
594
+ rfc3339_datetime_ut(to_txt, sizeof(to_txt), to_s * USEC_PER_SEC, 0, true);
595
+
596
+ ND_LOG_STACK lgs[] = {
597
+ ND_LOG_FIELD_TXT(NDF_MODULE, "extreme cardinality protection"),
598
+ ND_LOG_FIELD_STR(NDF_NIDL_NODE, rc->rrdhost->hostname),
599
+ ND_LOG_FIELD_STR(NDF_NIDL_CONTEXT, rc->id),
600
+ ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &extreme_cardinality_msgid),
601
+ ND_LOG_FIELD_END(),
602
+ };
603
+ ND_LOG_STACK_PUSH(lgs);
604
+
605
+ nd_log(NDLS_DAEMON, NDLP_NOTICE,
606
+ "EXTREME CARDINALITY PROTECTION: host '%s', context '%s', %s: "
607
+ "forcefully cleared the retention of %zu metrics and %zu instances, "
608
+ "having non-tier0 retention from %s to %s.",
609
+ rrdhost_hostname(rc->rrdhost),
610
+ string2str(rc->id),
611
+ descr,
612
+ metrics_deleted, instances_deleted,
613
+ from_txt, to_txt);
614
+
615
+ return true;
616
+ }
617
+
618
+ return false;
619
+}
620
+
621
+static bool rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs) {
622
+ bool ret = false;
623
+
624
if(reason != RRD_FLAG_NONE)
625
rrd_flag_set_updated(rc, reason);
626
631
size_t min_priority_not_collected = LONG_MAX;
632
size_t min_priority = LONG_MAX;
633
time_t min_first_time_t = LONG_MAX, max_last_time_t = 0;
514
- size_t instances_active = 0, instances_deleted = 0;
634
+ size_t instances_active = 0, instances_deleted = 0, instances_no_tier0 = 0;
635
bool live_retention = true, currently_collected = false, hidden = true;
636
if(dictionary_entries(rc->rrdinstances) > 0) {
637
RRDINSTANCE *ri;
655
continue;
656
}
657
658
+ if(unlikely(rrd_flag_check(ri, RRD_FLAG_NO_TIER0_RETENTION)))
659
+ instances_no_tier0++;
660
+
661
bool ri_collected = rrd_flag_is_collected(ri);
662
663
if(ri_collected && !rrd_flag_check(ri, RRD_FLAG_MERGED_COLLECTED_RI_TO_RC)) {
694
}
695
dfe_done(ri);
696
697
+ if(extreme_cardinality.enabled &&
698
+ extreme_cardinality.db_rotations &&
699
+ instances_no_tier0 >= extreme_cardinality.instances_count) {
700
+ size_t percent = (100 * instances_no_tier0 / instances_active);
701
+ if(percent >= extreme_cardinality.active_vs_archived_percentage) {
702
+ size_t to_keep = extreme_cardinality.active_vs_archived_percentage * instances_active / 100;
703
+ to_keep = MAX(to_keep, extreme_cardinality.instances_count);
704
+ size_t to_remove = instances_no_tier0 > to_keep ? instances_no_tier0 - to_keep : 0;
705
+
706
+ if(to_remove) {
707
+ char buf[256];
708
+ snprintfz(buf, sizeof(buf),
709
+ "total active instances %zu, not in tier0 %zu, ephemerality %zu%%",
710
+ instances_active, instances_no_tier0, percent);
711
+ ret = rrdinstance_forcefully_clear_retention(rc, to_remove, buf);
712
+ }
713
+ }
714
+ }
715
+
716
if(min_priority_collected != LONG_MAX)
717
// use the collected priority
718
min_priority = min_priority_collected;
811
812
rrd_flag_unset_updated(rc);
813
rrdcontext_unlock(rc);
814
+
815
+ return ret;
816
}
817
818
void rrdcontext_queue_for_post_processing(RRDCONTEXT *rc, const char *function __maybe_unused, RRD_FLAGS flags __maybe_unused) {
1160
heartbeat_t hb;
1161
heartbeat_init(&hb, RRDCONTEXT_WORKER_THREAD_HEARTBEAT_USEC);
1162
1163
+ extreme_cardinality.enabled = inicfg_get_boolean(
1164
+ &netdata_config, CONFIG_SECTION_DB, "extreme cardinality protection",
1165
+ nd_profile.storage_tiers > 1 && default_rrd_memory_mode == RRD_DB_MODE_DBENGINE
1166
+ );
1167
+
1168
+ extreme_cardinality.instances_count = inicfg_get_number_range(
1169
+ &netdata_config, CONFIG_SECTION_DB, "extreme cardinality keep instances",
1170
+ (long long)extreme_cardinality.instances_count, 1, 1000000);
1171
+
1172
+ extreme_cardinality.active_vs_archived_percentage = inicfg_get_number_range(
1173
+ &netdata_config, CONFIG_SECTION_DB, "extreme cardinality min ephemerality",
1174
+ (long long)extreme_cardinality.active_vs_archived_percentage, 0, 100);
1175
+
1176
while (service_running(SERVICE_CONTEXT)) {
1177
worker_is_idle();
1178
heartbeat_next(&hb);
1182
usec_t now_ut = now_realtime_usec();
1183
1184
if(rrdcontext_next_db_rotation_ut && now_ut > rrdcontext_next_db_rotation_ut) {
1185
+ extreme_cardinality.db_rotations++;
1186
rrdcontext_recalculate_retention_all_hosts();
1187
rrdcontext_garbage_collect_for_all_hosts();
1188
rrdcontext_next_db_rotation_ut = 0;