]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
WIP: GC fixes
authorVladimír Čunát <vladimir.cunat@nic.cz>
Tue, 4 May 2021 18:48:45 +0000 (20:48 +0200)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Tue, 4 May 2021 18:48:45 +0000 (20:48 +0200)
- factor out cdb_compat.h
- knot_db_api_t for mdbx - at least most of it,
  so we don't have to make significant changes in GC code
  It's mostly copied from libknot.

lib/cache/cdb_compat.c [new file with mode: 0644]
lib/cache/cdb_compat.h [new file with mode: 0644]
lib/cache/cdb_lmdb.c
lib/cache/cdb_lmdb.h
lib/meson.build
utils/cache_gc/db.c
utils/cache_gc/db.h
utils/cache_gc/kr_cache_gc.c

diff --git a/lib/cache/cdb_compat.c b/lib/cache/cdb_compat.c
new file mode 100644 (file)
index 0000000..b15f671
--- /dev/null
@@ -0,0 +1,285 @@
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *  SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "lib/cache/cdb_compat.h"
+#include "lib/cache/cdb_lmdb.h"
+
+#if KR_USE_MDBX
+       #include <libknot/error.h>
+       #include <stdlib.h>
+       #include <string.h>
+#else
+       #include <libknot/db/db_lmdb.h>
+#endif
+
+#if !KR_USE_MDBX
+const knot_db_api_t *kr_cdb_pt2knot_db_api_t(kr_cdb_pt db)
+{
+       return knot_db_lmdb_api();
+}
+#endif
+
+/* The following majority of this C file has only minor differences
+ * from Knot's src/libknot/db/db_lmdb.c
+ * Some mdbx differences get fully covered by cdb_compat.h defines, some don't.
+ */
+#if KR_USE_MDBX
+
+/*! Beware: same name but different struct than in resolver's cdb_lmdb.c */
+struct lmdb_env
+{
+       bool shared;
+       MDB_dbi dbi;
+       MDB_env *env;
+       knot_mm_t *pool;
+};
+
+/*!
+ * \brief Convert error code returned by LMDB to Knot DNS error code.
+ *
+ * LMDB defines own error codes but uses additional ones from libc:
+ * - LMDB errors do not conflict with Knot DNS ones.
+ * - Significant LMDB errors are mapped to Knot DNS ones.
+ * - Standard errors are converted to negative value to match Knot DNS mapping.
+ */
+static int lmdb_error_to_knot(int error)
+{
+       if (error == MDB_SUCCESS) {
+               return KNOT_EOK;
+       }
+
+       if (error == MDB_NOTFOUND) {
+               return KNOT_ENOENT;
+       }
+
+       if (error == MDB_TXN_FULL) {
+               return KNOT_ELIMIT;
+       }
+
+       if (error == MDB_MAP_FULL || error == ENOSPC) {
+               return KNOT_ESPACE;
+       }
+
+       return -abs(error);
+}
+
+static
+int knot_db_lmdb_txn_begin(knot_db_t *db, knot_db_txn_t *txn, knot_db_txn_t *parent,
+                           unsigned flags)
+{
+       txn->db = db;
+       txn->txn = NULL;
+
+       unsigned txn_flags = 0;
+       if (flags & KNOT_DB_RDONLY) {
+               txn_flags |= MDB_RDONLY;
+       } else {
+               txn_flags |= MDB_RDWR;
+       }
+
+       MDB_txn *parent_txn = (parent != NULL) ? (MDB_txn *)parent->txn : NULL;
+
+       struct lmdb_env *env = db;
+       int ret = mdb_txn_begin(env->env, parent_txn, txn_flags, (MDB_txn **)&txn->txn);
+       if (ret != MDB_SUCCESS) {
+               return lmdb_error_to_knot(ret);
+       }
+
+       return KNOT_EOK;
+}
+
+static int txn_begin(knot_db_t *db, knot_db_txn_t *txn, unsigned flags)
+{
+       return knot_db_lmdb_txn_begin(db, txn, NULL, flags);
+}
+
+static int txn_commit(knot_db_txn_t *txn)
+{
+       int ret = mdb_txn_commit((MDB_txn *)txn->txn);
+       if (ret != MDB_SUCCESS) {
+               return lmdb_error_to_knot(ret);
+       }
+
+       return KNOT_EOK;
+}
+
+static void txn_abort(knot_db_txn_t *txn)
+{
+       mdb_txn_abort((MDB_txn *)txn->txn);
+}
+
+static knot_db_iter_t *iter_set(knot_db_iter_t *iter, knot_db_val_t *key, unsigned flags)
+{
+       MDB_cursor *cursor = iter;
+
+       MDBX_cursor_op op = MDBX_SET;
+       switch(flags) {
+       case KNOT_DB_NOOP:  return cursor;
+       case KNOT_DB_FIRST: op = MDBX_FIRST; break;
+       case KNOT_DB_LAST:  op = MDBX_LAST;  break;
+       case KNOT_DB_NEXT:  op = MDBX_NEXT; break;
+       case KNOT_DB_PREV:  op = MDBX_PREV; break;
+       case KNOT_DB_LEQ:
+       case KNOT_DB_GEQ:   op = MDBX_SET_RANGE; break;
+       default: break;
+       }
+
+       MDB_val db_key = { 0 };
+       if (key) {
+               db_key = val_knot2mdb(*key);
+       }
+       MDB_val unused_key = { 0 }, unused_val = { 0 };
+
+       int ret = mdb_cursor_get(cursor, key ? &db_key : &unused_key, &unused_val, op);
+
+       /* LEQ is not supported in LMDB, workaround using GEQ. */
+       if (flags == KNOT_DB_LEQ && key) {
+               /* Searched key is after the last key. */
+               if (ret != MDB_SUCCESS) {
+                       return iter_set(iter, NULL, KNOT_DB_LAST);
+               }
+               /* If the searched key != matched, get previous. */
+               if ((key->len != db_key.iov_len) ||
+                   (memcmp(key->data, db_key.iov_base, key->len) != 0)) {
+                       return iter_set(iter, NULL, KNOT_DB_PREV);
+               }
+       }
+
+       if (ret != MDB_SUCCESS) {
+               mdb_cursor_close(cursor);
+               return NULL;
+       }
+
+       return cursor;
+}
+
+static knot_db_iter_t *iter_begin(knot_db_txn_t *txn, unsigned flags)
+{
+       struct lmdb_env *env = txn->db;
+       MDB_cursor *cursor = NULL;
+
+       int ret = mdb_cursor_open(txn->txn, env->dbi, &cursor);
+       if (ret != MDB_SUCCESS) {
+               return NULL;
+       }
+
+       /* Clear sorted flag, as it's always sorted. */
+       flags &= ~KNOT_DB_SORTED;
+
+       return iter_set(cursor, NULL, (flags == 0) ? KNOT_DB_FIRST : flags);
+}
+
+static knot_db_iter_t *iter_next(knot_db_iter_t *iter)
+{
+       return iter_set(iter, NULL, KNOT_DB_NEXT);
+}
+
+static int iter_key(knot_db_iter_t *iter, knot_db_val_t *key)
+{
+       MDB_cursor *cursor = iter;
+
+       MDB_val mdb_key, mdb_val;
+       int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT);
+       if (ret != MDB_SUCCESS) {
+               return lmdb_error_to_knot(ret);
+       }
+
+       *key = val_mdb2knot(mdb_key);
+       return KNOT_EOK;
+}
+
+static int iter_val(knot_db_iter_t *iter, knot_db_val_t *val)
+{
+       MDB_cursor *cursor = iter;
+
+       MDB_val mdb_key, mdb_val;
+       int ret = mdb_cursor_get(cursor, &mdb_key, &mdb_val, MDB_GET_CURRENT);
+       if (ret != MDB_SUCCESS) {
+               return lmdb_error_to_knot(ret);
+       }
+
+       *val = val_mdb2knot(mdb_val);
+       return KNOT_EOK;
+}
+
+static void iter_finish(knot_db_iter_t *iter)
+{
+       if (iter == NULL) {
+               return;
+       }
+
+       MDB_cursor *cursor = iter;
+       mdb_cursor_close(cursor);
+}
+
+static int find(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags)
+{
+       knot_db_iter_t *iter = iter_begin(txn, KNOT_DB_NOOP);
+       if (iter == NULL) {
+               return KNOT_ERROR;
+       }
+
+       int ret = KNOT_EOK;
+       if (iter_set(iter, key, flags) == NULL) {
+               return KNOT_ENOENT;
+       } else {
+               ret = iter_val(iter, val);
+       }
+
+       iter_finish(iter);
+       return ret;
+}
+
+static int insert(knot_db_txn_t *txn, knot_db_val_t *key, knot_db_val_t *val, unsigned flags)
+{
+       struct lmdb_env *env = txn->db;
+
+       MDB_val db_key = val_knot2mdb(*key);
+       MDB_val data = val_knot2mdb(*val);
+
+       /* Reserve if only size is declared. */
+       unsigned mdb_flags = 0;
+       if (val->len > 0 && val->data == NULL) {
+               mdb_flags |= MDB_RESERVE;
+       }
+
+       int ret = mdb_put(txn->txn, env->dbi, &db_key, &data, mdb_flags);
+       if (ret != MDB_SUCCESS) {
+               return lmdb_error_to_knot(ret);
+       }
+
+       /* Update the result. */
+       *val = val_mdb2knot(data);
+       return KNOT_EOK;
+}
+
+static int del(knot_db_txn_t *txn, knot_db_val_t *key)
+{
+       struct lmdb_env *env = txn->db;
+       MDB_val db_key = val_knot2mdb(*key);
+       MDB_val data = { 0 };
+
+       int ret = mdb_del(txn->txn, env->dbi, &db_key, &data);
+       if (ret != MDB_SUCCESS) {
+               return lmdb_error_to_knot(ret);
+       }
+
+       return KNOT_EOK;
+}
+
+const knot_db_api_t *kr_cdb_pt2knot_db_api_t(kr_cdb_pt db)
+{
+       static const knot_db_api_t api = {
+               "mdbx",
+               NULL, NULL, //init, deinit,
+               txn_begin, txn_commit, txn_abort,
+               NULL, NULL, //count, clear,
+               find, insert, del,
+               iter_begin, iter_set, iter_next, iter_key, iter_val, iter_finish
+       };
+       return &api;
+}
+
+#endif // KR_USE_MDBX
+
diff --git a/lib/cache/cdb_compat.h b/lib/cache/cdb_compat.h
new file mode 100644 (file)
index 0000000..f851b48
--- /dev/null
@@ -0,0 +1,96 @@
+/*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ *  SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libknot/db/db.h>
+
+#if KR_USE_MDBX
+       #include <mdbx.h>
+       // FIXME: investigate mysterious constant-sized memleaks
+#else
+       #include <lmdb.h>
+#endif
+
+#if KR_USE_MDBX
+       #define MDB_env MDBX_env
+       #define mdb_env_create mdbx_env_create
+       #define MDB_SUCCESS 0
+       #define MDB_WRITEMAP MDBX_WRITEMAP
+       #define MDB_MAPASYNC MDBX_UTTERLY_NOSYNC
+       #define MDB_NOTLS MDBX_NOTLS
+       #define mdb_env_open mdbx_env_open
+       #define mdb_filehandle_t mdbx_filehandle_t
+       #define mdb_env_get_fd mdbx_env_get_fd
+       #define MDB_txn MDBX_txn
+       #define mdb_txn_begin mdbx_txn_begin
+       #define mdb_dbi_open mdbx_dbi_open
+       #define MDB_dbi MDBX_dbi
+       #define mdb_txn_commit mdbx_txn_commit
+       #define mdb_env_close mdbx_env_close
+       // Avoid mdbx_env_sync() as it uses some macro magic.
+       #define mdb_env_sync(env, force) mdbx_env_sync_ex((env), (force), false)
+       #define mdb_dbi_close mdbx_dbi_close
+       #define MDB_cursor MDBX_cursor
+       #define MDB_NOTFOUND MDBX_NOTFOUND
+       #define MDB_MAP_FULL MDBX_MAP_FULL
+       #define MDB_TXN_FULL MDBX_TXN_FULL
+       #define mdb_strerror mdbx_strerror
+       // Different field names as well; see val_mdb2knot().
+       #define MDB_val MDBX_val
+       // TODO: can be improved
+       #define mdb_env_set_mapsize mdbx_env_set_mapsize
+       #define MDB_RDONLY MDBX_TXN_RDONLY
+       #define mdb_txn_renew mdbx_txn_renew
+       #define MDB_READERS_FULL MDBX_READERS_FULL
+       #define mdb_reader_check mdbx_reader_check
+       #define mdb_txn_reset mdbx_txn_reset
+       #define mdb_cursor_renew mdbx_cursor_renew
+       #define mdb_cursor_open mdbx_cursor_open
+       #define mdb_cursor_close mdbx_cursor_close
+       #define mdb_cursor_get mdbx_cursor_get
+       #define MDB_SET_RANGE MDBX_SET_RANGE
+       #define mdb_txn_abort mdbx_txn_abort
+       #define MDB_PREV MDBX_PREV
+       #define MDB_NEXT MDBX_NEXT
+       #define mdb_env_get_path mdbx_env_get_path
+       #define mdb_del mdbx_del
+       #define MDB_RESERVE MDBX_RESERVE
+       #define mdb_put mdbx_put
+       #define mdb_get mdbx_get
+       #define mdb_drop mdbx_drop
+       // just an extra field at the end
+       #define MDB_stat MDBX_stat
+       #define MDB_RDWR MDBX_TXN_READWRITE
+       #define MDB_DATANAME MDBX_DATANAME
+       #define mdb_env_info(env, info) \
+               mdbx_env_info_ex((env), NULL, (info), sizeof(MDBX_envinfo))
+       #define MDB_GET_CURRENT MDBX_GET_CURRENT
+#else
+       #define MDB_RDWR 0
+       #define MDB_DATANAME "/data.mdb"
+#endif
+
+/** Conversion between knot and lmdb structs for values. */
+static inline knot_db_val_t val_mdb2knot(MDB_val v)
+{
+       return (knot_db_val_t){
+       #if KR_USE_MDBX
+               .len = v.iov_len, .data = v.iov_base
+       #else
+               .len = v.mv_size, .data = v.mv_data
+       #endif
+       };
+}
+static inline MDB_val val_knot2mdb(knot_db_val_t v)
+{
+       return (MDB_val){
+       #if KR_USE_MDBX
+               .iov_len = v.len, .iov_base = v.data
+       #else
+               .mv_size = v.len, .mv_data = v.data
+       #endif
+       };
+}
+
index 8cc30e222eb783af81d8620cc9e30532ffc0cf91..ca98878feb60a14d9b9d81e0c73e0cea41edd495 100644 (file)
 #include "lib/cache/impl.h"
 #include "lib/utils.h"
 
