Commit daa0e304 authored by Marek Vavrusa's avatar Marek Vavrusa

Array based zone database with improved performance.

HAT-trie zone database wasn't effective for small number of zones,
because hashing presented a constant penalty. New zone database
is represented by an array of zones, sorted by number of labels and
then lexicographically. Zones are then grouped by the label count
into stack, so obviously mismatching names are pruned when searching.
Each labelcount group is then search either linearly or using binary
search based on the array length.
parent a432a6cf
......@@ -20,11 +20,13 @@ samples/Makefile.am
src/Makefile.am
src/common/acl.c
src/common/acl.h
src/common/array-sort.h
src/common/atomic.h
src/common/base32hex.c
src/common/base32hex.h
src/common/base64.c
src/common/base64.h
src/common/binsearch.h
src/common/crc.h
src/common/dSFMT-params.h
src/common/dSFMT-params521.h
......@@ -284,3 +286,4 @@ tests/tap/float.h
tests/tap/macros.h
tests/wire.c
tests/ztree.c
tests/zonedb.c
......@@ -177,7 +177,9 @@ libknots_la_SOURCES = \
common/slab/slab.c \
common/slab/slab.h \
common/slab/alloc-common.h \
common/array-sort.h \
common/atomic.h \
common/binsearch.h \
common/memdup.h \
common/mempattern.h \
common/mempattern.c \
......
......@@ -335,7 +335,6 @@ static int remote_c_zonestatus(server_t *s, remote_cmdargs_t* a)
}
rcu_read_unlock();
free(zones);
a->rlen = sizeof(a->resp) - 1 - rb;
return ret;
......@@ -382,7 +381,6 @@ static int remote_c_flush(server_t *s, remote_cmdargs_t* a)
ret = remote_zone_flush(s, zones[i]);
}
rcu_read_unlock();
free(zones);
return ret;
}
......
......@@ -314,7 +314,7 @@ int main(int argc, char **argv)
int res = 0;
log_server_info("Starting server...\n");
if ((server_start(server)) == KNOT_EOK) {
size_t zcount = server->nameserver->zone_db->zone_count;
size_t zcount = server->nameserver->zone_db->count;
if (!zcount) {
log_server_warning("Server started, but no zones served.\n");
}
......
......@@ -474,10 +474,6 @@ int server_refresh(server_t *server)
knot_nameserver_t *ns = server->nameserver;
evsched_t *sch = ((server_t *)knot_ns_get_data(ns))->sched;
const knot_zone_t **zones = knot_zonedb_zones(ns->zone_db);
if (zones == NULL) {
rcu_read_unlock();
return KNOT_ENOMEM;
}
/* REFRESH zones. */
for (unsigned i = 0; i < knot_zonedb_zone_count(ns->zone_db); ++i) {
......@@ -496,7 +492,6 @@ int server_refresh(server_t *server)
/* Unlock RCU. */
rcu_read_unlock();
free(zones);
return KNOT_EOK;
}
......
......@@ -1556,80 +1556,58 @@ static int zones_insert_zone(conf_zone_t *z, knot_zone_t **dst,
return ret;
}
/*! \brief Structure for multithreaded zone loading. */
struct zonewalk_t {
/*! \brief Context for threaded zone loader. */
typedef struct {
const struct conf_t *config;
knot_nameserver_t *ns;
knot_zonedb_t *db_new;
pthread_mutex_t lock;
int inserted;
unsigned qhead;
unsigned qtail;
conf_zone_t *q[];
};
} zone_loader_ctx_t;
/*! Thread entrypoint for loading zones. */
static int zonewalker(dthread_t *thread)
static int zones_loader_thread(dthread_t *thread)
{
if (thread == NULL) {
if (thread == NULL || thread->data == NULL) {
return KNOT_ERROR;
}
struct zonewalk_t *zw = (struct zonewalk_t *)thread->data;
if (zw == NULL) {
return KNOT_ERROR;
}
unsigned i = 0;
int inserted = 0;
knot_zone_t **zones = NULL;
size_t allocd = 0;
int ret = KNOT_ERROR;
knot_zone_t *zone = NULL;
conf_zone_t *zone_config = NULL;
zone_loader_ctx_t *ctx = (zone_loader_ctx_t *)thread->data;
for(;;) {
/* Fetch queue head. */
pthread_mutex_lock(&zw->lock);
i = zw->qhead++;
pthread_mutex_unlock(&zw->lock);
if (i >= zw->qtail) {
/* Fetch zone configuration from the list. */
pthread_mutex_lock(&ctx->lock);
if (EMPTY_LIST(ctx->config->zones)) {
pthread_mutex_unlock(&ctx->lock);
break;
}
if (mreserve((char **)&zones, sizeof(knot_zone_t*),
inserted + 1, 32, &allocd) < 0) {
dbg_zones("zones: failed to reserve space for "
"loading zones\n");
continue;
}
/* Disconnect from the list and start processing. */
zone_config = HEAD(ctx->config->zones);
rem_node(&zone_config->n);
pthread_mutex_unlock(&ctx->lock);
ret = zones_insert_zone(zone_config, &zone, ctx->ns);
int ret = zones_insert_zone(zw->q[i], zones + inserted, zw->ns);
/* Insert into database if properly loaded. */
pthread_mutex_lock(&ctx->lock);
if (ret == KNOT_EOK) {
++inserted;
}
}
/* Collect results. */
pthread_mutex_lock(&zw->lock);
zw->inserted += inserted;
for (int i = 0; i < inserted; ++i) {
zonedata_t *zd = (zonedata_t *)knot_zone_data(zones[i]);
if (knot_zonedb_add_zone(zw->db_new, zones[i]) != KNOT_EOK) {
log_server_error("Failed to insert zone '%s' "
"into database.\n", zd->conf->name);
/* Not doing this here would result in memory errors. */
rem_node(&zd->conf->n);
knot_zone_deep_free(zones + i);
if (knot_zonedb_add_zone(ctx->db_new, zone) != KNOT_EOK) {
log_server_error("Failed to insert zone '%s' "
"into database.\n", zone_config->name);
knot_zone_deep_free(&zone);
}
} else {
/* Unlink zone config from conf(),
* transferring ownership to zonedata. */
rem_node(&zd->conf->n);
/* Unable to load, discard configuration. */
conf_free_zone(zone_config);
}
pthread_mutex_unlock(&ctx->lock);
}
pthread_mutex_unlock(&zw->lock);
free(zones);
return KNOT_EOK;
}
static int zonewalker_destruct(dthread_t *thread)
static int zones_loader_destruct(dthread_t *thread)
{
knot_dnssec_thread_cleanup();
return KNOT_EOK;
......@@ -1642,50 +1620,30 @@ static int zonewalker_destruct(dthread_t *thread)
* new. New zones are loaded.
*
* \param ns Name server instance.
* \param zone_conf Zone configuration.
* \param db_new New zone database.
* \param conf Server configuration.
*
* \return Number of inserted zones.
*/
static int zones_insert_zones(knot_nameserver_t *ns,
const list_t *zone_conf,
knot_zonedb_t *db_new)
static knot_zonedb_t *zones_load_zonedb(knot_nameserver_t *ns, const conf_t *conf)
{
int ret = 0;
size_t zcount = 0;
conf_zone_t *z = NULL;
WALK_LIST(z, *zone_conf) {
++zcount;
}
if (zcount == 0)
return 0;
/* Initialize zonewalker. */
size_t zwlen = sizeof(struct zonewalk_t) + zcount * sizeof(conf_zone_t*);
struct zonewalk_t *zw = malloc(zwlen);
if (zw == NULL) {
return KNOT_ENOMEM;
}
memset(zw, 0, zwlen);
zw->ns = ns;
zw->db_new = db_new;
zw->inserted = 0;
if (pthread_mutex_init(&zw->lock, NULL) < 0) {
free(zw);
return KNOT_ENOMEM;
/* Initialize threaded loader. */
int ret = KNOT_EOK;
zone_loader_ctx_t ctx;
ctx.ns = ns;
ctx.config = conf;
ctx.db_new = knot_zonedb_new(conf->zones_count);
if (ctx.db_new == NULL) {
return NULL;
}
unsigned i = 0;
WALK_LIST(z, *zone_conf) {
zw->q[i++] = z;
if (pthread_mutex_init(&ctx.lock, NULL) < 0) {
knot_zonedb_free(&ctx.db_new);
return NULL;
}
zw->qhead = 0;
zw->qtail = zcount;
/* Initialize threads. */
size_t thrs = dt_optimal_size();
if (thrs > zcount) thrs = zcount;
dt_unit_t *unit = dt_create_coherent(thrs, &zonewalker,
&zonewalker_destruct, zw);
size_t thread_count = MIN(conf->zones_count, dt_optimal_size());
dt_unit_t *unit = NULL;
unit = dt_create_coherent(thread_count, &zones_loader_thread, &zones_loader_destruct, &ctx);
if (unit != NULL) {
/* Start loading. */
dt_start(unit);
......@@ -1693,14 +1651,13 @@ static int zones_insert_zones(knot_nameserver_t *ns,
dt_delete(&unit);
/* Collect counts. */
ret = zw->inserted;
ret = knot_zonedb_zone_count(ctx.db_new);
} else {
ret = KNOT_ENOMEM;
}
pthread_mutex_destroy(&zw->lock);
free(zw);
return ret;
pthread_mutex_destroy(&ctx.lock);
return ctx.db_new;
}
/*----------------------------------------------------------------------------*/
......@@ -1719,34 +1676,23 @@ static int zones_insert_zones(knot_nameserver_t *ns,
static int zones_remove_zones(const knot_zonedb_t *db_new,
knot_zonedb_t *db_old)
{
hattrie_iter_t *i = hattrie_iter_begin(db_new->zone_tree, 0);
while(!hattrie_iter_finished(i)) {
unsigned new_zone_count = db_new->count;
const knot_zone_t **new_zones = knot_zonedb_zones(db_new);
const knot_zone_t *old_zone = NULL;
for (unsigned i = 0; i < new_zone_count; ++i) {
/* try to find the new zone in the old DB
* if the pointers match, remove the zone from old DB
*/
/*! \todo Find better way of removing zone with given pointer.*/
knot_zone_t *new_zone = (knot_zone_t *)(*hattrie_iter_val(i));
knot_zone_t *old_zone = knot_zonedb_find_zone(
db_old, knot_zone_name(new_zone));
if (old_zone == new_zone) {
dbg_zones_exec(
char *name = knot_dname_to_str(knot_zone_name(old_zone));
dbg_zones_verb("zones: zone pointers match, removing zone %s "
"from database.\n", name);
free(name);
);
old_zone = knot_zonedb_find_zone(db_old, knot_zone_name(new_zones[i]));
if (old_zone == new_zones[i]) {
/* Remove from zone db. */
knot_zone_t * rm = knot_zonedb_remove_zone(db_old,
knot_zone_name(old_zone));
assert(rm == old_zone);
}
hattrie_iter_next(i);
}
hattrie_iter_free(i);
return KNOT_EOK;
}
......@@ -2125,28 +2071,19 @@ int zones_update_db_from_config(const conf_t *conf, knot_nameserver_t *ns,
}
rcu_read_unlock();
/* Create new zone DB */
knot_zonedb_t *db_new = knot_zonedb_new();
if (db_new == NULL) {
return KNOT_ERROR;
}
log_server_info("Loading %d zones...\n", conf->zones_count);
/* Insert all required zones to the new zone DB. */
/*! \warning RCU must not be locked as some contents switching will
be required. */
int inserted = zones_insert_zones(ns, &conf->zones, db_new);
if (inserted < 0) {
log_server_warning("Failed to load zones - %s\n",
knot_strerror(inserted));
inserted = 0;
}
log_server_info("Loaded %d out of %d zones.\n", inserted,
conf->zones_count);
if (inserted != conf->zones_count) {
log_server_warning("Not all the zones were loaded.\n");
knot_zonedb_t *db_new = zones_load_zonedb(ns, conf);
if (db_new == NULL) {
log_server_warning("Failed to load zones.\n");
} else {
size_t loaded = knot_zonedb_zone_count(db_new);
log_server_info("Loaded %zu out of %d zones.\n",
loaded, conf->zones_count);
if (loaded != conf->zones_count) {
log_server_warning("Not all the zones were loaded.\n");
}
}
/* Lock RCU to ensure none will deallocate any data under our hands. */
......@@ -2172,8 +2109,8 @@ int zones_update_db_from_config(const conf_t *conf, knot_nameserver_t *ns,
*/
int ret = zones_remove_zones(db_new, *db_old);
/* Heal zonedb index. */
hattrie_build_index(db_new->zone_tree);
/* Rebuild zone database search stack. */
knot_zonedb_build_index(db_new);
/* Unlock RCU, messing with any data will not affect us now */
rcu_read_unlock();
......@@ -3025,21 +2962,18 @@ int zones_ns_conf_hook(const struct conf_t *conf, void *data)
/* Update events scheduled for zone. */
rcu_read_lock();
knot_zone_t **zones = (knot_zone_t **)knot_zonedb_zones(ns->zone_db);
if (zones == NULL) {
rcu_read_unlock();
return KNOT_ENOMEM;
}
knot_zone_t *zone = NULL;
const knot_zone_t **zones = knot_zonedb_zones(ns->zone_db);
/* REFRESH zones. */
for (unsigned i = 0; i < knot_zonedb_zone_count(ns->zone_db); ++i) {
zones_schedule_refresh(zones[i], 0); /* Now. */
zones_schedule_notify(zones[i]);
zone = (knot_zone_t *)zones[i];
zones_schedule_refresh(zone, 0); /* Now. */
zones_schedule_notify(zone);
}
/* Unlock RCU. */
rcu_read_unlock();
free(zones);
return KNOT_EOK;
}
......
......@@ -3057,7 +3057,7 @@ knot_nameserver_t *knot_ns_create()
// Create zone database structure
dbg_ns("Creating Zone Database structure...\n");
ns->zone_db = knot_zonedb_new();
ns->zone_db = knot_zonedb_new(0);
if (ns->zone_db == NULL) {
ERR_ALLOC_FAILED;
free(ns);
......
This diff is collapsed.
......@@ -30,20 +30,43 @@
#ifndef _KNOT_ZONEDB_H_
#define _KNOT_ZONEDB_H_
#include "common/hattrie/hat-trie.h"
#include "libknot/zone/zone.h"
#include "libknot/zone/node.h"
#include "libknot/dname.h"
/*!
* \brief Zone database structure. Contains all zones managed by the server.
/*
* Zone DB represents a list of managed zones.
* Hashing should be avoided as it is expensive when only a small number of
* zones is present (TLD case). Linear run-length algorithms or worse should
* be avoided as well, as the number of zones may be large.
*
* Use of string-based algorithms for suffix search is viable, but would require
* transformation each time a name is searched. That again would be a
* constant cost even if the number of zones would be small.
*
* Zone database structure is a stack of zones grouped by label count in
* descending order (root label not counted), therefore first match is the longest.
* Each stack level is sorted for convenient binary search.
* example:
* {3 labels, 2 items} => [ 'a.b.c', 'b.b.c' ]
* {2 labels, 1 items} => [ 'x.z' ]
* {1 labels, 2 items} => [ 'y', 'w' ]
*
* Stack is built on top of the sorted array of zones for direct access and
* less memory requirements.
*/
struct knot_zonedb {
hattrie_t *zone_tree; /*!< AVL tree of zones. */
size_t zone_count;
};
typedef struct knot_zonedb knot_zonedb_t;
typedef struct {
unsigned labels;
unsigned count;
knot_zone_t** array;
} knot_zonedb_stack_t;
typedef struct {
unsigned count, reserved;
knot_zone_t **array;
unsigned stack_height;
knot_zonedb_stack_t stack[KNOT_DNAME_MAXLABELS];
} knot_zonedb_t;
/*----------------------------------------------------------------------------*/
......@@ -53,7 +76,7 @@ typedef struct knot_zonedb knot_zonedb_t;
* \return Pointer to the created zone database structure or NULL if an error
* occured.
*/
knot_zonedb_t *knot_zonedb_new();
knot_zonedb_t *knot_zonedb_new(unsigned size);
/*!
* \brief Adds new zone to the database.
......@@ -69,15 +92,8 @@ int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone);
/*!
* \brief Removes the given zone from the database if it exists.
*
* \note Assumes that the zone was adjusted using knot_zone_adjust_dnames().
* If it was not, it may leak some memory due to checks used in
* knot_rdata_deep_free().
*
* \param db Zone database to remove from.
* \param zone_name Name of the zone to be removed.
* \param destroy_zone Set to <> 0 if you do want the function to destroy the
* zone after removing from zone database. Set to 0
* otherwise.
*
* \retval KNOT_EOK
* \retval KNOT_ENOZONE
......@@ -85,6 +101,18 @@ int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone);
knot_zone_t * knot_zonedb_remove_zone(knot_zonedb_t *db,
const knot_dname_t *zone_name);
/*!
* \brief Build zone stack for faster lookup.
*
* Zone stack structure is described in the knot_zonedb_t struct.
*
* \param db Zone database.
* \retval KNOT_EOK
* \retval KNOT_ENOMEM
* \retval KNOT_EINVAL
*/
int knot_zonedb_build_index(knot_zonedb_t *db);
/*!
* \brief Finds zone exactly matching the given zone name.
*
......@@ -94,8 +122,8 @@ knot_zone_t * knot_zonedb_remove_zone(knot_zonedb_t *db,
* \return Zone with \a zone_name being the owner of the zone apex or NULL if
* not found.
*/
knot_zone_t *knot_zonedb_find_zone(const knot_zonedb_t *db,
const knot_dname_t *zone_name);
knot_zone_t *knot_zonedb_find_zone(knot_zonedb_t *db,
const knot_dname_t *zone_name);
/*!
......@@ -107,8 +135,8 @@ knot_zone_t *knot_zonedb_find_zone(const knot_zonedb_t *db,
* \retval Zone in which the domain name should be present or NULL if no such
* zone is found.
*/
const knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db,
const knot_dname_t *dname);
knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db,
const knot_dname_t *zone_name);
knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db,
const knot_dname_t *zone_name);
......@@ -127,10 +155,6 @@ void knot_zonedb_free(knot_zonedb_t **db);
/*!
* \brief Destroys and deallocates the whole zone database including the zones.
*
* \note Assumes that the zone was adjusted using knot_zone_adjust_dnames().
* If it was not, it may leak some memory due to checks used in
* knot_rdata_deep_free().
*
* \param db Zone database to be destroyed.
*/
void knot_zonedb_deep_free(knot_zonedb_t **db);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment