diff --git a/Knot.files b/Knot.files
index befe0ffcaf6f09e79ec1e8571dde467ac7476975..6a51d053339ee8d8bce1abbfe4fbee6486698af6 100644
--- a/Knot.files
+++ b/Knot.files
@@ -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
diff --git a/src/Makefile.am b/src/Makefile.am
index 972a94f3182378476675293c256f889e81fcd717..82fe55b0965b969b05388527f9a2cecc2ec6d39a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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			\
diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index 42152989aaafc978d0cb01e0280755b775c7cff9..6b19d6d1f5d9e940a92cb36aa2dd046dbc836466 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.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;
 	}
 
diff --git a/src/knot/main.c b/src/knot/main.c
index 835b826f95307a28c6c683181f39fee6d15473d6..9c593da32d2684803733b2a848b7308f002ebf7e 100644
--- a/src/knot/main.c
+++ b/src/knot/main.c
@@ -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");
 		}
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index 1e5f56eb68f3711123c4cf92a102edcc33a71d5a..a94df55670b1b0d03407e0288b3aae74c8e372e2 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -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;
 }
 
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index a7395782c33f2ddd74a1cc285a46ab39e7baa546..c1eec6c738ce2d535a5fb7ee8ad26407414564ca 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -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;
 }
diff --git a/src/libknot/nameserver/name-server.c b/src/libknot/nameserver/name-server.c
index 0df4a409c545b0d444e1904462946080c47756f8..48f1648402af1b1a29af7beb59d7b6f788aa58b7 100644
--- a/src/libknot/nameserver/name-server.c
+++ b/src/libknot/nameserver/name-server.c
@@ -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);
diff --git a/src/libknot/zone/zonedb.c b/src/libknot/zone/zonedb.c
index 54bbd9810f60abc923a6a4df0a63dabebc6289fb..bf153bb5a190ec07e87121a3a92a3969fb0e2732 100644
--- a/src/libknot/zone/zonedb.c
+++ b/src/libknot/zone/zonedb.c
@@ -20,35 +20,103 @@
 
 #include <urcu.h>
 
+#include "common/binsearch.h"
 #include "libknot/common.h"
 #include "libknot/zone/zone.h"
 #include "libknot/zone/zonedb.h"
 #include "libknot/dname.h"
+#include "libknot/util/wire.h"
 #include "libknot/zone/node.h"
 #include "libknot/util/debug.h"
 
+/* Array sorter generator. */
+static int knot_zonedb_cmp(const knot_dname_t* d1, const knot_dname_t *d2);
+#define ASORT_PREFIX(X) knot_zonedb_##X
+#define ASORT_KEY_TYPE knot_zone_t* 
+#define ASORT_LT(x, y) (knot_zonedb_cmp((x)->name, (y)->name) < 0)
+#include "common/array-sort.h"
+
+/* Defines */
+#define BSEARCH_THRESHOLD 8 /* >= N for which binary search is favoured */
+
 /*----------------------------------------------------------------------------*/
 /* Non-API functions                                                          */
 /*----------------------------------------------------------------------------*/
 