+#include "lib/cache/cdb_compat.h"
 
 /* Defines */
 #define LMDB_DIR_MODE   0770
 #define LMDB_FILE_MODE  0660
 
-#if KR_USE_MDBX
-       #include <mdbx.h>
-       // FIXME: investigate mysterious constant-sized memleaks
-#else
-       #include <lmdb.h>
-#endif
-
-#if KR_USE_MDBX
-       #define MDB_env MDBX_env
-       #define mdb_env_create mdbx_env_create
-       #define MDB_SUCCESS 0
-       #define MDB_WRITEMAP MDBX_WRITEMAP
-       #define MDB_MAPASYNC MDBX_UTTERLY_NOSYNC
-       #define MDB_NOTLS MDBX_NOTLS
-       #define mdb_env_open mdbx_env_open
-       #define mdb_filehandle_t mdbx_filehandle_t
-       #define mdb_env_get_fd mdbx_env_get_fd
-       #define MDB_txn MDBX_txn
-       #define mdb_txn_begin mdbx_txn_begin
-       #define mdb_dbi_open mdbx_dbi_open
-       #define MDB_dbi MDBX_dbi
-       #define mdb_txn_commit mdbx_txn_commit
-       #define mdb_env_close mdbx_env_close
-       // Avoid mdbx_env_sync() as it uses some macro magic.
-       #define mdb_env_sync(env, force) mdbx_env_sync_ex((env), (force), false)
-       #define mdb_dbi_close mdbx_dbi_close
-       #define MDB_cursor MDBX_cursor
-       #define MDB_NOTFOUND MDBX_NOTFOUND
-       #define MDB_MAP_FULL MDBX_MAP_FULL
-       #define MDB_TXN_FULL MDBX_TXN_FULL
-       #define mdb_strerror mdbx_strerror
-       // Different field names as well; see val_mdb2knot().
-       #define MDB_val MDBX_val
-       // TODO: can be improved
-       #define mdb_env_set_mapsize mdbx_env_set_mapsize
-       #define MDB_RDONLY MDBX_TXN_RDONLY
-       #define mdb_txn_renew mdbx_txn_renew
-       #define MDB_READERS_FULL MDBX_READERS_FULL
-       #define mdb_reader_check mdbx_reader_check
-       #define mdb_txn_reset mdbx_txn_reset
-       #define mdb_cursor_renew mdbx_cursor_renew
-       #define mdb_cursor_open mdbx_cursor_open
-       #define mdb_cursor_close mdbx_cursor_close
-       #define mdb_cursor_get mdbx_cursor_get
-       #define MDB_SET_RANGE MDBX_SET_RANGE
-       #define mdb_txn_abort mdbx_txn_abort
-       #define MDB_PREV MDBX_PREV
-       #define MDB_NEXT MDBX_NEXT
-       #define mdb_env_get_path mdbx_env_get_path
-       #define mdb_del mdbx_del
-       #define MDB_RESERVE MDBX_RESERVE
-       #define mdb_put mdbx_put
-       #define mdb_get mdbx_get
-       #define mdb_drop mdbx_drop
-       // just an extra field at the end
-       #define MDB_stat MDBX_stat
-       #define MDB_RDWR MDBX_TXN_READWRITE
-       #define MDB_DATANAME MDBX_DATANAME
-       #define mdb_env_info(env, info) \
-               mdbx_env_info_ex((env), NULL, (info), sizeof(MDBX_envinfo))
-#else
-       #define MDB_RDWR 0
-       #define MDB_DATANAME "/data.mdb"
-#endif
 
 /* TODO: we rely on mirrors of these two structs not changing layout
  * in libknot and knot resolver! */
