]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
kr_cache_gc: separated DB transactions to prevent blocking; config; bugfix
authorLibor Peltan <libor.peltan@nic.cz>
Fri, 20 Apr 2018 11:42:54 +0000 (13:42 +0200)
committerPetr Špaček <petr.spacek@nic.cz>
Wed, 3 Jul 2019 13:28:17 +0000 (15:28 +0200)
utils/kr_cache_gc/kr_cache_gc.c
utils/kr_cache_gc/kr_cache_gc.h
utils/kr_cache_gc/main.c

index 772aed6341c62db11fa1945f0ad63a569e4c6a0a..802c9ed5065231dee56d700864e312af83085580 100644 (file)
@@ -12,6 +12,8 @@
 #include <lib/cache/impl.h>
 #include <lib/defines.h>
 
+#include "kr_cache_gc.h"
+
 // TODO remove and use time(NULL) ! this is just for debug with pre-generated cache
 int64_t now = 1523701784;
 
@@ -34,6 +36,14 @@ static double gc_timer_end(gc_timer_t *t)
        return (((double)end.tv_sec - (double)start->tv_sec) + ((double)end.tv_nsec - (double)start->tv_nsec) / 1e9);
 }
 
+static unsigned long gc_timer_usecs(gc_timer_t *t)
+{
+       gc_timer_t *start = t == NULL ? &gc_timer_internal : t;
+       gc_timer_t end = { 0 };
+       (void)clock_gettime(CLOCK_MONOTONIC, &end);
+       return ((end.tv_sec - start->tv_sec) * 1000000UL + (end.tv_nsec - start->tv_nsec) / 1000UL);
+}
+
 // section: function key_consistent
 
 static const uint16_t *key_consistent(knot_db_val_t key)
@@ -91,6 +101,19 @@ static knot_db_t *knot_db_t_kres2libknot(const knot_db_t *db)
        return libknot_db;
 }
 
+// section: dbval_copy
+
+static knot_db_val_t *dbval_copy(const knot_db_val_t *from)
+{
+       knot_db_val_t *to = malloc(sizeof(knot_db_val_t) + from->len);
+       if (to != NULL) {
+               memcpy(to, from, sizeof(knot_db_val_t));
+               to->data = to + 1; // == ((uit8_t *)to) + sizeof(knot_db_val_t)
+               memcpy(to->data, from->data, from->len);
+       }
+       return to;
+}
+
 // section: rrtype list
 
 dynarray_declare(rrtype, uint16_t, DYNARRAY_VISIBILITY_STATIC, 64);
@@ -122,24 +145,24 @@ static void rrtypelist_print(rrtype_dynarray_t *arr)
 
 // section: main
 