+/*! \brief Discard zone in zone database. */
+static void delete_zone_from_db(knot_zone_t *zone)
+{
+	synchronize_rcu();
+	knot_zone_set_flag(zone, KNOT_ZONE_DISCARDED, 1);
+	knot_zone_release(zone);
+}
+
+/*! \brief Zone database zone name compare function. */
+static int knot_zonedb_cmp(const knot_dname_t* d1, const knot_dname_t *d2)
+{
+	int a_labels = knot_dname_labels(d1, NULL);
+	int b_labels = knot_dname_labels(d2, NULL);
+	
+	/* Lexicographic order. */
+	if (a_labels == b_labels) {
+		return knot_dname_cmp(d1, d2);
+	}
+	
+	/* Name with more labels goes first. */
+	return b_labels - a_labels;
+}
+
+/*! \brief Find an equal name in sorted array (binary search). */
+#define ZONEDB_CMP(arr,i,x) knot_zonedb_cmp(((arr)[i])->name, (x))
+static long knot_zonedb_binsearch(knot_zone_t **arr, unsigned count,
+                               const knot_dname_t *name)
+{
+	return BIN_SEARCH_FIRST_EQ_CMP(arr, count, name, ZONEDB_CMP);
+}
+
+/*! \brief Find an equal name in an array (linear search).
+ *  \note Linear search uses simple name equality test which could be
+ *        faster than canonical compare and therefore more efficient for
+ *        smaller arrays.
+ */
+static long knot_zonedb_linear_search(knot_zone_t **arr, unsigned count,
+                               const knot_dname_t *name) {
+	for (unsigned i = 0; i < count; ++i) {
+		if (knot_dname_is_equal(arr[i]->name, name)) {
+			return i;
+		}
+	}
+	return -1;
+}
+
+/*! \brief Zone array search. */
+static long knot_zonedb_array_search(knot_zone_t **arr, unsigned count,
+                               const knot_dname_t *name)
+{
+	if (count < BSEARCH_THRESHOLD) {
+		return knot_zonedb_linear_search(arr, count, name);
+	} else {
+		return knot_zonedb_binsearch(arr, count, name);
+	}
+}
+
 /*----------------------------------------------------------------------------*/
 /* API functions                                                              */
 /*----------------------------------------------------------------------------*/
 
-knot_zonedb_t *knot_zonedb_new()
+knot_zonedb_t *knot_zonedb_new(unsigned size)
 {
-	knot_zonedb_t *db =
-		(knot_zonedb_t *)malloc(sizeof(knot_zonedb_t));
+	knot_zonedb_t *db = malloc(sizeof(knot_zonedb_t));
 	CHECK_ALLOC_LOG(db, NULL);
 
-	db->zone_tree = hattrie_create();
-	if (db->zone_tree == NULL) {
+	memset(db, 0, sizeof(knot_zonedb_t));
+	db->reserved = size;
+	db->array = malloc(size * sizeof(knot_zone_t*));
+	if (db->array == NULL) {
 		free(db);
 		return NULL;
 	}
 
-	db->zone_count = 0;
-
 	return db;
 }
 
@@ -72,12 +140,12 @@ int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone)
 		}
 	}
 
-	/* Insert new record. */
-	uint8_t lf[KNOT_DNAME_MAXLEN];
-	knot_dname_lf(lf, zone->name, NULL);
-	*hattrie_get(db->zone_tree, (char*)lf+1, *lf) = zone;
+	/* Invalidate search index. */
+	db->stack_height = 0;
 
-	db->zone_count++;
+	/* Create new record. */
+	assert(db->count < db->reserved); /* Should be already checked. */
+	db->array[db->count++] = zone;
 
 	return ret;
 }
@@ -87,57 +155,107 @@ 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)
 {
-	/* Fetch if exists. */
-	uint8_t lf[KNOT_DNAME_MAXLEN];
-	knot_dname_lf(lf, zone_name, NULL);
-
-	value_t *old_zone = hattrie_tryget(db->zone_tree, (char*)lf+1, *lf);
-	if (old_zone == NULL)
+	if (db == NULL || zone_name == NULL) {
 		return NULL;
-
-	/* Remove from db. */
-	if (hattrie_del(db->zone_tree, (char*)lf+1, *lf) < 0)
+	}
+	
+	/* Find the possible zone to remove. */
+	int pos = knot_zonedb_array_search(db->array, db->count, zone_name);
+	if (pos < 0) {
 		return NULL;
+	}
 
-	--db->zone_count;
-	return (knot_zone_t *)*old_zone;
+	/* Invalidate search index. */
+	db->stack_height = 0;
+	
+	/* Move rest of the array to not break the ordering. */
+	knot_zone_t *removed_zone = db->array[pos];
+	unsigned remainder = (db->count - (pos + 1)) * sizeof(knot_zone_t*);
+	memmove(db->array + pos, db->array + pos + 1, remainder);
+	--db->count;
+	
+	return removed_zone;
 }
 
 /*----------------------------------------------------------------------------*/
 
