diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
index 90b0354887629cff94ae4cd6be627d9c1039121c..5350bd5b06fac284133b2ddad2ba578f722586d8 100644
--- a/src/knot/ctl/remote.c
+++ b/src/knot/ctl/remote.c
@@ -108,7 +108,7 @@ static int remote_rdata_apply(server_t *s, remote_cmdargs_t* a, remote_zonef_t *
 			const knot_dname_t *dn =
 				knot_rdata_ns_name(rr, i);
 			rcu_read_lock();
-			zone = knot_zonedb_find_zone(ns->zone_db, dn);
+			zone = knot_zonedb_find(ns->zone_db, dn);
 			if (cb(s, zone) != KNOT_EOK) {
 				a->rc = KNOT_RCODE_SERVFAIL;
 			}
@@ -254,14 +254,16 @@ static int remote_c_zonestatus(server_t *s, remote_cmdargs_t* a)
 	int ret = KNOT_EOK;
 	rcu_read_lock();
 	knot_nameserver_t *ns =  s->nameserver;
-	const knot_zone_t **zones = knot_zonedb_zones(ns->zone_db);
-	for (unsigned i = 0; i < knot_zonedb_zone_count(ns->zone_db); ++i) {
-		zonedata_t *zd = (zonedata_t *)zones[i]->data;
+	knot_zonedb_iter_t it;
+	knot_zonedb_iter_begin(ns->zone_db, &it);
+	while(!knot_zonedb_iter_finished(&it)) {
+		const knot_zone_t *zone = knot_zonedb_iter_val(&it);
+		zonedata_t *zd = (zonedata_t *)zone->data;
 
 		/* Fetch latest serial. */
 		const knot_rrset_t *soa_rrs = 0;
 		uint32_t serial = 0;
-		knot_zone_contents_t *contents = knot_zone_get_contents(zones[i]);
+		knot_zone_contents_t *contents = knot_zone_get_contents(zone);
 		if (contents) {
 			soa_rrs = knot_node_rrset(knot_zone_contents_apex(contents),
 			                          KNOT_RRTYPE_SOA);
@@ -333,7 +335,7 @@ static int remote_c_zonestatus(server_t *s, remote_cmdargs_t* a)
 		rb -= n;
 		dst += n;
 
-
+		knot_zonedb_iter_next(&it);
 	}
 	rcu_read_unlock();
 
@@ -377,9 +379,11 @@ static int remote_c_flush(server_t *s, remote_cmdargs_t* a)
 		dbg_server_verb("remote: flushing all zones\n");
 		rcu_read_lock();
 		knot_nameserver_t *ns =  s->nameserver;
-		const knot_zone_t **zones = knot_zonedb_zones(ns->zone_db);
-		for (unsigned i = 0; i < knot_zonedb_zone_count(ns->zone_db); ++i) {
-			ret = remote_zone_flush(s, zones[i]);
+		knot_zonedb_iter_t it;
+		knot_zonedb_iter_begin(ns->zone_db, &it);
+		while(!knot_zonedb_iter_finished(&it)) {
+			ret = remote_zone_flush(s, knot_zonedb_iter_val(&it));
+			knot_zonedb_iter_next(&it);
 		}
 		rcu_read_unlock();
 		return ret;
diff --git a/src/knot/main.c b/src/knot/main.c
index 704b5a89d649b3421139ecc6305032e6ad9596cb..7866fa928ba06514185575454d6beecf32d85edd 100644
--- a/src/knot/main.c
+++ b/src/knot/main.c
@@ -325,8 +325,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->count;
-		if (!zcount) {
+		if (!knot_zonedb_size(server->nameserver->zone_db)) {
 			log_server_warning("Server started, but no zones served.\n");
 		}
 
diff --git a/src/knot/server/notify.c b/src/knot/server/notify.c
index d93fee8554d1df43708cc28e36510d62c8712590..cb1f98b158d51b2027085620039d2d558789a5e5 100644
--- a/src/knot/server/notify.c
+++ b/src/knot/server/notify.c
@@ -261,7 +261,7 @@ int notify_process_request(knot_nameserver_t *ns,
 	unsigned serial = 0;
 	const knot_dname_t *qname = knot_packet_qname(notify);
 	rcu_read_lock(); /* z */
-	const knot_zone_t *z = knot_zonedb_find_zone_for_name(ns->zone_db, qname);
+	const knot_zone_t *z = knot_zonedb_find_suffix(ns->zone_db, qname);
 	if (z != NULL) {
 		ret = notify_check_and_schedule(ns, z, from);
 		const knot_rrset_t *soa_rr = NULL;
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index 3d89002de55cfae5fefa839a579d5a9f67959a3b..19334028f60ebbd2eff98c4263a513f586b73821 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -470,21 +470,23 @@ int server_refresh(server_t *server)
 	rcu_read_lock();
 	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);
+	knot_zonedb_iter_t it;
+	knot_zonedb_iter_begin(ns->zone_db, &it);
+	unsigned count = 0;
+	while(!knot_zonedb_iter_finished(&it)) {
+		const knot_zone_t *zone = knot_zonedb_iter_val(&it);
+		zonedata_t *zd = (zonedata_t *)zone->data;
 
-	/* REFRESH zones. */
-	for (unsigned i = 0; i < knot_zonedb_zone_count(ns->zone_db); ++i) {
-		zonedata_t *zd = (zonedata_t *)zones[i]->data;
-		if (zd == NULL) {
-			continue;
-		}
 		/* Expire REFRESH timer. */
-		if (zd->xfr_in.timer) {
+		if (zd && zd->xfr_in.timer) {
 			evsched_cancel(sch, zd->xfr_in.timer);
 			evsched_schedule(sch, zd->xfr_in.timer,
-			                 tls_rand() * 500 + i/2);
+					 tls_rand() * 500 + count/2);
 			/* Cumulative delay. */
+			++count;
 		}
+
+		knot_zonedb_iter_next(&it);
 	}
 
 	/* Unlock RCU. */
diff --git a/src/knot/server/zone-load.c b/src/knot/server/zone-load.c
index d7f68e2431df2f1ac95afdf77224a03659ee2114..062974119dfc835051cc4c001d2215be6beb07b2 100644
--- a/src/knot/server/zone-load.c
+++ b/src/knot/server/zone-load.c
@@ -590,7 +590,7 @@ static int update_zone(knot_zone_t **dst, conf_zone_t *conf, knot_nameserver_t *
 		return KNOT_ENOMEM;
 	}
 
-	knot_zone_t *old_zone = knot_zonedb_find_zone(ns->zone_db, apex);
+	knot_zone_t *old_zone = knot_zonedb_find(ns->zone_db, apex);
 	knot_zone_t *new_zone = get_zone(old_zone, apex, conf, ns);
 
 	knot_dname_free(&apex);
@@ -698,7 +698,7 @@ static int zone_loader_thread(dthread_t *thread)
 		/* Insert into database if properly loaded. */
 		pthread_mutex_lock(&ctx->lock);
 		if (ret == KNOT_EOK) {
-			if (knot_zonedb_add_zone(ctx->db_new, zone) != KNOT_EOK) {
+			if (knot_zonedb_insert(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);
@@ -784,21 +784,22 @@ static knot_zonedb_t *load_zonedb(knot_nameserver_t *ns, const conf_t *conf)
 static int remove_zones(const knot_zonedb_t *db_new,
                               knot_zonedb_t *db_old)
 {
-	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) {
+	knot_zonedb_iter_t it;
+	knot_zonedb_iter_begin(db_new, &it);
+	while(!knot_zonedb_iter_finished(&it)) {
+		const knot_zone_t *old_zone = NULL;
+		const knot_zone_t *new_zone = knot_zonedb_iter_val(&it);
+		const knot_dname_t *zone_name = new_zone->name;
 
 		/* try to find the new zone in the old DB
 		 * if the pointers match, remove the zone from old DB
 		 */
-		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);
+		old_zone = knot_zonedb_find(db_old, zone_name);
+		if (old_zone == new_zone) {
+			knot_zonedb_del(db_old, zone_name);
 		}
+
+		knot_zonedb_iter_next(&it);
 	}
 
 	return KNOT_EOK;
@@ -832,7 +833,7 @@ int zones_update_db_from_config(const conf_t *conf, knot_nameserver_t *ns,
 		log_server_warning("Failed to load zones.\n");
 		return KNOT_ENOMEM;
 	} else {
-		size_t loaded = knot_zonedb_zone_count(db_new);
+		size_t loaded = knot_zonedb_size(db_new);
 		log_server_info("Loaded %zu out of %d zones.\n",
 		                loaded, conf->zones_count);
 		if (loaded != conf->zones_count) {
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index 6541777ce52c37ef4d9cfe0adcc476ee5e45c857..e56880f72b8b1e7cc2f7754aefc93f6f9041228b 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -1780,7 +1780,7 @@ int zones_process_response(knot_nameserver_t *nameserver,
 		rcu_read_lock();
 		const knot_dname_t *zone_name = knot_packet_qname(packet);
 		/*! \todo Change the access to the zone db. */
-		knot_zone_t *zone = knot_zonedb_find_zone(
+		knot_zone_t *zone = knot_zonedb_find(
 		                        nameserver->zone_db,
 		                        zone_name);
 
@@ -2023,14 +2023,13 @@ int zones_ns_conf_hook(const struct conf_t *conf, void *data)
 	knot_zonedb_deep_free(&old_db);
 
 	/* Update events scheduled for zone. */
-	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) {
-		zone = (knot_zone_t *)zones[i];
+	knot_zonedb_iter_t it;
+	knot_zonedb_iter_begin(ns->zone_db, &it);
+	while(!knot_zonedb_iter_finished(&it)) {
+		knot_zone_t *zone = knot_zonedb_iter_val(&it);
 		zones_schedule_refresh(zone, 0); /* Now. */
 		zones_schedule_notify(zone);
+		knot_zonedb_iter_next(&it);
 	}
 
 	return KNOT_EOK;
@@ -2919,7 +2918,7 @@ int zones_do_diff_and_sign(const conf_zone_t *z, knot_zone_t *zone,
 	zones_cancel_dnssec(zone);
 	/* Calculate differences. */
 	rcu_read_lock();
-	knot_zone_t *z_old = knot_zonedb_find_zone(ns->zone_db,
+	knot_zone_t *z_old = knot_zonedb_find(ns->zone_db,
 	                                           zone->name);
 	/* Ensure both new and old have zone contents. */
 	knot_zone_contents_t *zc = knot_zone_get_contents(zone);
diff --git a/src/libknot/nameserver/name-server.c b/src/libknot/nameserver/name-server.c
index 17e68a140105182885ee6b157b577d1c6a291ef6..839d002ac27d6a31cc87de9df7cb2676e6042a2c 100644
--- a/src/libknot/nameserver/name-server.c
+++ b/src/libknot/nameserver/name-server.c
@@ -84,14 +84,14 @@ static const knot_zone_t *ns_get_zone_for_qname(knot_zonedb_t *zdb,
 	 */
 	if (qtype == KNOT_RRTYPE_DS) {
 		const knot_dname_t *parent = knot_wire_next_label(qname, NULL);
-		zone = knot_zonedb_find_zone_for_name(zdb, parent);
+		zone = knot_zonedb_find_suffix(zdb, parent);
 		/* If zone does not exist, search for its parent zone,
 		   this will later result to NODATA answer. */
 		if (zone == NULL) {
-			zone = knot_zonedb_find_zone_for_name(zdb, qname);
+			zone = knot_zonedb_find_suffix(zdb, qname);
 		}
 	} else {
-		zone = knot_zonedb_find_zone_for_name(zdb, qname);
+		zone = knot_zonedb_find_suffix(zdb, qname);
 	}
 
 	return zone;
@@ -3580,7 +3580,7 @@ dbg_ns_exec_verb(
 	free(name_str);
 );
 	// find zone
-	*zone = knot_zonedb_find_zone(zonedb, qname);
+	*zone = knot_zonedb_find(zonedb, qname);
 
 	return KNOT_EOK;
 }
@@ -3707,7 +3707,7 @@ dbg_ns_exec_verb(
 	free(name_str);
 );
 	// find zone in which to search for the name
-	knot_zone_t *zone = knot_zonedb_find_zone(zonedb, qname);
+	knot_zone_t *zone = knot_zonedb_find(zonedb, qname);
 
 	// if no zone found, return NotAuth
 	if (zone == NULL) {
diff --git a/src/libknot/zone/zonedb.c b/src/libknot/zone/zonedb.c
index 3769650ca012e1f7cc67190e3da801d662bbfde4..0099265090bd6ceb77aafe2b990c844e04d062dd 100644
--- a/src/libknot/zone/zonedb.c
+++ b/src/libknot/zone/zonedb.c
@@ -20,7 +20,6 @@
 
 #include <urcu.h>
 
-#include "common/binsearch.h"
 #include "libknot/common.h"
 #include "libknot/zone/zone.h"
 #include "libknot/zone/zonedb.h"
@@ -28,16 +27,8 @@
 #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 */
+#include "common/mempattern.h"
+#include "common/mempool.h"
 
 /*----------------------------------------------------------------------------*/
 /* Non-API functions                                                          */
@@ -51,216 +42,124 @@ static void delete_zone_from_db(knot_zone_t *zone)
 	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_LEQ(arr,i,x) (knot_zonedb_cmp(((arr)[i])->name, (x)) <= 0)
-static long knot_zonedb_binsearch(knot_zone_t **arr, unsigned count,
-                                  const knot_dname_t *name)
-{
-	int k = BIN_SEARCH_FIRST_GE_CMP(arr, count, ZONEDB_LEQ, name) - 1;
-	if (k > -1 && knot_dname_is_equal(arr[k]->name, name)) {
-			return k;
-	}
-
-	return -1;
-
-}
-
-/*! \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(unsigned size)
+knot_zonedb_t *knot_zonedb_new(uint32_t size)
 {
-	knot_zonedb_t *db = malloc(sizeof(knot_zonedb_t));
-	CHECK_ALLOC_LOG(db, 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);
+	/* Create memory pool context. */
+	mm_ctx_t mm;
+	mm.ctx = mp_new(4096);
+	mm.alloc = (mm_alloc_t)mp_alloc;
+	mm.free = mm_nofree;
+	knot_zonedb_t *db = mm.alloc(mm.ctx, sizeof(knot_zonedb_t));
+	if (db == NULL) {
+		return NULL;
+	}
+
+	db->maxlabels = 0;
+	db->hash = hhash_create_mm((size + 1) * 2, &mm);
+	if (db->hash == NULL) {
+		mm.free(db);
 		return NULL;
 	}
 
+	memcpy(&db->mm, &mm, sizeof(mm_ctx_t));
 	return db;
 }
 
 /*----------------------------------------------------------------------------*/
 
-int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone)
+int knot_zonedb_insert(knot_zonedb_t *db, knot_zone_t *zone)
 {
 	if (db == NULL || zone == NULL) {
 		return KNOT_EINVAL;
 	}
 
-	/* Invalidate search index. */
-	db->stack_height = 0;
-
-	/* Create new record. */
-	assert(db->count < db->reserved); /* Should be already checked. */
-	db->array[db->count++] = zone;
-
-	return KNOT_EOK;
+	int name_size = knot_dname_size(zone->name);
+	return hhash_insert(db->hash, (const char*)zone->name, name_size, zone);
 }
 
 /*----------------------------------------------------------------------------*/
 
-knot_zone_t *knot_zonedb_remove_zone(knot_zonedb_t *db,
-                                     const knot_dname_t *zone_name)
+int knot_zonedb_del(knot_zonedb_t *db, const knot_dname_t *zone_name)
 {
 	if (db == NULL || zone_name == NULL) {
-		return NULL;
-	}
-	
-	/* Find the possible zone to remove. */
-	int pos = knot_zonedb_array_search(db->array, db->count, zone_name);
-	if (pos < 0) {
-		return NULL;
+		return KNOT_EINVAL;
 	}
-
-	/* 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;
+	/* Can't guess maximum label count now. */
+	db->maxlabels = KNOT_DNAME_MAXLABELS;
+	/* Attempt to remove zone. */
+	int name_size = knot_dname_size(zone_name);
+	return hhash_del(db->hash, (const char*)zone_name, name_size);
 }
 
 /*----------------------------------------------------------------------------*/
 
 int knot_zonedb_build_index(knot_zonedb_t *db)
 {
-	if (!db) {
-		return KNOT_EINVAL;
-	}
+	/* Rebuild order index. */
+	hhash_build_index(db->hash);
 
-	/* 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. */
-	int prev_label_count = -1;
-	int current_label_count = -1;
-	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;
-		
+	/* Calculate maxlabels. */
+	db->maxlabels = 0;
+	knot_zonedb_iter_t it;
+	knot_zonedb_iter_begin(db, &it);
+	while (!knot_zonedb_iter_finished(&it)) {
+		knot_zone_t *zone = knot_zonedb_iter_val(&it);
+		db->maxlabels = MAX(db->maxlabels, knot_dname_labels(zone->name, NULL));
+		knot_zonedb_iter_next(&it);
 	}
-	
+
 	return KNOT_EOK;
 }
 
 /*----------------------------------------------------------------------------*/
 
-knot_zone_t *knot_zonedb_find_zone(knot_zonedb_t *db,
-                                       const knot_dname_t *zone_name)
+knot_zone_t *knot_zonedb_find(knot_zonedb_t *db, const knot_dname_t *zone_name)
 {
 	if (!db || !zone_name) {
 		return NULL;
 	}
 
-	int pos = knot_zonedb_array_search(db->array, db->count, zone_name);
-	if (pos < 0) {
+	int name_size = knot_dname_size(zone_name);
+	value_t *ret = hhash_find(db->hash, (const char*)zone_name, name_size);
+	if (ret == NULL) {
 		return NULL;
 	}
 
-	return db->array[pos];
+	return *ret;
 }
 
 /*----------------------------------------------------------------------------*/
 
-knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db,
-                                            const knot_dname_t *dname)
+knot_zone_t *knot_zonedb_find_suffix(knot_zonedb_t *db, const knot_dname_t *dname)
 {
-	int zone_labels = knot_dname_labels(dname, NULL);
-	if (db == NULL || zone_labels < 0) {
+	if (db == NULL || dname == NULL) {
 		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) {
-			dname = knot_wire_next_label(dname, NULL);
-			--zone_labels;
-		}
+	/* We know we have at most N label zones, so let's compare only those
+	 * N last labels. */
+	int zone_labels = knot_dname_labels(dname, NULL);
+	while (zone_labels > db->maxlabels) {
+		dname = knot_wire_next_label(dname, NULL);
+		--zone_labels;
+	}
 
-		/* Possible candidate, search the array. */
-		int k = knot_zonedb_array_search(sp->array, sp->count, dname);
-		if (k > -1) {
-			return sp->array[k];
+	/* Compare possible suffixes. */
+	knot_zone_t *ret = NULL;
+	while (zone_labels > -1) { /* Include root label. */
+		ret = knot_zonedb_find(db, dname);
+		if (ret != NULL) {
+			break;
 		}
+		dname = knot_wire_next_label(dname, NULL);
 	}
 
-	return NULL;
+	return ret;
 }
 
 /*----------------------------------------------------------------------------*/
@@ -275,7 +174,7 @@ knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db,
 
 	// Remove the contents from the zone, but keep the zone in the zonedb.
 
-	knot_zone_t *zone = knot_zonedb_find_zone(db, zone_name);
+	knot_zone_t *zone = knot_zonedb_find(db, zone_name);
 	if (zone == NULL) {
 		return NULL;
 	}
@@ -285,28 +184,20 @@ knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db,
 
 /*----------------------------------------------------------------------------*/
 
-size_t knot_zonedb_zone_count(const knot_zonedb_t *db)
+size_t knot_zonedb_size(const knot_zonedb_t *db)
 {
-	return db->count;
+	return db->hash->weight;
 }
 
 /*----------------------------------------------------------------------------*/
 
-const knot_zone_t **knot_zonedb_zones(const knot_zonedb_t *db)
+void knot_zonedb_free(knot_zonedb_t **db)
 {
-	if (db == NULL) {
-		return NULL;
+	if (db == NULL || *db == NULL) {
+		return;
 	}
-	
-	return (const knot_zone_t **)db->array;
-}
 
-/*----------------------------------------------------------------------------*/
-
-void knot_zonedb_free(knot_zonedb_t **db)
-{
-	free((*db)->array);
-	free(*db);
+	mp_delete((*db)->mm.ctx);
 	*db = NULL;
 }
 
@@ -314,9 +205,13 @@ void knot_zonedb_free(knot_zonedb_t **db)
 
 void knot_zonedb_deep_free(knot_zonedb_t **db)
 {
-	dbg_zonedb("Deleting zone db (%p).\n", *db);
-	for (unsigned i = 0; i < (*db)->count; ++i) {
-		delete_zone_from_db((*db)->array[i]);
+	/* Reindex for iteration. */
+	knot_zonedb_build_index(*db);
+	knot_zonedb_iter_t it;
+	knot_zonedb_iter_begin(*db, &it);
+	while (!knot_zonedb_iter_finished(&it)) {
+		delete_zone_from_db(knot_zonedb_iter_val(&it));
+		knot_zonedb_iter_next(&it);
 	}
 
 	knot_zonedb_free(db);
diff --git a/src/libknot/zone/zonedb.h b/src/libknot/zone/zonedb.h
index 581cc300fdd89e52a0401d5d18549128649537f7..9d3e94e373552809186d654188b0d77470a77e13 100644
--- a/src/libknot/zone/zonedb.h
+++ b/src/libknot/zone/zonedb.h
@@ -33,41 +33,32 @@
 #include "libknot/zone/zone.h"
 #include "libknot/zone/node.h"
 #include "libknot/dname.h"
+#include "common/hhash.h"
 
 /*
  * 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.
+ * zones is present (TLD case). Fortunately hhash is able to do linear scan if
+ * it has only a handful of names present. Furthermore, we track the name with
+ * the most labels in the database. So if we have for example a 'a.b.' in the
+ * database and search for 'c.d.a.b.' we can trim the 'c.d.' and search for
+ * the suffix as we now there can't be a closer match.
  */
 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];
+	uint16_t maxlabels;
+	hhash_t *hash;
+	mm_ctx_t mm;
 } knot_zonedb_t;
 
+/*
+ * Mapping of iterators to internal data structure.
+ */
+typedef hhash_iter_t knot_zonedb_iter_t;
+#define knot_zonedb_iter_begin(db, it) hhash_iter_begin((db)->hash, it, true)
+#define knot_zonedb_iter_finished(it) hhash_iter_finished(it)
+#define knot_zonedb_iter_next(it) hhash_iter_next(it)
+#define knot_zonedb_iter_val(it) *hhash_iter_val(it)
+
 /*----------------------------------------------------------------------------*/
 
 /*!
@@ -76,7 +67,7 @@ typedef struct {
  * \return Pointer to the created zone database structure or NULL if an error
  *         occured.
  */
-knot_zonedb_t *knot_zonedb_new(unsigned size);
+knot_zonedb_t *knot_zonedb_new(uint32_t size);
 
 /*!
  * \brief Adds new zone to the database.
@@ -87,7 +78,7 @@ knot_zonedb_t *knot_zonedb_new(unsigned size);
  * \retval KNOT_EOK
  * \retval KNOT_EZONEIN
  */
-int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone);
+int knot_zonedb_insert(knot_zonedb_t *db, knot_zone_t *zone);
 
 /*!
  * \brief Removes the given zone from the database if it exists.
@@ -98,18 +89,10 @@ int knot_zonedb_add_zone(knot_zonedb_t *db, knot_zone_t *zone);
  * \retval KNOT_EOK
  * \retval KNOT_ENOZONE
  */
-knot_zone_t * knot_zonedb_remove_zone(knot_zonedb_t *db,
-                                      const knot_dname_t *zone_name);
+int knot_zonedb_del(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);
 
@@ -122,8 +105,7 @@ int knot_zonedb_build_index(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(knot_zonedb_t *db,
-                                   const knot_dname_t *zone_name);
+knot_zone_t *knot_zonedb_find(knot_zonedb_t *db, const knot_dname_t *zone_name);
 
 
 /*!
@@ -135,14 +117,12 @@ knot_zone_t *knot_zonedb_find_zone(knot_zonedb_t *db,
  * \retval Zone in which the domain name should be present or NULL if no such
  *         zone is found.
  */
-knot_zone_t *knot_zonedb_find_zone_for_name(knot_zonedb_t *db,
-                                            const knot_dname_t *dname);
+knot_zone_t *knot_zonedb_find_suffix(knot_zonedb_t *db, const knot_dname_t *dname);
 
 knot_zone_contents_t *knot_zonedb_expire_zone(knot_zonedb_t *db,
                                               const knot_dname_t *zone_name);
 
-size_t knot_zonedb_zone_count(const knot_zonedb_t *db);
-const knot_zone_t **knot_zonedb_zones(const knot_zonedb_t *db);
+size_t knot_zonedb_size(const knot_zonedb_t *db);
 
 /*!
  * \brief Destroys and deallocates the zone database structure (but not the
@@ -159,7 +139,6 @@ void knot_zonedb_free(knot_zonedb_t **db);
  */
 void knot_zonedb_deep_free(knot_zonedb_t **db);
 
-/*----------------------------------------------------------------------------*/
 
 #endif /* _KNOT_ZONEDB_H_ */
 
diff --git a/tests/zonedb.c b/tests/zonedb.c
index bdb6f57c96c9f56f27f9a53c0be3db6377168527..9ebfa3d570ed3d42885a64fd2e80573f7af4388b 100644
--- a/tests/zonedb.c
+++ b/tests/zonedb.c
@@ -56,7 +56,7 @@ int main(int argc, char *argv[])
 			knot_dname_free(&dname);
 			goto cleanup;
 		}
-		if (knot_zonedb_add_zone(db, zones[i]) == KNOT_EOK) {
+		if (knot_zonedb_insert(db, zones[i]) == KNOT_EOK) {
 			++nr_passed;
 		} else {
 			diag("knot_zonedb_add_zone(%s) failed", zone_list[i]);
@@ -71,7 +71,7 @@ int main(int argc, char *argv[])
 	nr_passed = 0;
 	for (unsigned i = 0; i < ZONE_COUNT; ++i) {
 		dname = knot_dname_from_str(zone_list[i]);
-		if (knot_zonedb_find_zone(db, dname) == zones[i]) {
+		if (knot_zonedb_find(db, dname) == zones[i]) {
 			++nr_passed;
 		} else {
 			diag("knot_zonedb_find_zone(%s) failed", zone_list[i]);
@@ -88,7 +88,7 @@ int main(int argc, char *argv[])
 			strncat(buf, zone_list[i], strlen(zone_list[i]));
 		}
 		dname = knot_dname_from_str(buf);
-		if (knot_zonedb_find_zone_for_name(db, dname) == zones[i]) {
+		if (knot_zonedb_find_suffix(db, dname) == zones[i]) {
 			++nr_passed;
 		} else {
 			diag("knot_zonedb_find_zone(%s) failed", buf);
@@ -101,8 +101,7 @@ int main(int argc, char *argv[])
 	nr_passed = 0;
 	for (unsigned i = 0; i < ZONE_COUNT; ++i) {
 		dname = knot_dname_from_str(zone_list[i]);
-		zone = knot_zonedb_remove_zone(db, dname);
-		if (zone == zones[i]) {
+		if (knot_zonedb_del(db, dname) == KNOT_EOK) {
 			knot_zone_free(&zone);
 			++nr_passed;
 		} else {