-dynarray_declare(entry, knot_db_val_t, DYNARRAY_VISIBILITY_STATIC, 256);
-dynarray_define(entry, knot_db_val_t, DYNARRAY_VISIBILITY_STATIC);
+dynarray_declare(entry, knot_db_val_t*, DYNARRAY_VISIBILITY_STATIC, 256);
+dynarray_define(entry, knot_db_val_t*, DYNARRAY_VISIBILITY_STATIC);
 
 
-int kr_cache_gc(const char *cache)
+int kr_cache_gc(kr_cache_gc_cfg_t *cfg)
 {
-       char cache_data[strlen(cache) + 10];
-       snprintf(cache_data, sizeof(cache_data), "%s/data.mdb", cache);
+       char cache_data[strlen(cfg->cache_path) + 10];
+       snprintf(cache_data, sizeof(cache_data), "%s/data.mdb", cfg->cache_path);
 
        struct stat st = { 0 };
-       if (stat(cache, &st) || !(st.st_mode & S_IFDIR) || stat(cache_data, &st)) {
-               printf("Error: %s does not exist or is not a LMDB.\n", cache);
+       if (stat(cfg->cache_path, &st) || !(st.st_mode & S_IFDIR) || stat(cache_data, &st)) {
+               printf("Error: %s does not exist or is not a LMDB.\n", cfg->cache_path);
                return -ENOENT;
        }
 
        size_t cache_size = st.st_size;
 
-       struct kr_cdb_opts opts = { cache, cache_size };
+       struct kr_cdb_opts opts = { cfg->cache_path, cache_size };
        struct kr_cache krc = { 0 };
 
        int ret = kr_cache_open(&krc, NULL, &opts, NULL);
@@ -148,8 +171,14 @@ int kr_cache_gc(const char *cache)
                return -EINVAL;
        }
 
+       entry_dynarray_t to_del = { 0 };
+       rrtype_dynarray_t cache_rrtypes = { 0 };
+
+       gc_timer_t timer_analyze = { 0 }, timer_delete = { 0 }, timer_rw_txn = { 0 };
+
        const knot_db_api_t *api = knot_db_lmdb_api();
        knot_db_txn_t txn = { 0 };
+       knot_db_iter_t *it = NULL;
        knot_db_t *db = knot_db_t_kres2libknot(krc.db);
        if (db == NULL) {
                printf("Out of memory.\n");
@@ -160,13 +189,14 @@ int kr_cache_gc(const char *cache)
        size_t real_size = knot_db_lmdb_get_mapsize(db), usage = knot_db_lmdb_get_usage(db);
        printf("Cache size: %zu, Usage: %zu (%.2lf%%)\n", real_size, usage, (double)usage / real_size * 100.0);
 
-       ret = api->txn_begin(db, &txn, 0);
+       gc_timer_start(&timer_analyze);
+
+       ret = api->txn_begin(db, &txn, KNOT_DB_RDONLY);
        if (ret != KNOT_EOK) {
                printf("Error starting DB transaction (%s).\n", knot_strerror(ret));
                goto fail;
        }
 
-       knot_db_iter_t *it = NULL;
        it = api->iter_begin(&txn, KNOT_DB_FIRST);
        if (it == NULL) {
                printf("Error iterating DB.\n");
@@ -174,10 +204,9 @@ int kr_cache_gc(const char *cache)
                goto fail;
        }
 
-       entry_dynarray_t to_del = { 0 };
-       rrtype_dynarray_t cache_rrtypes = { 0 };
-       gc_timer_start(NULL);
        size_t cache_records = 0, deleted_records = 0;
+       size_t oversize_records = 0, already_gone = 0;;
+       size_t used_space = 0, rw_txn_count = 1;
 
        while (it != NULL) {
                knot_db_val_t key = { 0 }, val = { 0 };
@@ -194,44 +223,87 @@ int kr_cache_gc(const char *cache)
                        int64_t over = entry->time + entry->ttl;
                        over -= now;
                        if (over < 0) {
-                               entry_dynarray_add(&to_del, &key);
+                               knot_db_val_t *todelete;
+                               if ((cfg->temp_keys_space > 0 &&
+                                    used_space + key.len + sizeof(key) > cfg->temp_keys_space) ||
+                                   (todelete = dbval_copy(&key)) == NULL) {
+                                       oversize_records++;
+                               } else {
+                                       used_space += todelete->len + sizeof(*todelete);
+                                       entry_dynarray_add(&to_del, &todelete);
+                               }
                        }
                }
 
                it = api->iter_next(it);
        }
 
-       printf("Cache analyzed in %.2lf secs, %zu records types", gc_timer_end(NULL), cache_records);
+       api->txn_abort(&txn);
+
+       printf("Cache analyzed in %.2lf secs, %zu records types", gc_timer_end(&timer_analyze), cache_records);
        rrtypelist_print(&cache_rrtypes);
+       printf("%zu records to be deleted using %.2lf MBytes of temporary memory, %zu records skipped due to memory limit.\n",
+              to_del.size, ((double)used_space / 1048576.0), oversize_records);
        rrtype_dynarray_free(&cache_rrtypes);
 
-       gc_timer_start(NULL);
+       gc_timer_start(&timer_delete);
+       gc_timer_start(&timer_rw_txn);
        rrtype_dynarray_t deleted_rrtypes = { 0 };
 
-       dynarray_foreach(entry, knot_db_val_t, i, to_del) {
-               ret = api->del(&txn, i);
-               if (ret != KNOT_EOK) {
-                       printf("Warning: skipping deleting because of error (%s)\n", knot_strerror(ret));
-               } else {
+       ret = api->txn_begin(db, &txn, 0);
+       if (ret != KNOT_EOK) {
+               printf("Error starting DB transaction (%s).\n", knot_strerror(ret));
+               goto fail;
+       }
+
+       dynarray_foreach(entry, knot_db_val_t*, i, to_del) {
+               ret = api->del(&txn, *i);
+               switch (ret) {
+               case KNOT_EOK:
                        deleted_records++;
-                       const uint16_t *entry_type = ret == KNOT_EOK ? key_consistent(*i) : NULL;
+                       const uint16_t *entry_type = ret == KNOT_EOK ? key_consistent(**i) : NULL;
                        assert(entry_type != NULL);
                        rrtypelist_add(&deleted_rrtypes, *entry_type);
+                       break;
+               case KNOT_ENOENT:
+                       already_gone++;
+                       break;
+               default:
+                       printf("Warning: skipping deleting because of error (%s)\n", knot_strerror(ret));
+                       continue;
+               }
+               if ((cfg->rw_txn_items > 0 &&
+                    (deleted_records + already_gone) % cfg->rw_txn_items == 0) ||
+                   (cfg->rw_txn_duration > 0 &&
+                    gc_timer_usecs(&timer_rw_txn) > cfg->rw_txn_duration)) {
+                       ret = api->txn_commit(&txn);
+                       if (ret == KNOT_EOK) {
+                               rw_txn_count++;
+                               usleep(cfg->rw_txn_delay);
+                               gc_timer_start(&timer_rw_txn);
+                               ret = api->txn_begin(db, &txn, 0);
+                       }
+                       if (ret != KNOT_EOK) {
+                               printf("Error restarting DB transaction (%s)\n", knot_strerror(ret));
+                               goto fail;
+                       }
                }
        }
 
-       printf("Deleted in %.2lf secs %zu records types", gc_timer_end(NULL), deleted_records);
+       printf("Deleted %zu records (%zu already gone) types", deleted_records, already_gone);
        rrtypelist_print(&deleted_rrtypes);
-       rrtype_dynarray_free(&deleted_rrtypes);
-
-       entry_dynarray_free(&to_del);
+       printf("It took %.2lf secs, %zu transactions \n", gc_timer_end(&timer_delete), rw_txn_count);
 
-       //api->iter_finish(it);
-       //it = NULL;
        ret = api->txn_commit(&txn);
        txn.txn = NULL;
 
 fail:
+       rrtype_dynarray_free(&deleted_rrtypes);
+       dynarray_foreach(entry, knot_db_val_t*, i, to_del) {
+               free(*i);
+       }
+       entry_dynarray_free(&to_del);
+
        api->iter_finish(it);
        if (txn.txn) {
                api->txn_abort(&txn);
index b492f8b3b71216e3dedee062fda99f0ce22934f6..8c2e3b2e4fe92c41639f177297428ac96855f334 100644 (file)
@@ -1,3 +1,19 @@
-int kr_cache_gc(const char *cache);
+#pragma once
+
+#include <stddef.h>
+
+typedef struct {
+        const char *cache_path;          // path to the LMDB with resolver cache
+        unsigned long gc_interval;       // waiting time between two whole garbage collections in usecs (0 = just one-time cleanup)
+
+        size_t temp_keys_space;          // maximum amount of temporary memory for copied keys in bytes (0 = unlimited)
+
+        size_t rw_txn_items;             // maximum number of deleted records per RW transaction (0 = unlimited)
+        unsigned long rw_txn_duration;   // maximum duration of RW transaction in usecs (0 = unlimited)
+        unsigned long rw_txn_delay;      // waiting time between two RW transactions in usecs
+} kr_cache_gc_cfg_t;
+
+
+int kr_cache_gc(kr_cache_gc_cfg_t *cfg);
 
 #define KR_CACHE_GC_VERSION "0.1"
index bacea1742e3716fa986d08da122c11e234bf9e0d..808f66bbf8b774d2ba6697f7f2846c45645b5fd9 100644 (file)
@@ -28,7 +28,13 @@ static void got_killed(int signum)
 
 static void print_help()
 {
-       printf("Usage: kr_cache_gc -c <resolver_cache> [ -d <garbage_interval(ms)> ]\n");
+       printf("Usage: kr_cache_gc -c <resolver_cache> [ optional params... ]\n");
+       printf("Optional params:\n");
+       printf(" -d <garbage_interval(millis)>\n");
+       printf(" -l <deletes_per_txn>\n");
+       printf(" -m <rw_txn_duration(usecs)>\n");
+       printf(" -w <wait_next_rw_txn(usecs)>\n");
+       printf(" -t <temporary_memory(MBytes)>\n");
 }
 
 int main(int argc, char *argv[])
@@ -41,22 +47,33 @@ int main(int argc, char *argv[])
        signal(SIGCHLD, got_killed);
        signal(SIGINT, got_killed);
 
-       const char *cache_path = NULL;
-       unsigned long interval = 0;
+       kr_cache_gc_cfg_t cfg = { 0 };
 
        int o;
-       while ((o = getopt(argc, argv, "hc:d:")) != -1) {
+       while ((o = getopt(argc, argv, "hc:d:l:m:w:t:")) != -1) {
                switch (o) {
                case 'c':
-                       cache_path = optarg;
+                       cfg.cache_path = optarg;
                        break;
+#define get_nonneg_optarg(to) do { if (atol(optarg) < 0) { print_help(); return 2; } to = atol(optarg); } while (0)
                case 'd':
-                       if (atol(optarg) < 0) {
-                               print_help();
-                               return 2;
-                       }
-                       interval = atol(optarg) * 1000;
+                       get_nonneg_optarg(cfg.gc_interval);
+                       cfg.gc_interval *= 1000;
                        break;
+               case 'l':
+                       get_nonneg_optarg(cfg.rw_txn_items);
+                       break;
+               case 'm':
+                       get_nonneg_optarg(cfg.rw_txn_duration);
+                       break;
+               case 'w':
+                       get_nonneg_optarg(cfg.rw_txn_delay);
+                       break;
+               case 't':
+                       get_nonneg_optarg(cfg.temp_keys_space);
+                       cfg.temp_keys_space *= 1048576;
+                       break;
+#undef get_nonneg_optarg
                case ':':
                case '?':
                case 'h':
@@ -67,20 +84,20 @@ int main(int argc, char *argv[])
                }
        }
 
-       if (cache_path == NULL) {
+       if (cfg.cache_path == NULL) {
                print_help();
                return 1;
        }
 
        do {
-               int ret = kr_cache_gc(cache_path);
+               int ret = kr_cache_gc(&cfg);
                if (ret) {
                        printf("Error (%s)\n", kr_strerror(ret));
                        return 10;
                }
 
-               usleep(interval);
-       } while (interval > 0 && !killed);
+               usleep(cfg.gc_interval);
+       } while (cfg.gc_interval > 0 && !killed);
 
        return 0;
 }