Skip to content

Commit a9a1c1d

Browse files
committed
NC | lifecycle | expire objects with batching
Signed-off-by: nadav mizrahi <[email protected]>
1 parent f14ce2e commit a9a1c1d

File tree

3 files changed

+69
-52
lines changed

3 files changed

+69
-52
lines changed

config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,9 @@ config.NC_LIFECYCLE_RUN_DELAY_LIMIT_MINS = 2;
10321032
/** @type {'UTC' | 'LOCAL'} */
10331033
config.NC_LIFECYCLE_TZ = 'LOCAL';
10341034

1035+
config.NC_LIFECYCLE_LIST_BATCH_SIZE = 1000;
1036+
config.NC_LIFECYCLE_BUCKET_BATCH_SIZE = 10000;
1037+
10351038
config.NC_LIFECYCLE_GPFS_ILM_ENABLED = false;
10361039
////////// GPFS //////////
10371040
config.GPFS_DOWN_DELAY = 1000;

src/manage_nsfs/manage_nsfs_events_utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ NoobaaEvent.LIFECYCLE_FAILED = Object.freeze({
444444
event_type: 'ERROR',
445445
scope: 'NODE',
446446
severity: 'ERROR',
447-
state: 'DEGRADED',
447+
state: 'HEALTHY',
448448
});
449449

