master
c 1,149 lines 44.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "rrdcontext-internal.h"
4
5 static struct {
6 bool enabled;
7 size_t db_rotations; // count of actual dbengine rotations only
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 void rrdcontext_count_db_rotation(void) {
18 __atomic_add_fetch(&extreme_cardinality.db_rotations, 1, __ATOMIC_RELAXED);
19 }
20
21 static uint64_t rrdcontext_get_next_version(RRDCONTEXT *rc);
22
23 static void rrdcontext_garbage_collect_for_all_hosts(void);
24
25 extern usec_t rrdcontext_next_db_rotation_ut;
26 extern size_t rrdcontext_full_gc_rerun_requested;
27
28 // ----------------------------------------------------------------------------
29 // version hash calculation
30
31 uint64_t rrdcontext_version_hash_with_callback(
32 RRDHOST *host,
33 void (*callback)(RRDCONTEXT *, bool, void *),
34 bool snapshot,
35 void *bundle) {
36
37 if(unlikely(!host || !host->rrdctx.contexts)) return 0;
38
39 RRDCONTEXT *rc;
40 uint64_t hash = 0;
41
42 // loop through all contexts of the host
43 dfe_start_read(host->rrdctx.contexts, rc) {
44
45 rrdcontext_lock(rc);
46
47 if(unlikely(rrd_flag_check(rc, RRD_FLAG_HIDDEN))) {
48 rrdcontext_unlock(rc);
49 continue;
50 }
51
52 if(unlikely(callback))
53 callback(rc, snapshot, bundle);
54
55 // skip any deleted contexts
56 if(unlikely(rrd_flag_is_deleted(rc))) {
57 rrdcontext_unlock(rc);
58 continue;
59 }
60
61 // we use rc->hub.* which has the latest
62 // metadata we have sent to the hub
63
64 // if a context is currently queued, rc->hub.* does NOT
65 // reflect the queued changes. rc->hub.* is updated with
66 // their metadata, after messages are dispatched to hub.
67
68 // when the context is being collected,
69 // rc->hub.last_time_t is already zero
70
71 hash += rc->hub.version + rc->hub.last_time_s - rc->hub.first_time_s;
72
73 rrdcontext_unlock(rc);
74
75 }
76 dfe_done(rc);
77
78 return hash;
79 }
80
81 // ----------------------------------------------------------------------------
82 // retention recalculation
83
84 static void rrdhost_update_cached_retention(RRDHOST *host, time_t first_time_s, time_t last_time_s, bool global) {
85 if(unlikely(!host))
86 return;
87
88 spinlock_lock(&host->retention.spinlock);
89
90 time_t old_first_time_s = host->retention.first_time_s;
91
92 if(global) {
93 host->retention.first_time_s = first_time_s;
94 host->retention.last_time_s = last_time_s;
95 }
96 else {
97 if(!host->retention.first_time_s || (first_time_s && first_time_s < host->retention.first_time_s))
98 host->retention.first_time_s = first_time_s;
99
100 if(!host->retention.last_time_s || last_time_s > host->retention.last_time_s)
101 host->retention.last_time_s = last_time_s;
102 }
103
104 bool stream_path_update_required = old_first_time_s != host->retention.first_time_s;
105
106 spinlock_unlock(&host->retention.spinlock);
107
108 if(stream_path_update_required)
109 stream_path_retention_updated(host);
110 }
111
112 void rrdcontext_recalculate_context_retention(RRDCONTEXT *rc, RRD_FLAGS reason, bool worker_jobs) {
113 bool forcefully_removed_instances = false;
114 do {
115 forcefully_removed_instances = rrdcontext_post_process_updates(rc, true, reason, worker_jobs);
116 } while(forcefully_removed_instances);
117 }
118
119 void rrdcontext_recalculate_host_retention(RRDHOST *host, RRD_FLAGS reason, bool worker_jobs) {
120 if(unlikely(!host || !host->rrdctx.contexts)) return;
121
122 time_t first_time_s = 0;
123 time_t last_time_s = 0;
124
125 RRDCONTEXT *rc;
126 dfe_start_read(host->rrdctx.contexts, rc) {
127 rrdcontext_recalculate_context_retention(rc, reason, worker_jobs);
128
129 if(!first_time_s || (rc->first_time_s && rc->first_time_s < first_time_s))
130 first_time_s = rc->first_time_s;
131
132 if(!last_time_s || rc->last_time_s > last_time_s)
133 last_time_s = rc->last_time_s;
134 }
135 dfe_done(rc);
136
137 rrdhost_update_cached_retention(host, first_time_s, last_time_s, true);
138 }
139
140 static void rrdcontext_recalculate_retention_all_hosts(void) {
141 // Don't pre-clear rrdcontext_next_db_rotation_ut here -- the caller in
142 // rrdcontext_main clears it via CAS at the end of the pass, expecting
143 // the same deadline value it observed. Pre-clearing would let a
144 // concurrent rrdcontext_request_full_gc() arm a new deadline that the
145 // caller's unconditional store-to-zero would then silently overwrite,
146 // dropping the request.
147 RRDHOST *host;
148 dfe_start_reentrant(rrdhost_root_index, host) {
149 worker_is_busy(WORKER_JOB_RETENTION);
150 rrdcontext_recalculate_host_retention(host, RRD_FLAG_UPDATE_REASON_DB_ROTATION, true);
151 }
152 dfe_done(host);
153 }
154
155 // ----------------------------------------------------------------------------
156 // garbage collector
157
158 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) {
159 *min_first_time_t = LONG_MAX;
160 *max_last_time_t = 0;
161
162 for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
163 STORAGE_ENGINE *eng = host->db[tier].eng;
164
165 time_t first_time_t = 0, last_time_t = 0;
166 if (eng->api.metric_retention_by_id(host->db[tier].si, id, &first_time_t, &last_time_t)) {
167 if (first_time_t > 0 && first_time_t < *min_first_time_t)
168 *min_first_time_t = first_time_t;
169
170 if (last_time_t > *max_last_time_t)
171 *max_last_time_t = last_time_t;
172 }
173
174 if(tier == 0 && tier0_retention)
175 *tier0_retention = first_time_t || last_time_t;
176 }
177 }
178
179 bool rrdmetric_update_retention(RRDMETRIC *rm) {
180 time_t min_first_time_t = LONG_MAX, max_last_time_t = 0;
181 RRDDIM *rd = rrdmetric_rrddim_get_and_lock(rm);
182
183 if(rd) {
184 min_first_time_t = rrddim_first_entry_s(rd);
185 max_last_time_t = rrddim_last_entry_s(rd);
186 rrdmetric_rrddim_unlock(rd);
187 rrd_flag_clear(rm, RRD_FLAG_NO_TIER0_RETENTION);
188 }
189 else {
190 bool tier0_retention;
191 get_metric_retention_by_id(rm->ri->rc->rrdhost, rm->uuid, &min_first_time_t, &max_last_time_t, &tier0_retention);
192
193 if(tier0_retention)
194 rrd_flag_clear(rm, RRD_FLAG_NO_TIER0_RETENTION);
195 else
196 rrd_flag_set(rm, RRD_FLAG_NO_TIER0_RETENTION);
197 }
198
199 if(min_first_time_t == LONG_MAX)
200 min_first_time_t = 0;
201
202 if(min_first_time_t > max_last_time_t) {
203 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);
204 SWAP(min_first_time_t, max_last_time_t);
205 }
206
207 // check if retention changed
208
209 if (min_first_time_t != rm->first_time_s) {
210 rm->first_time_s = min_first_time_t;
211 rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
212 }
213
214 if (max_last_time_t != rm->last_time_s) {
215 rm->last_time_s = max_last_time_t;
216 rrd_flag_set_updated(rm, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
217 }
218
219 if(unlikely(!rm->first_time_s && !rm->last_time_s))
220 rrdmetric_set_deleted(rm, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
221
222 rrd_flag_set(rm, RRD_FLAG_LIVE_RETENTION);
223
224 return true;
225 }
226
227 static inline bool rrdmetric_should_be_deleted(RRDMETRIC *rm) {
228 if(likely(!rrd_flag_check(rm, RRD_FLAGS_REQUIRED_FOR_DELETIONS)))
229 return false;
230
231 if(likely(rrd_flag_check(rm, RRD_FLAGS_PREVENTING_DELETIONS)))
232 return false;
233
234 if(likely(rrdmetric_rrddim_atomic_load(rm)))
235 return false;
236
237 rrdmetric_update_retention(rm);
238 if(rm->first_time_s || rm->last_time_s)
239 return false;
240
241 return true;
242 }
243
244 static inline bool rrdinstance_should_be_deleted(RRDINSTANCE *ri) {
245 if(likely(!rrd_flag_check(ri, RRD_FLAGS_REQUIRED_FOR_DELETIONS)))
246 return false;
247
248 if(likely(rrd_flag_check(ri, RRD_FLAGS_PREVENTING_DELETIONS)))
249 return false;
250
251 if(likely(ri->rrdset))
252 return false;
253
254 if(unlikely(dictionary_referenced_items(ri->rrdmetrics) != 0))
255 return false;
256
257 if(unlikely(dictionary_entries(ri->rrdmetrics) != 0))
258 return false;
259
260 if(ri->first_time_s || ri->last_time_s)
261 return false;
262
263 return true;
264 }
265
266 bool rrdcontext_should_be_deleted(RRDCONTEXT *rc) {
267 if(likely(!rrd_flag_check(rc, RRD_FLAGS_REQUIRED_FOR_DELETIONS)))
268 return false;
269
270 if(likely(rrd_flag_check(rc, RRD_FLAGS_PREVENTING_DELETIONS)))
271 return false;
272
273 if(unlikely(dictionary_referenced_items(rc->rrdinstances) != 0))
274 return false;
275
276 if(unlikely(dictionary_entries(rc->rrdinstances) != 0))
277 return false;
278
279 if(unlikely(rc->first_time_s || rc->last_time_s))
280 return false;
281
282 return true;
283 }
284
285 void rrdcontext_delete_from_sql_unsafe(RRDCONTEXT *rc) {
286 // we need to refresh the string pointers in rc->hub
287 // in case the context changed values
288 rc->hub.id = string2str(rc->id);
289 rc->hub.title = string2str(rc->title);
290 rc->hub.units = string2str(rc->units);
291 rc->hub.family = string2str(rc->family);
292
293 if (rc->rrdhost->rrd_memory_mode != RRD_DB_MODE_DBENGINE)
294 return;
295
296 // delete it from SQL
297 if(ctx_delete_context(&rc->rrdhost->host_id.uuid, &rc->hub) != 0)
298 netdata_log_error("RRDCONTEXT: failed to delete context '%s' version %"PRIu64" from SQL.",
299 rc->hub.id, rc->hub.version);
300 }
301
302 void rrdcontext_garbage_collect_single_host(RRDHOST *host, bool worker_jobs) {
303
304 internal_error(true, "RRDCONTEXT: garbage collecting context structures of host '%s'", rrdhost_hostname(host));
305
306 RRDCONTEXT *rc;
307 dfe_start_reentrant(host->rrdctx.contexts, rc) {
308 if(unlikely(worker_jobs && !service_running(SERVICE_CONTEXT))) break;
309
310 if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP);
311
312 rrdcontext_lock(rc);
313
314 RRDINSTANCE *ri;
315 dfe_start_reentrant(rc->rrdinstances, ri) {
316 if(unlikely(worker_jobs && !service_running(SERVICE_CONTEXT))) break;
317
318 RRDMETRIC *rm;
319 dfe_start_write(ri->rrdmetrics, rm) {
320 if(rrdmetric_should_be_deleted(rm)) {
321 if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE);
322 if(!dictionary_del(ri->rrdmetrics, string2str(rm->id)))
323 netdata_log_error("RRDCONTEXT: metric '%s' of instance '%s' of context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.",
324 string2str(rm->id),
325 string2str(ri->id),
326 string2str(rc->id),
327 rrdhost_hostname(host));
328 else
329 internal_error(
330 true,
331 "RRDCONTEXT: metric '%s' of instance '%s' of context '%s' of host '%s', deleted from rrdmetrics dictionary.",
332 string2str(rm->id),
333 string2str(ri->id),
334 string2str(rc->id),
335 rrdhost_hostname(host));
336 }
337 }
338 dfe_done(rm);
339
340 if(rrdinstance_should_be_deleted(ri)) {
341 if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE);
342 if(!dictionary_del(rc->rrdinstances, string2str(ri->id)))
343 netdata_log_error("RRDCONTEXT: instance '%s' of context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.",
344 string2str(ri->id),
345 string2str(rc->id),
346 rrdhost_hostname(host));
347 else
348 internal_error(
349 true,
350 "RRDCONTEXT: instance '%s' of context '%s' of host '%s', deleted from rrdmetrics dictionary.",
351 string2str(ri->id),
352 string2str(rc->id),
353 rrdhost_hostname(host));
354 }
355
356 dictionary_garbage_collect(ri->rrdmetrics);
357 }
358 dfe_done(ri);
359 dictionary_garbage_collect(rc->rrdinstances);
360
361 if(unlikely(rrdcontext_should_be_deleted(rc))) {
362 if(worker_jobs) worker_is_busy(WORKER_JOB_CLEANUP_DELETE);
363 rrdcontext_dequeue_from_post_processing(rc);
364 rrdcontext_delete_from_sql_unsafe(rc);
365
366 if(!dictionary_del(host->rrdctx.contexts, string2str(rc->id)))
367 netdata_log_error("RRDCONTEXT: context '%s' of host '%s', failed to be deleted from rrdmetrics dictionary.",
368 string2str(rc->id),
369 rrdhost_hostname(host));
370 else
371 internal_error(
372 true,
373 "RRDCONTEXT: context '%s' of host '%s', deleted from rrdmetrics dictionary.",
374 string2str(rc->id),
375 rrdhost_hostname(host));
376 }
377
378 // the item is referenced in the dictionary
379 // so, it is still here to unlock, even if we have deleted it
380 rrdcontext_unlock(rc);
381 }
382 dfe_done(rc);
383
384 dictionary_garbage_collect(host->rrdctx.contexts);
385 }
386
387 static void rrdcontext_garbage_collect_for_all_hosts(void) {
388 RRDHOST *host;
389 dfe_start_reentrant(rrdhost_root_index, host) {
390 rrdcontext_garbage_collect_single_host(host, true);
391 }
392 dfe_done(host);
393 }
394
395 // ----------------------------------------------------------------------------
396 // post processing
397
398 static void rrdmetric_process_updates(RRDMETRIC *rm, bool force, RRD_FLAGS reason, bool worker_jobs) {
399 if(reason != RRD_FLAG_NONE)
400 rrd_flag_set_updated(rm, reason);
401
402 if(!force && !rrd_flag_is_updated(rm) && rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION) && !rrd_flag_check(rm, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION))
403 return;
404
405 if(worker_jobs)
406 worker_is_busy(WORKER_JOB_PP_METRIC);
407
408 if(reason & RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD) {
409 rrdmetric_set_archived(rm);
410 rrd_flag_set(rm, RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD);
411 }
412 if(rrd_flag_is_deleted(rm) && (reason & RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION))
413 rrdmetric_set_archived(rm);
414
415 rrdmetric_update_retention(rm);
416
417 rrd_flag_unset_updated(rm);
418 }
419
420 static void rrdinstance_post_process_updates(RRDINSTANCE *ri, bool force, RRD_FLAGS reason, bool worker_jobs) {
421 if(reason != RRD_FLAG_NONE)
422 rrd_flag_set_updated(ri, reason);
423
424 if(!force && !rrd_flag_is_updated(ri) && rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION))
425 return;
426
427 if(worker_jobs)
428 worker_is_busy(WORKER_JOB_PP_INSTANCE);
429
430 time_t min_first_time_t = LONG_MAX, max_last_time_t = 0;
431 size_t metrics_active = 0, metrics_deleted = 0, metrics_no_tier0 = 0;
432 bool live_retention = true, currently_collected = false;
433 if(dictionary_entries(ri->rrdmetrics) > 0) {
434 RRDMETRIC *rm;
435 dfe_start_read((DICTIONARY *)ri->rrdmetrics, rm) {
436 if(unlikely(worker_jobs && !service_running(SERVICE_CONTEXT))) break;
437
438 RRD_FLAGS reason_to_pass = reason;
439 if(rrd_flag_check(ri, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION))
440 reason_to_pass |= RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION;
441
442 rrdmetric_process_updates(rm, force, reason_to_pass, worker_jobs);
443
444 if(unlikely(!rrd_flag_check(rm, RRD_FLAG_LIVE_RETENTION)))
445 live_retention = false;
446
447 if(unlikely(rrd_flag_check(rm, RRD_FLAG_NO_TIER0_RETENTION)))
448 metrics_no_tier0++;
449
450 if (unlikely((rrdmetric_should_be_deleted(rm)))) {
451 metrics_deleted++;
452 continue;
453 }
454
455 if(!currently_collected && rrd_flag_is_collected(rm) && rm->first_time_s)
456 currently_collected = true;
457
458 metrics_active++;
459
460 if (rm->first_time_s && rm->first_time_s < min_first_time_t)
461 min_first_time_t = rm->first_time_s;
462
463 if (rm->last_time_s && rm->last_time_s > max_last_time_t)
464 max_last_time_t = rm->last_time_s;
465 }
466 dfe_done(rm);
467 }
468
469 if(metrics_no_tier0 && metrics_no_tier0 == metrics_active)
470 rrd_flag_set(ri, RRD_FLAG_NO_TIER0_RETENTION);
471 else
472 rrd_flag_clear(ri, RRD_FLAG_NO_TIER0_RETENTION);
473
474 if(unlikely(live_retention && !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)))
475 rrd_flag_set(ri, RRD_FLAG_LIVE_RETENTION);
476 else if(unlikely(!live_retention && rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)))
477 rrd_flag_clear(ri, RRD_FLAG_LIVE_RETENTION);
478
479 if(unlikely(!metrics_active)) {
480 // no metrics available
481
482 if(ri->first_time_s) {
483 ri->first_time_s = 0;
484 rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
485 }
486
487 if(ri->last_time_s) {
488 ri->last_time_s = 0;
489 rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
490 }
491
492 rrdinstance_set_deleted(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
493 }
494 else {
495 // we have active metrics...
496
497 if (unlikely(min_first_time_t == LONG_MAX))
498 min_first_time_t = 0;
499
500 if (unlikely(min_first_time_t == 0 || max_last_time_t == 0)) {
501 if(ri->first_time_s) {
502 ri->first_time_s = 0;
503 rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
504 }
505
506 if(ri->last_time_s) {
507 ri->last_time_s = 0;
508 rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
509 }
510
511 if(likely(live_retention))
512 rrdinstance_set_deleted(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
513 }
514 else {
515 rrd_flag_clear(ri, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
516
517 if (unlikely(ri->first_time_s != min_first_time_t)) {
518 ri->first_time_s = min_first_time_t;
519 rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
520 }
521
522 if (unlikely(ri->last_time_s != max_last_time_t)) {
523 ri->last_time_s = max_last_time_t;
524 rrd_flag_set_updated(ri, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
525 }
526
527 if(likely(currently_collected))
528 rrdinstance_set_collected(ri);
529 else
530 rrdinstance_set_archived(ri);
531 }
532 }
533
534 rrd_flag_unset_updated(ri);
535 }
536
537 static bool rrdinstance_forcefully_clear_retention(RRDCONTEXT *rc, size_t count, const char *descr) {
538 if(!count) return false;
539
540 RRDHOST *host = rc->rrdhost;
541
542 time_t from_s = LONG_MAX;
543 time_t to_s = 0;
544
545 size_t instances_deleted = 0;
546 size_t metrics_deleted = 0;
547 RRDINSTANCE *ri;
548 dfe_start_read(rc->rrdinstances, ri) {
549 if(!rrd_flag_check(ri, RRD_FLAG_NO_TIER0_RETENTION) || rrd_flag_is_collected(ri) || ri->rrdset)
550 continue;
551
552 size_t metrics_cleared = 0;
553 RRDMETRIC *rm;
554 dfe_start_read(ri->rrdmetrics, rm) {
555 if(!rrd_flag_check(rm, RRD_FLAG_NO_TIER0_RETENTION) || rrd_flag_is_collected(rm) || rrdmetric_rrddim_atomic_load(rm))
556 continue;
557
558 rrdmetric_update_retention(rm);
559
560 if(rm->first_time_s < from_s)
561 from_s = rm->first_time_s;
562
563 if(rm->last_time_s > to_s)
564 to_s = rm->last_time_s;
565
566 for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
567 STORAGE_ENGINE *eng = host->db[tier].eng;
568 eng->api.metric_retention_delete_by_id(host->db[tier].si, rm->uuid);
569 }
570
571 metrics_cleared++;
572 metrics_deleted++;
573 rrdmetric_update_retention(rm);
574 rrdmetric_trigger_updates(rm, __FUNCTION__ );
575 }
576 dfe_done(rm);
577
578 if(metrics_cleared) {
579 rrdinstance_trigger_updates(ri, __FUNCTION__ );
580 instances_deleted++;
581
582 if(--count == 0)
583 break;
584 }
585 }
586 dfe_done(ri);
587
588 if(metrics_deleted) {
589 char from_txt[128], to_txt[128];
590
591 if(!from_s || from_s == LONG_MAX)
592 snprintfz(from_txt, sizeof(from_txt), "%s", "NONE");
593 else
594 rfc3339_datetime_ut(from_txt, sizeof(from_txt), from_s * USEC_PER_SEC, 0, true);
595
596 if(!to_s)
597 snprintfz(to_txt, sizeof(to_txt), "%s", "NONE");
598 else
599 rfc3339_datetime_ut(to_txt, sizeof(to_txt), to_s * USEC_PER_SEC, 0, true);
600
601 ND_LOG_STACK lgs[] = {
602 ND_LOG_FIELD_TXT(NDF_MODULE, "extreme cardinality protection"),
603 ND_LOG_FIELD_STR(NDF_NIDL_NODE, rc->rrdhost->hostname),
604 ND_LOG_FIELD_STR(NDF_NIDL_CONTEXT, rc->id),
605 ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &extreme_cardinality_msgid),
606 ND_LOG_FIELD_END(),
607 };
608 ND_LOG_STACK_PUSH(lgs);
609
610 nd_log(NDLS_DAEMON, NDLP_NOTICE,
611 "EXTREME CARDINALITY PROTECTION: host '%s', context '%s', %s: "
612 "forcefully cleared the retention of %zu metrics and %zu instances, "
613 "having non-tier0 retention from %s to %s.",
614 rrdhost_hostname(rc->rrdhost),
615 string2str(rc->id),
616 descr,
617 metrics_deleted, instances_deleted,
618 from_txt, to_txt);
619
620 return true;
621 }
622
623 return false;
624 }
625
626 bool rrdcontext_post_process_updates(RRDCONTEXT *rc, bool force, RRD_FLAGS reason, bool worker_jobs) {
627 bool ret = false;
628
629 if(reason != RRD_FLAG_NONE)
630 rrd_flag_set_updated(rc, reason);
631
632 if(worker_jobs)
633 worker_is_busy(WORKER_JOB_PP_CONTEXT);
634
635 size_t min_priority_collected = LONG_MAX;
636 size_t min_priority_not_collected = LONG_MAX;
637 size_t min_priority = LONG_MAX;
638 time_t min_first_time_t = LONG_MAX, max_last_time_t = 0;
639 size_t instances_active = 0, instances_deleted = 0, instances_no_tier0 = 0;
640 bool live_retention = true, currently_collected = false, hidden = true;
641 if(dictionary_entries(rc->rrdinstances) > 0) {
642 RRDINSTANCE *ri;
643 dfe_start_reentrant(rc->rrdinstances, ri) {
644 if(unlikely(worker_jobs && !service_running(SERVICE_CONTEXT))) break;
645
646 RRD_FLAGS reason_to_pass = reason;
647 if(rrd_flag_check(rc, RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION))
648 reason_to_pass |= RRD_FLAG_UPDATE_REASON_UPDATE_RETENTION;
649
650 rrdinstance_post_process_updates(ri, force, reason_to_pass, worker_jobs);
651
652 if(unlikely(hidden && !rrd_flag_check(ri, RRD_FLAG_HIDDEN)))
653 hidden = false;
654
655 if(unlikely(live_retention && !rrd_flag_check(ri, RRD_FLAG_LIVE_RETENTION)))
656 live_retention = false;
657
658 if (unlikely(rrdinstance_should_be_deleted(ri))) {
659 instances_deleted++;
660 continue;
661 }
662
663 if(unlikely(rrd_flag_check(ri, RRD_FLAG_NO_TIER0_RETENTION)))
664 instances_no_tier0++;
665
666 bool ri_collected = rrd_flag_is_collected(ri);
667
668 if(ri_collected && !rrd_flag_check(ri, RRD_FLAG_MERGED_COLLECTED_RI_TO_RC)) {
669 rrdcontext_update_from_collected_rrdinstance(ri);
670 rrd_flag_set(ri, RRD_FLAG_MERGED_COLLECTED_RI_TO_RC);
671 }
672
673 if(unlikely(!currently_collected && rrd_flag_is_collected(ri) && ri->first_time_s))
674 currently_collected = true;
675
676 internal_error(rc->units != ri->units,
677 "RRDCONTEXT: '%s' rrdinstance '%s' has different units, context '%s', instance '%s'",
678 string2str(rc->id), string2str(ri->id),
679 string2str(rc->units), string2str(ri->units));
680
681 instances_active++;
682
683 if (ri->priority >= RRDCONTEXT_MINIMUM_ALLOWED_PRIORITY) {
684 if(rrd_flag_is_collected(ri)) {
685 if(ri->priority < min_priority_collected)
686 min_priority_collected = ri->priority;
687 }
688 else {
689 if(ri->priority < min_priority_not_collected)
690 min_priority_not_collected = ri->priority;
691 }
692 }
693
694 if (ri->first_time_s && ri->first_time_s < min_first_time_t)
695 min_first_time_t = ri->first_time_s;
696
697 if (ri->last_time_s && ri->last_time_s > max_last_time_t)
698 max_last_time_t = ri->last_time_s;
699 }
700 dfe_done(ri);
701
702 if(extreme_cardinality.enabled &&
703 __atomic_load_n(&extreme_cardinality.db_rotations, __ATOMIC_RELAXED) &&
704 instances_active &&
705 instances_no_tier0 >= extreme_cardinality.instances_count) {
706 size_t percent = (100 * instances_no_tier0 / instances_active);
707 if(percent >= extreme_cardinality.active_vs_archived_percentage) {
708 size_t to_keep = extreme_cardinality.active_vs_archived_percentage * instances_active / 100;
709 to_keep = MAX(to_keep, extreme_cardinality.instances_count);
710 size_t to_remove = instances_no_tier0 > to_keep ? instances_no_tier0 - to_keep : 0;
711
712 if(to_remove) {
713 char buf[256];
714 snprintfz(buf, sizeof(buf),
715 "total active instances %zu, not in tier0 %zu, ephemerality %zu%%",
716 instances_active, instances_no_tier0, percent);
717 ret = rrdinstance_forcefully_clear_retention(rc, to_remove, buf);
718 }
719 }
720 }
721
722 if(min_priority_collected != LONG_MAX)
723 // use the collected priority
724 min_priority = min_priority_collected;
725 else
726 // use the non-collected priority
727 min_priority = min_priority_not_collected;
728 }
729
730 {
731 bool previous_hidden = rrd_flag_check(rc, RRD_FLAG_HIDDEN);
732 if (hidden != previous_hidden) {
733 if (hidden && !rrd_flag_check(rc, RRD_FLAG_HIDDEN))
734 rrd_flag_set(rc, RRD_FLAG_HIDDEN);
735 else if (!hidden && rrd_flag_check(rc, RRD_FLAG_HIDDEN))
736 rrd_flag_clear(rc, RRD_FLAG_HIDDEN);
737 }
738
739 bool previous_live_retention = rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION);
740 if (live_retention != previous_live_retention) {
741 if (live_retention && !rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION))
742 rrd_flag_set(rc, RRD_FLAG_LIVE_RETENTION);
743 else if (!live_retention && rrd_flag_check(rc, RRD_FLAG_LIVE_RETENTION))
744 rrd_flag_clear(rc, RRD_FLAG_LIVE_RETENTION);
745 }
746 }
747
748 rrdcontext_lock(rc);
749 rc->pp.executions++;
750
751 if(unlikely(!instances_active)) {
752 // we had some instances, but they are gone now...
753
754 if(rc->first_time_s) {
755 rc->first_time_s = 0;
756 rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
757 }
758
759 if(rc->last_time_s) {
760 rc->last_time_s = 0;
761 rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
762 }
763
764 rrdcontext_set_deleted(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
765 }
766 else {
767 // we have some active instances...
768
769 if (unlikely(min_first_time_t == LONG_MAX))
770 min_first_time_t = 0;
771
772 if (unlikely(min_first_time_t == 0 && max_last_time_t == 0)) {
773 if(rc->first_time_s) {
774 rc->first_time_s = 0;
775 rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
776 }
777
778 if(rc->last_time_s) {
779 rc->last_time_s = 0;
780 rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
781 }
782
783 rrdcontext_set_deleted(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
784 }
785 else {
786 rrd_flag_clear(rc, RRD_FLAG_UPDATE_REASON_ZERO_RETENTION);
787
788 if (unlikely(rc->first_time_s != min_first_time_t)) {
789 rc->first_time_s = min_first_time_t;
790 rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_FIRST_TIME_T);
791 }
792
793 if (rc->last_time_s != max_last_time_t) {
794 rc->last_time_s = max_last_time_t;
795 rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_LAST_TIME_T);
796 }
797
798 if(likely(currently_collected))
799 rrdcontext_set_collected(rc);
800 else
801 rrdcontext_set_archived(rc);
802 }
803
804 if (min_priority != LONG_MAX && rc->priority != min_priority) {
805 rc->priority = min_priority;
806 rrd_flag_set_updated(rc, RRD_FLAG_UPDATE_REASON_CHANGED_METADATA);
807 }
808 }
809
810 if(unlikely(rrd_flag_is_updated(rc))) {
811 if(check_if_cloud_version_changed_unsafe(rc, false)) {
812 rc->version = rrdcontext_get_next_version(rc);
813 rrdcontext_add_to_hub_queue(rc);
814 }
815 }
816
817 rrd_flag_unset_updated(rc);
818 rrdcontext_unlock(rc);
819
820 return ret;
821 }
822
823 void rrdcontext_queue_for_post_processing(RRDCONTEXT *rc, const char *function __maybe_unused, RRD_FLAGS flags __maybe_unused) {
824 #if 0
825 if(string_strcmp(rc->id, "system.cpu") == 0) {
826 CLEAN_BUFFER *wb = buffer_create(0, NULL);
827 buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
828 buffer_json_member_add_array(wb, "flags");
829 rrd_flags_to_buffer_json_array_items(rc->flags, wb);
830 buffer_json_array_close(wb);
831 buffer_json_member_add_array(wb, "reasons");
832 rrd_reasons_to_buffer_json_array_items(rc->flags, wb);
833 buffer_json_array_close(wb);
834 buffer_json_finalize(wb);
835 nd_log(NDLS_DAEMON, NDLP_EMERG, "%s() context '%s', triggered: %s",
836 function, string2str(rc->id), buffer_tostring(wb));
837 }
838 #endif
839
840 rrdcontext_add_to_pp_queue(rc);
841 }
842
843 void rrdcontext_initial_processing_after_loading(RRDCONTEXT *rc) {
844 rrdcontext_dequeue_from_post_processing(rc);
845 rrdcontext_post_process_updates(rc, false, RRD_FLAG_NONE, false);
846 }
847
848 void rrdcontext_delete_after_loading(RRDHOST *host, RRDCONTEXT *rc) {
849 rrdcontext_del_from_hub_queue(rc, false);
850 rrdcontext_dequeue_from_post_processing(rc);
851 dictionary_del(host->rrdctx.contexts, string2str(rc->id));
852 }
853
854 // ----------------------------------------------------------------------------
855 // dispatching contexts to cloud
856
857 static uint64_t rrdcontext_get_next_version(RRDCONTEXT *rc) {
858 time_t now = now_realtime_sec();
859 uint64_t version = MAX(rc->version, rc->hub.version);
860 version = MAX((uint64_t)now, version);
861 version++;
862 return version;
863 }
864
865 void rrdcontext_message_send_unsafe(RRDCONTEXT *rc, bool snapshot __maybe_unused, void *bundle __maybe_unused) {
866
867 // save it, so that we know the last version we sent to hub
868 rc->version = rc->hub.version = rrdcontext_get_next_version(rc);
869 rc->hub.id = string2str(rc->id);
870 rc->hub.title = string2str(rc->title);
871 rc->hub.units = string2str(rc->units);
872 rc->hub.family = string2str(rc->family);
873 rc->hub.chart_type = rrdset_type_name(rc->chart_type);
874 rc->hub.priority = rc->priority;
875 rc->hub.first_time_s = rc->first_time_s;
876 rc->hub.last_time_s = rrd_flag_is_collected(rc) ? 0 : rc->last_time_s;
877 rc->hub.deleted = rrd_flag_is_deleted(rc) ? true : false;
878
879 struct context_updated message = {
880 .id = rc->hub.id,
881 .version = rc->hub.version,
882 .title = rc->hub.title,
883 .units = rc->hub.units,
884 .family = rc->hub.family,
885 .chart_type = rc->hub.chart_type,
886 .priority = rc->hub.priority,
887 .first_entry = rc->hub.first_time_s,
888 .last_entry = rc->hub.last_time_s,
889 .deleted = rc->hub.deleted,
890 };
891
892 if(likely(!rrd_flag_check(rc, RRD_FLAG_HIDDEN))) {
893 if (snapshot) {
894 if (!rc->hub.deleted)
895 contexts_snapshot_add_ctx_update(bundle, &message);
896 }
897 else
898 contexts_updated_add_ctx_update(bundle, &message);
899 }
900
901 // store it to SQL
902
903 if(rrd_flag_is_deleted(rc))
904 rrdcontext_delete_from_sql_unsafe(rc);
905
906 else {
907 if (rc->rrdhost->rrd_memory_mode != RRD_DB_MODE_DBENGINE)
908 return;
909 if (ctx_store_context(&rc->rrdhost->host_id.uuid, &rc->hub) != 0)
910 netdata_log_error(
911 "RRDCONTEXT: failed to save context '%s' version %" PRIu64 " to SQL.", rc->hub.id, rc->hub.version);
912 }
913 }
914
915 bool check_if_cloud_version_changed_unsafe(RRDCONTEXT *rc, bool sending __maybe_unused) {
916 bool id_changed = false,
917 title_changed = false,
918 units_changed = false,
919 family_changed = false,
920 chart_type_changed = false,
921 priority_changed = false,
922 first_time_changed = false,
923 last_time_changed = false,
924 deleted_changed = false;
925
926 RRD_FLAGS flags = rrd_flags_get(rc);
927
928 if(unlikely(string2str(rc->id) != rc->hub.id))
929 id_changed = true;
930
931 if(unlikely(string2str(rc->title) != rc->hub.title))
932 title_changed = true;
933
934 if(unlikely(string2str(rc->units) != rc->hub.units))
935 units_changed = true;
936
937 if(unlikely(string2str(rc->family) != rc->hub.family))
938 family_changed = true;
939
940 if(unlikely(rrdset_type_name(rc->chart_type) != rc->hub.chart_type))
941 chart_type_changed = true;
942
943 if(unlikely(rc->priority != rc->hub.priority))
944 priority_changed = true;
945
946 if(unlikely((uint64_t)rc->first_time_s != rc->hub.first_time_s))
947 first_time_changed = true;
948
949 if(unlikely((uint64_t)((flags & RRD_FLAG_COLLECTED) ? 0 : rc->last_time_s) != rc->hub.last_time_s))
950 last_time_changed = true;
951
952 if(unlikely(((flags & RRD_FLAG_DELETED) ? true : false) != rc->hub.deleted))
953 deleted_changed = true;
954
955 if(unlikely(id_changed || title_changed || units_changed || family_changed || chart_type_changed || priority_changed || first_time_changed || last_time_changed || deleted_changed)) {
956
957 internal_error(LOG_TRANSITIONS,
958 "RRDCONTEXT: %s NEW VERSION '%s'%s of host '%s', version %"PRIu64", title '%s'%s, units '%s'%s, family '%s'%s, chart type '%s'%s, priority %u%s, first_time_t %ld%s, last_time_t %ld%s, deleted '%s'%s, (queued for %llu ms, expected %llu ms)",
959 sending?"SENDING":"QUEUE",
960 string2str(rc->id), id_changed ? " (CHANGED)" : "",
961 rrdhost_hostname(rc->rrdhost),
962 rc->version,
963 string2str(rc->title), title_changed ? " (CHANGED)" : "",
964 string2str(rc->units), units_changed ? " (CHANGED)" : "",
965 string2str(rc->family), family_changed ? " (CHANGED)" : "",
966 rrdset_type_name(rc->chart_type), chart_type_changed ? " (CHANGED)" : "",
967 rc->priority, priority_changed ? " (CHANGED)" : "",
968 rc->first_time_s, first_time_changed ? " (CHANGED)" : "",
969 (flags & RRD_FLAG_COLLECTED) ? 0 : rc->last_time_s, last_time_changed ? " (CHANGED)" : "",
970 (flags & RRD_FLAG_DELETED) ? "true" : "false", deleted_changed ? " (CHANGED)" : "",
971 sending ? (now_realtime_usec() - rc->queue.queued_ut) / USEC_PER_MS : 0,
972 sending ? (rc->queue.scheduled_dispatch_ut - rc->queue.queued_ut) / USEC_PER_MS : 0
973 );
974
975 rrdhost_update_cached_retention(rc->rrdhost, rc->first_time_s, rc->last_time_s, false);
976
977 return true;
978 }
979
980 if(!(flags & RRD_FLAG_COLLECTED))
981 rrdhost_update_cached_retention(rc->rrdhost, rc->first_time_s, rc->last_time_s, false);
982
983 return false;
984 }
985
986 usec_t rrdcontext_calculate_queued_dispatch_time_ut(RRDCONTEXT *rc, usec_t now_ut) {
987
988 if(likely(rc->queue.delay_calc_ut >= rc->queue.queued_ut))
989 return rc->queue.scheduled_dispatch_ut;
990
991 RRD_FLAGS flags = rc->queue.queued_flags;
992
993 usec_t delay = LONG_MAX;
994 int i;
995 struct rrdcontext_reason *reason;
996 for(i = 0, reason = &rrdcontext_reasons[i]; reason->name ; reason = &rrdcontext_reasons[++i]) {
997 if(unlikely(flags & reason->flag)) {
998 if(reason->delay_ut < delay)
999 delay = reason->delay_ut;
1000 }
1001 }
1002
1003 if(unlikely(delay == LONG_MAX)) {
1004 internal_error(true, "RRDCONTEXT: '%s', cannot find minimum delay of flags %x", string2str(rc->id), (unsigned int)flags);
1005 delay = 60 * USEC_PER_SEC;
1006 }
1007
1008 rc->queue.delay_calc_ut = now_ut;
1009 usec_t dispatch_ut = rc->queue.scheduled_dispatch_ut = rc->queue.queued_ut + delay;
1010 return dispatch_ut;
1011 }
1012
1013 // ----------------------------------------------------------------------------
1014 // worker thread
1015
1016 static void rrdcontext_main_cleanup(void *pptr) {
1017 struct netdata_static_thread *static_thread = CLEANUP_FUNCTION_GET_PTR(pptr);
1018 if(!static_thread) return;
1019
1020 static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
1021
1022 // custom code
1023 worker_unregister();
1024
1025 static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
1026 }
1027
1028 void rrdcontext_main(void *ptr) {
1029 CLEANUP_FUNCTION_REGISTER(rrdcontext_main_cleanup) cleanup_ptr = ptr;
1030
1031 worker_register("RRDCONTEXT");
1032 worker_register_job_name(WORKER_JOB_HOSTS, "hosts");
1033 worker_register_job_name(WORKER_JOB_CHECK, "dedup checks");
1034 worker_register_job_name(WORKER_JOB_SEND, "sent contexts");
1035 worker_register_job_name(WORKER_JOB_DEQUEUE, "deduplicated contexts");
1036 worker_register_job_name(WORKER_JOB_RETENTION, "metrics retention");
1037 worker_register_job_name(WORKER_JOB_QUEUED, "queued contexts");
1038 worker_register_job_name(WORKER_JOB_CLEANUP, "cleanups");
1039 worker_register_job_name(WORKER_JOB_CLEANUP_DELETE, "deletes");
1040 worker_register_job_name(WORKER_JOB_PP_METRIC, "check metrics");
1041 worker_register_job_name(WORKER_JOB_PP_INSTANCE, "check instances");
1042 worker_register_job_name(WORKER_JOB_PP_CONTEXT, "check contexts");
1043
1044 worker_register_job_custom_metric(WORKER_JOB_HUB_QUEUE_SIZE, "hub queue size", "contexts", WORKER_METRIC_ABSOLUTE);
1045 worker_register_job_custom_metric(WORKER_JOB_PP_QUEUE_SIZE, "post processing queue size", "contexts", WORKER_METRIC_ABSOLUTE);
1046
1047 heartbeat_t hb;
1048 heartbeat_init(&hb, RRDCONTEXT_WORKER_THREAD_HEARTBEAT_USEC);
1049
1050 extreme_cardinality.enabled = inicfg_get_boolean(
1051 &netdata_config, CONFIG_SECTION_DB, "extreme cardinality protection",
1052 nd_profile.storage_tiers > 1 && default_rrd_memory_mode == RRD_DB_MODE_DBENGINE
1053 );
1054
1055 extreme_cardinality.instances_count = inicfg_get_number_range(
1056 &netdata_config, CONFIG_SECTION_DB, "extreme cardinality keep instances",
1057 (long long)extreme_cardinality.instances_count, 1, 1000000);
1058
1059 extreme_cardinality.active_vs_archived_percentage = inicfg_get_number_range(
1060 &netdata_config, CONFIG_SECTION_DB, "extreme cardinality min ephemerality",
1061 (long long)extreme_cardinality.active_vs_archived_percentage, 0, 100);
1062
1063 while (service_running(SERVICE_CONTEXT)) {
1064 worker_is_idle();
1065 heartbeat_next(&hb);
1066
1067 if(unlikely(!service_running(SERVICE_CONTEXT))) break;
1068
1069 usec_t now_ut = now_realtime_usec();
1070
1071 usec_t deadline = __atomic_load_n(&rrdcontext_next_db_rotation_ut, __ATOMIC_RELAXED);
1072 if(deadline && now_ut > deadline) {
1073 // db_rotations is bumped by rrdcontext_count_db_rotation() from
1074 // rrdcontext_db_rotation() only -- the chart-cleanup trigger
1075 // (rrdcontext_request_full_gc) does NOT bump it, so the
1076 // extreme-cardinality guard at line ~700 still activates only
1077 // after a real dbengine rotation.
1078 rrdcontext_recalculate_retention_all_hosts();
1079 rrdcontext_garbage_collect_for_all_hosts();
1080 // Clear the slot only if it still holds the deadline we
1081 // processed. Two writers can race this pass:
1082 // - rrdcontext_request_full_gc() arms only when the slot is
1083 // zero, but the slot stays non-zero throughout this pass,
1084 // so any such request during the pass is coalesced into
1085 // the in-flight pass and does not arm a new deadline.
1086 // - rrdcontext_db_rotation() stores unconditionally. If it
1087 // fires during this pass, the slot now holds a fresh
1088 // deadline; the CAS here fails and that deadline drives
1089 // the next iteration.
1090 __atomic_compare_exchange_n(&rrdcontext_next_db_rotation_ut,
1091 &deadline, 0,
1092 false,
1093 __ATOMIC_RELAXED, __ATOMIC_RELAXED);
1094
1095 // If a rrdcontext_request_full_gc() landed mid-pass (its CAS
1096 // failed because the slot was non-zero with an expired
1097 // deadline), the host whose archive motivated it may have
1098 // already been walked in this pass. Schedule a follow-up:
1099 // arm a fresh deadline iff the slot is currently zero. If
1100 // another path (e.g. dbengine rotation) just armed a new
1101 // deadline, the CAS leaves it alone.
1102 if(__atomic_exchange_n(&rrdcontext_full_gc_rerun_requested, 0, __ATOMIC_RELAXED)) {
1103 usec_t expected = 0;
1104 usec_t fresh = now_realtime_usec() + FULL_RETENTION_SCAN_DELAY_AFTER_DB_ROTATION_SECS * USEC_PER_SEC;
1105 __atomic_compare_exchange_n(&rrdcontext_next_db_rotation_ut,
1106 &expected, fresh,
1107 false,
1108 __ATOMIC_RELAXED, __ATOMIC_RELAXED);
1109 }
1110 }
1111
1112 size_t hub_queued_contexts_for_all_hosts = 0;
1113 size_t pp_queued_contexts_for_all_hosts = 0;
1114
1115 RRDHOST *host;
1116 dfe_start_reentrant(rrdhost_root_index, host) {
1117 if(unlikely(!service_running(SERVICE_CONTEXT))) break;
1118
1119 // Allow timed-out deferred checkpoints to replay even if context loading is stuck.
1120 rrdcontext_hub_pending_checkpoint_replay(host);
1121
1122 if(rrdhost_flag_check(host, RRDHOST_FLAG_PENDING_CONTEXT_LOAD))
1123 continue;
1124
1125 if(rrdhost_flag_check(host, RRDHOST_FLAG_RRDCONTEXT_GET_RETENTION)) {
1126 rrdcontext_recalculate_host_retention(host, RRD_FLAG_UPDATE_REASON_DISCONNECTED_CHILD, false);
1127 rrdhost_flag_clear(host, RRDHOST_FLAG_RRDCONTEXT_GET_RETENTION);
1128 }
1129
1130 worker_is_busy(WORKER_JOB_HOSTS);
1131
1132 pp_queued_contexts_for_all_hosts += rrdcontext_queue_entries(&host->rrdctx.pp_queue);
1133 rrdcontext_post_process_queued_contexts(host);
1134
1135 // replay deferred checkpoint if post-processing drained the queue, or on timeout
1136 rrdcontext_hub_pending_checkpoint_replay(host);
1137
1138 hub_queued_contexts_for_all_hosts += rrdcontext_queue_entries(&host->rrdctx.hub_queue);
1139 rrdcontext_dispatch_queued_contexts_to_hub(host, now_ut);
1140
1141 if (host->rrdctx.contexts)
1142 dictionary_garbage_collect(host->rrdctx.contexts);
1143 }
1144 dfe_done(host);
1145
1146 worker_set_metric(WORKER_JOB_HUB_QUEUE_SIZE, (NETDATA_DOUBLE)hub_queued_contexts_for_all_hosts);
1147 worker_set_metric(WORKER_JOB_PP_QUEUE_SIZE, (NETDATA_DOUBLE)pp_queued_contexts_for_all_hosts);
1148 }
1149 }