-knot_zone_t *knot_zonedb_find_zone(const knot_zonedb_t *db,
+int knot_zonedb_build_index(knot_zonedb_t *db)
+{
+	/* First, sort all zones based on the label count first and lexicographic
+	 * order second. The name with most labels goes first. 
+	 * i.e. {a, a.b, a.c, b } -> {a.b, a.c, a, b} */
+	knot_zonedb_sort(db->array, db->count);
+	
+	/* Scan the array and group names with the same label count together. */
+	unsigned prev_label_count = 0;
+	unsigned current_label_count = 0;
+	knot_zone_t **endp = db->array + db->count;
+	knot_zonedb_stack_t *stack_top = db->stack - 1; /* Before actual stack. */
+	db->stack_height = 0;
+	
+	for (knot_zone_t **zone = db->array; zone != endp; ++zone) {
+		/* Insert into current label count group. */
+		current_label_count = knot_dname_labels((*zone)->name, NULL);
+		if (current_label_count == prev_label_count) {
+			++stack_top->count;
+			continue;
+		}
+		
+		/* Begin new label count group. */
+		++stack_top;
+		++db->stack_height;
+		stack_top->count = 1;
+		stack_top->labels = current_label_count;
+		stack_top->array = zone;
+		prev_label_count = current_label_count;
+		
+	}
+	
+	return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+knot_zone_t *knot_zonedb_find_zone(knot_zonedb_t *db,
                                        const knot_dname_t *zone_name)
 {
-	uint8_t lf[KNOT_DNAME_MAXLEN];
-	knot_dname_lf(lf, zone_name, NULL);
-	value_t *val = hattrie_tryget(db->zone_tree, (char*)lf+1, *lf);
-	if (val)
-		return (knot_zone_t *)*val;
+	int pos = knot_zonedb_array_search(db->array, db->count, zone_name);
+	if (pos < 0) {
+		return NULL;
+	}
 
-	return NULL;
+	return db->array[pos];
 }
 
 /*----------------------------------------------------------------------------*/
 
-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)
 {
-	if (db == NULL || dname == NULL) {
+	int zone_labels = knot_dname_labels(zone_name, NULL);
+	if (db == NULL || zone_labels < 0) {
 		return NULL;
 	}
+	
+	/* Walk down the stack, from the most labels to least. */
+	knot_zonedb_stack_t *sp = db->stack, *endp = db->stack + db->stack_height;
+	for (; sp != endp; ++sp) {
+		/* Inspect only zones with <= labels than zone_labels. */
+		if (sp->labels > zone_labels) {
+			continue;
+		}
+
+		/* Skip non-matched labels. */
+		while (sp->labels < zone_labels) {
+			zone_name = knot_wire_next_label(zone_name, NULL);
+			--zone_labels;
+		}
 
-	uint8_t lf[KNOT_DNAME_MAXLEN];
-	knot_dname_lf(lf, dname, NULL);
-
-	/* Attempt to find longest matching prefix.
-	 * as zones are stored as \x00com\x00lake\x00
-	 * the longest matching prefix is essentially a best match,
-	 * as it is guaranteed that inserted names end in \x00
-	 */
-	value_t *val = NULL;
-	int len = hattrie_find_lpr(db->zone_tree, (char*)lf+1, *lf, &val);
-	if (len > -1) {
-		return (const knot_zone_t *)*val;
+		/* Possible candidate, search the array. */
+		int k = knot_zonedb_array_search(sp->array, sp->count, zone_name);
+		if (k > -1) {
+			return sp->array[k];
+		}
 	}
 
 	return NULL;
@@ -148,6 +266,7 @@ const knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db,
 knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db,
                                               const knot_dname_t *zone_name)
 {
+	
 	if (db == NULL || zone_name == NULL) {
 		return NULL;
 	}
@@ -164,98 +283,39 @@ knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db,
 
 /*----------------------------------------------------------------------------*/
 
-knot_zonedb_t *knot_zonedb_copy(const knot_zonedb_t *db)
-{
-	knot_zonedb_t *db_new =
-		(knot_zonedb_t *)malloc(sizeof(knot_zonedb_t));
-	CHECK_ALLOC_LOG(db_new, NULL);
-
-	db_new->zone_tree = hattrie_dup(db->zone_tree, NULL);
-	if (db_new->zone_tree == NULL) {
-		free(db_new);
-		return NULL;
-	}
-
-	return db_new;
-}
-
-/*----------------------------------------------------------------------------*/
-
 size_t knot_zonedb_zone_count(const knot_zonedb_t *db)
 {
-	return db->zone_count;
-}
-
-/*----------------------------------------------------------------------------*/
-
-struct knot_zone_db_tree_arg {
-	const knot_zone_t **zones;
-	size_t count;
-};
-
-/*----------------------------------------------------------------------------*/
-
-static void save_zone_to_array(void *node, void *data)
-{
-	knot_zone_t *zone = (knot_zone_t *)node;
-	struct knot_zone_db_tree_arg *args =
-		(struct knot_zone_db_tree_arg *)data;
-	assert(data);
-	args->zones[args->count++] = zone;
+	return db->count;
 }
 
 /*----------------------------------------------------------------------------*/
 
 const knot_zone_t **knot_zonedb_zones(const knot_zonedb_t *db)
 {
-	struct knot_zone_db_tree_arg args;
-	args.zones = malloc(sizeof(knot_zone_t *) * db->zone_count);
-	args.count = 0;
-	CHECK_ALLOC_LOG(args.zones, NULL);
-
-	hattrie_iter_t *i = hattrie_iter_begin(db->zone_tree, 1);
-	while(!hattrie_iter_finished(i)) {
-		save_zone_to_array(*hattrie_iter_val(i), &args);
-		hattrie_iter_next(i);
+	if (db == NULL) {
+		return NULL;
 	}
-	hattrie_iter_free(i);
-
-	assert(db->zone_count == args.count);
-
-	return args.zones;
+	
+	return (const knot_zone_t **)db->array;
 }
 
 /*----------------------------------------------------------------------------*/
 
 void knot_zonedb_free(knot_zonedb_t **db)
 {
-	hattrie_free((*db)->zone_tree);
+	free((*db)->array);
 	free(*db);
 	*db = NULL;
 }
 
 /*----------------------------------------------------------------------------*/
 
-static int delete_zone_from_db(value_t *node, void *data)
-{
-	UNUSED(data);
-	assert(node);
-	if (*node != NULL) {
-		knot_zone_t *zone = (knot_zone_t *)(*node);
-		synchronize_rcu();
-		knot_zone_set_flag(zone, KNOT_ZONE_DISCARDED, 1);
-		knot_zone_release(zone);
-		*node = NULL;
-	}
-
-	return KNOT_EOK;
-}
-
 void knot_zonedb_deep_free(knot_zonedb_t **db)
 {
 	dbg_zonedb("Deleting zone db (%p).\n", *db);
-	hattrie_apply_rev((*db)->zone_tree, delete_zone_from_db, NULL);
-	hattrie_free((*db)->zone_tree);
-	free(*db);
-	*db = NULL;
+	for (unsigned i = 0; i < (*db)->count; ++i) {
+		delete_zone_from_db((*db)->array[i]);
+	}
+
+	knot_zonedb_free(db);
 }
diff --git a/src/libknot/zone/zonedb.h b/src/libknot/zone/zonedb.h
index e1cfa13ebc5d71fb43f4fcefe51b62b3aa3668ea..a3f72b63bcdbc3e0bfd3bdf53e1ec0d5899f8ace 100644
--- a/src/libknot/zone/zonedb.h
+++ b/src/libknot/zone/zonedb.h
@@ -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);