450450
NoobaaEvent.LIFECYCLE_TIMEOUT = Object.freeze({
@@ -455,7 +455,7 @@ NoobaaEvent.LIFECYCLE_TIMEOUT = Object.freeze({
455455
event_type: 'ERROR',
456456
scope: 'NODE',
457457
severity: 'ERROR',
458-
state: 'DEGRADED',
458+
state: 'HEALTHY',
459459
});
460460

461461
exports.NoobaaEvent = NoobaaEvent;

src/manage_nsfs/nc_lifecycle.js

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ const config_fs_options = { silent_if_missing: true };
3131

3232
const lifecycle_run_status = {
3333
running_host: os.hostname(), lifecycle_run_times: {},
34-
total_stats: _get_default_stats(), buckets_statuses: {}
34+
total_stats: _get_default_stats(), buckets_statuses: {},
35+
state: {is_finished: false}
3536
};
3637

3738
let return_short_status = false;
@@ -137,13 +138,19 @@ async function run_lifecycle(config_fs, disable_service_validation) {
137138
*/
138139
async function process_buckets(config_fs, bucket_names, system_json) {
139140
const buckets_concurrency = 10; // TODO - think about it
140-
await P.map_with_concurrency(buckets_concurrency, bucket_names, async bucket_name =>
141-
await _call_op_and_update_status({
142-
bucket_name,
143-
op_name: TIMED_OPS.PROCESS_BUCKET,
144-
op_func: async () => process_bucket(config_fs, bucket_name, system_json)
145-
})
146-
);
141+
while (!lifecycle_run_status.state.is_finished) {
142+
await P.map_with_concurrency(buckets_concurrency, bucket_names, async bucket_name =>
143+
await _call_op_and_update_status({
144+
bucket_name,
145+
op_name: TIMED_OPS.PROCESS_BUCKET,
146+
op_func: async () => process_bucket(config_fs, bucket_name, system_json)
147+
})
148+
);
149+
lifecycle_run_status.state.is_finished = Object.values(lifecycle_run_status.buckets_statuses).reduce(
150+
(acc, bucket) => acc && (bucket.state?.is_finished),
151+
true
152+
);
153+
}
147154
}
148155

149156
/**
@@ -158,7 +165,10 @@ async function process_bucket(config_fs, bucket_name, system_json) {
158165
const object_sdk = new NsfsObjectSDK('', config_fs, account, bucket_json.versioning, config_fs.config_root, system_json);
159166
await object_sdk._simple_load_requesting_account();
160167
const should_notify = notifications_util.should_notify_on_event(bucket_json, notifications_util.OP_TO_EVENT.lifecycle_delete.name);
161-
if (!bucket_json.lifecycle_configuration_rules) return {};
168+
if (!bucket_json.lifecycle_configuration_rules) {
169+
lifecycle_run_status.buckets_statuses[bucket_json.name].state = {is_finished: true};
170+
return;
171+
}
162172
await process_rules(config_fs, bucket_json, object_sdk, should_notify);
163173
}
164174

@@ -170,16 +180,31 @@ async function process_bucket(config_fs, bucket_name, system_json) {
170180
*/
171181
async function process_rules(config_fs, bucket_json, object_sdk, should_notify) {
172182
try {
173-
await P.all(_.map(bucket_json.lifecycle_configuration_rules,
174-
async (lifecycle_rule, index) =>
175-
await _call_op_and_update_status({
176-
bucket_name: bucket_json.name,
177-
rule_id: lifecycle_rule.id,
178-
op_name: TIMED_OPS.PROCESS_RULE,
179-
op_func: async () => process_rule(config_fs, lifecycle_rule, index, bucket_json, object_sdk, should_notify)
180-
})
181-
)
182-
);
183+
lifecycle_run_status.buckets_statuses[bucket_json.name].state ??= {};
184+
const bucket_state = lifecycle_run_status.buckets_statuses[bucket_json.name].state;
185+
bucket_state.num_processed_objects = 0;
186+
while (!bucket_state.is_finished && bucket_state.num_processed_objects < config.NC_LIFECYCLE_BUCKET_BATCH_SIZE) {
187+
await P.all(_.map(bucket_json.lifecycle_configuration_rules,
188+
async (lifecycle_rule, index) =>
189+
await _call_op_and_update_status({
190+
bucket_name: bucket_json.name,
191+
rule_id: lifecycle_rule.id,
192+
op_name: TIMED_OPS.PROCESS_RULE,
193+
op_func: async () => process_rule(config_fs,
194+
lifecycle_rule,
195+
index,
196+
bucket_json,
197+
object_sdk,
198+
should_notify
199+
)
200+
})
201+
)
202+
);
203+
bucket_state.is_finished = Object.values(lifecycle_run_status.buckets_statuses[bucket_json.name].rules_statuses).reduce(
204+
(acc, rule) => acc && (_.isEmpty(rule.state) || rule.state.is_finished),
205+
true
206+
);
207+
}
183208
} catch (err) {
184209
dbg.error('process_rules failed with error', err, err.code, err.message);
185210
}
@@ -268,7 +293,6 @@ async function process_rule(config_fs, lifecycle_rule, index, bucket_json, objec
268293
op_func: async () => abort_mpus(candidates, object_sdk)
269294
});
270295
}
271-
await update_lifecycle_rules_last_sync(config_fs, bucket_json, rule_id, index);
272296
} catch (err) {
273297
dbg.error('process_rule failed with error', err, err.code, err.message);
274298
}
@@ -350,8 +374,9 @@ async function throw_if_noobaa_not_active(config_fs, system_json) {
350374
*/
351375
async function get_candidates(bucket_json, lifecycle_rule, object_sdk, fs_context) {
352376
const candidates = { abort_mpu_candidates: [], delete_candidates: [] };
377+
const rule_state = lifecycle_run_status.buckets_statuses[bucket_json.name].rules_statuses[lifecycle_rule.id]?.state || {};
353378
if (lifecycle_rule.expiration) {
354-
candidates.delete_candidates = await get_candidates_by_expiration_rule(lifecycle_rule, bucket_json, object_sdk);
379+
candidates.delete_candidates = await get_candidates_by_expiration_rule(lifecycle_rule, bucket_json, object_sdk, rule_state);
355380
if (lifecycle_rule.expiration.days || lifecycle_rule.expiration.expired_object_delete_marker) {
356381
const dm_candidates = await get_candidates_by_expiration_delete_marker_rule(lifecycle_rule, bucket_json);
357382
candidates.delete_candidates = candidates.delete_candidates.concat(dm_candidates);
@@ -365,6 +390,7 @@ async function get_candidates(bucket_json, lifecycle_rule, object_sdk, fs_contex
365390
candidates.abort_mpu_candidates = await get_candidates_by_abort_incomplete_multipart_upload_rule(
366391
lifecycle_rule, bucket_json, object_sdk, fs_context);
367392
}
393+
lifecycle_run_status.buckets_statuses[bucket_json.name].rules_statuses[lifecycle_rule.id].state = rule_state;
368394
return candidates;
369395
}
370396

@@ -376,15 +402,10 @@ async function get_candidates(bucket_json, lifecycle_rule, object_sdk, fs_contex
376402
* @returns {boolean}
377403
*/
378404
function validate_rule_enabled(rule, bucket) {
379-
const now = Date.now();
380405
if (rule.status !== 'Enabled') {
381406
dbg.log0('validate_rule_enabled: SKIP bucket:', bucket.name, '(bucket id:', bucket._id, ') rule', util.inspect(rule), 'not Enabled');
382407
return false;
383408
}
384-
if (rule.last_sync && now - rule.last_sync < config.LIFECYCLE_SCHEDULE_MIN) {
385-
dbg.log0('validate_rule_enabled: SKIP bucket:', bucket.name, '(bucket id:', bucket._id, ') rule', util.inspect(rule), 'now', now, 'last_sync', rule.last_sync, 'schedule min', config.LIFECYCLE_SCHEDULE_MIN);
386-
return false;
387-
}
388409
return true;
389410
}
390411

@@ -410,12 +431,12 @@ function _get_lifecycle_object_info_from_list_object_entry(entry) {
410431
* @param {Object} bucket_json
411432
* @returns {Promise<Object[]>}
412433
*/
413-
async function get_candidates_by_expiration_rule(lifecycle_rule, bucket_json, object_sdk) {
434+
async function get_candidates_by_expiration_rule(lifecycle_rule, bucket_json, object_sdk, rule_state) {
414435
const is_gpfs = nb_native().fs.gpfs;
415436
if (is_gpfs && config.NC_LIFECYCLE_GPFS_ILM_ENABLED) {
416437
return get_candidates_by_expiration_rule_gpfs(lifecycle_rule, bucket_json);
417438
} else {
418-
return get_candidates_by_expiration_rule_posix(lifecycle_rule, bucket_json, object_sdk);
439+
return get_candidates_by_expiration_rule_posix(lifecycle_rule, bucket_json, object_sdk, rule_state);
419440
}
420441
}
421442

@@ -436,21 +457,34 @@ async function get_candidates_by_expiration_rule_gpfs(lifecycle_rule, bucket_jso
436457
* @param {Object} bucket_json
437458
* @returns {Promise<Object[]>}
438459
*/
439-
async function get_candidates_by_expiration_rule_posix(lifecycle_rule, bucket_json, object_sdk) {
460+
async function get_candidates_by_expiration_rule_posix(lifecycle_rule, bucket_json, object_sdk, rule_state) {
440461
const expiration = _get_expiration_time(lifecycle_rule.expiration);
441462
if (expiration < 0) return [];
442463
const filter_func = _build_lifecycle_filter({filter: lifecycle_rule.filter, expiration});
443464

444465
const filtered_objects = [];
445466
// TODO list_objects does not accept a filter and works in batch sizes of 1000. should handle batching
446467
// also should maybe create a helper function or add argument for a filter in list object
447-
const objects_list = await object_sdk.list_objects({bucket: bucket_json.name, prefix: lifecycle_rule.filter?.prefix});
468+
const objects_list = await object_sdk.list_objects({
469+
bucket: bucket_json.name,
470+
prefix: lifecycle_rule.filter?.prefix,
471+
key_marker: rule_state.key_marker,
472+
limit: config.NC_LIFECYCLE_LIST_BATCH_SIZE
473+
});
448474
objects_list.objects.forEach(obj => {
449475
const object_info = _get_lifecycle_object_info_from_list_object_entry(obj);
450476
if (filter_func(object_info)) {
451477
filtered_objects.push({key: object_info.key});
452478
}
453479
});
480+
481+
const bucket_state = lifecycle_run_status.buckets_statuses[bucket_json.name].state;
482+
bucket_state.num_processed_objects += objects_list.objects.length;
483+
if (objects_list.is_truncated) {
484+
rule_state.key_marker = objects_list.next_marker;
485+
} else {
486+
rule_state.is_finished = true;
487+
}
454488
return filtered_objects;
455489

456490
}
@@ -630,26 +664,6 @@ function _file_contain_tags(object_info, filter_tags) {
630664
//////// STATUS HELPERS ////////
631665
/////////////////////////////////
632666

633-
/**
634-
* update_lifecycle_rules_last_sync updates the last sync time of the lifecycle rule
635-
* @param {import('../sdk/config_fs').ConfigFS} config_fs
636-
* @param {Object} bucket_json
637-
* @param {String} rule_id
638-
* @param {number} index
639-
* @returns {Promise<Void>}
640-
*/
641-
async function update_lifecycle_rules_last_sync(config_fs, bucket_json, rule_id, index) {
642-
bucket_json.lifecycle_configuration_rules[index].last_sync = Date.now();
643-
const { num_objects_deleted = 0, num_mpu_aborted = 0 } =
644-
lifecycle_run_status.buckets_statuses[bucket_json.name].rules_statuses[rule_id].rule_stats;
645-
// if (res.num_objects_deleted >= config.LIFECYCLE_BATCH_SIZE) should_rerun = true; // TODO - think if needed and add something about mpu abort
646-
dbg.log0('nc_lifecycle.update_lifecycle_rules_last_sync Done bucket:', bucket_json.name, '(bucket id:', bucket_json._id, ') done deletion of objects per rule',
647-
bucket_json.lifecycle_configuration_rules[index],
648-
'time:', bucket_json.lifecycle_configuration_rules[index].last_sync,
649-
'num_objects_deleted:', num_objects_deleted, 'num_mpu_aborted:', num_mpu_aborted);
650-
await config_fs.update_bucket_config_file(bucket_json);
651-
}
652-
653667
/**
654668
* _call_op_and_update_status calls the op and report time and error to the lifecycle status.
655669
*

0 commit comments

Comments
 (0)