@@ -157,28 +94,6 @@ static int lmdb_error(int error)
        }
 }
 
-/** Conversion between knot and lmdb structs for values. */
-static inline knot_db_val_t val_mdb2knot(MDB_val v)
-{
-       return (knot_db_val_t){
-       #if KR_USE_MDBX
-               .len = v.iov_len, .data = v.iov_base
-       #else
-               .len = v.mv_size, .data = v.mv_data
-       #endif
-       };
-}
-static inline MDB_val val_knot2mdb(knot_db_val_t v)
-{
-       return (MDB_val){
-       #if KR_USE_MDBX
-               .iov_len = v.len, .iov_base = v.data
-       #else
-               .mv_size = v.len, .mv_data = v.data
-       #endif
-       };
-}
-
 /** Refresh mapsize value from file, including env->mapsize.
  * It's much lighter than reopen_env(). */
 static int refresh_mapsize(struct lmdb_env *env)
@@ -938,10 +853,6 @@ static double cdb_usage_percent(kr_cdb_pt db)
 /** Conversion between knot and lmdb structs. */
 knot_db_t *kr_cdb_pt2knot_db_t(kr_cdb_pt db)
 {
-#if KR_USE_MDBX
-       #pragma GCC warning "FIXME: we can't convert mdbx to libknot_lmdb_env; GC needs rewriting"
-       abort();
-#endif
        const struct lmdb_env *kres_db = db2env(db);
        struct libknot_lmdb_env *libknot_db = malloc(sizeof(*libknot_db));
        if (libknot_db != NULL) {
@@ -968,3 +879,4 @@ const struct kr_cdb_api *kr_cdb_lmdb(void)
 
        return &api;
 }
+
index 3429a222c7288ab881bb9e1d006e9c60a494f30f..74d9cac677cb859d14cff71c9fbcef0aa9ed47df 100644 (file)
 KR_EXPORT KR_CONST
 const struct kr_cdb_api *kr_cdb_lmdb(void);
 
-/** Create a pointer for knot_db_lmdb_api.  You free() it to release it. */
+/** Create a pointer for knot_db_api.  You free() it to release it. */
 KR_EXPORT
 knot_db_t *kr_cdb_pt2knot_db_t(kr_cdb_pt db);
 
+/** Get a pointer for knot_db_api.  You don't release it.
+ *
+ * Some operations aren't generally supported: init, deinit, count, clear.
+ */
+KR_EXPORT
+const knot_db_api_t *kr_cdb_pt2knot_db_api_t(kr_cdb_pt db);
+
index 9cb3f6c699ba1ca218fcac2be0589521884c4d7f..0e47e4079959791482ae20719faf5f432f103dd3 100644 (file)
@@ -3,6 +3,7 @@
 
 libkres_src = files([
   'cache/api.c',
+  'cache/cdb_compat.c',
   'cache/cdb_lmdb.c',
   'cache/entry_list.c',
   'cache/entry_pkt.c',
@@ -37,6 +38,7 @@ c_src_lint += libkres_src
 libkres_headers = files([
   'cache/api.h',
   'cache/cdb_api.h',
+  'cache/cdb_compat.h',
   'cache/cdb_lmdb.h',
   'cache/impl.h',
   'defines.h',
index b6bfaacab6a82dbd142d2665691b903fd0517a0d..bf7c3f0f2e351c8bbc47b65902c21433c9ee4a8a 100644 (file)
@@ -157,8 +157,8 @@ static const struct entry_h *val2entry(const knot_db_val_t val, uint16_t ktype)
        return NULL;
 }
 
-int kr_gc_cache_iter(knot_db_t * knot_db, const  kr_cache_gc_cfg_t *cfg,
-                       kr_gc_iter_callback callback, void *ctx)
+int kr_gc_cache_iter(const knot_db_api_t * api, knot_db_t * knot_db,
+               const  kr_cache_gc_cfg_t *cfg, kr_gc_iter_callback callback, void *ctx)
 {
        unsigned int counter_iter = 0;
        unsigned int counter_gc_consistent = 0;
@@ -166,7 +166,6 @@ int kr_gc_cache_iter(knot_db_t * knot_db, const  kr_cache_gc_cfg_t *cfg,
 
        knot_db_txn_t txn = { 0 };
        knot_db_iter_t *it = NULL;
-       const knot_db_api_t *api = knot_db_lmdb_api();
        gc_record_info_t info = { 0 };
        int64_t now = time(NULL);
 
index 48dc101a84bd37169c163d44cf756151f1d696f5..20542fbde60d22f2bf5bf0b8931f50a69f62e2ee 100644 (file)
@@ -18,8 +18,8 @@ void kr_gc_cache_close(struct kr_cache *kres_db, knot_db_t * knot_db);
 typedef int (*kr_gc_iter_callback)(const knot_db_val_t * key,
                                   gc_record_info_t * info, void *ctx);
 
-int kr_gc_cache_iter(knot_db_t * knot_db, const  kr_cache_gc_cfg_t *cfg,
-                       kr_gc_iter_callback callback, void *ctx);
+int kr_gc_cache_iter(const knot_db_api_t * api, knot_db_t * knot_db,
+               const  kr_cache_gc_cfg_t *cfg, kr_gc_iter_callback callback, void *ctx);
 
 /** Return RR type corresponding to the key or negative error code.
  *
index 068c457e5c72b99b182977f29219ca35fe338393..2fc81e24d28b36eac44524ff8b16a0474939016a 100644 (file)
@@ -194,6 +194,7 @@ int kr_cache_gc(kr_cache_gc_cfg_t *cfg, kr_cache_gc_state_t **state)
                return KNOT_EOK;
        }
 
+       const knot_db_api_t *api = kr_cdb_pt2knot_db_api_t((*state)->kres_db.db);
        //// 2. classify all cache items into categories
        //      and compute which categories to delete.
        gc_timer_t timer_analyze = { 0 }, timer_choose = { 0 }, timer_delete =
@@ -202,7 +203,7 @@ int kr_cache_gc(kr_cache_gc_cfg_t *cfg, kr_cache_gc_state_t **state)
        gc_timer_start(&timer_analyze);
        ctx_compute_categories_t cats = { { 0 }
        };
-       ret = kr_gc_cache_iter(db, cfg, cb_compute_categories, &cats);
+       ret = kr_gc_cache_iter(api, db, cfg, cb_compute_categories, &cats);
        if (ret != KNOT_EOK) {
                kr_cache_gc_free_state(state);
                return ret;
@@ -242,7 +243,7 @@ int kr_cache_gc(kr_cache_gc_cfg_t *cfg, kr_cache_gc_state_t **state)
        ctx_delete_categories_t to_del = { 0 };
        to_del.cfg_temp_keys_space = cfg->temp_keys_space;
        to_del.limit_category = limit_category;
-       ret = kr_gc_cache_iter(db, cfg, cb_delete_categories, &to_del);
+       ret = kr_gc_cache_iter(api, db, cfg, cb_delete_categories, &to_del);
        if (ret != KNOT_EOK) {
                entry_dynarray_deep_free(&to_del.to_delete);
                kr_cache_gc_free_state(state);
@@ -254,7 +255,6 @@ int kr_cache_gc(kr_cache_gc_cfg_t *cfg, kr_cache_gc_state_t **state)
             to_del.oversize_records);
 
        //// 4. execute the planned deletions.
-       const knot_db_api_t *api = knot_db_lmdb_api();
        knot_db_txn_t txn = { 0 };
        size_t deleted_records = 0, already_gone = 0, rw_txn_count = 0;