diff --git a/src/knot/dnssec/nsec-chain.h b/src/knot/dnssec/nsec-chain.h
index b9cf6a3e1a117ebcdb12941a96a877baffb6ed5e..23b6ac984b24e1103efed3f12385d3ef53a14aef 100644
--- a/src/knot/dnssec/nsec-chain.h
+++ b/src/knot/dnssec/nsec-chain.h
@@ -63,15 +63,13 @@ typedef int (*chain_iterate_create_cb)(knot_node_t *, knot_node_t *,
 inline static void bitmap_add_node_rrsets(bitmap_t *bitmap,
                                           const knot_node_t *node)
 {
-	knot_rrset_t **node_rrsets = knot_node_create_rrsets(node);
 	for (int i = 0; i < node->rrset_count; i++) {
-		const knot_rrset_t *rr = node_rrsets[i];
-		if (rr->type != KNOT_RRTYPE_NSEC &&
-		    rr->type != KNOT_RRTYPE_RRSIG) {
-			bitmap_add_type(bitmap, rr->type);
+		knot_rrset_t rr = RRSET_INIT_N(node, i);
+		if (rr.type != KNOT_RRTYPE_NSEC &&
+		    rr.type != KNOT_RRTYPE_RRSIG) {
+			bitmap_add_type(bitmap, rr.type);
 		}
 	}
-	knot_node_free_created_rrsets(node, node_rrsets);
 }
 
 /*!
diff --git a/src/knot/nameserver/update.c b/src/knot/nameserver/update.c
index 21ff05d8e3931f5982bc020e8d5edf1b7954d14b..c0db3fa601f95becf34d4f692c808964be60a1dd 100644
--- a/src/knot/nameserver/update.c
+++ b/src/knot/nameserver/update.c
@@ -401,10 +401,7 @@ static int zones_process_update_auth(struct query_data *qdata)
 	}
 
 	// Cleanup.
-	xfrin_cleanup_successful_update(NULL);
-	if (sec_chs) {
-		xfrin_cleanup_successful_update(NULL);
-	}
+	xfrin_cleanup_successful_update(old_contents);
 
 	// Free changesets, but not the data.
 	zones_free_merged_changesets(chgsets, sec_chs);
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index bb66ac232e55c19229221dbb6c0fc0d9d17ec5f8..4db77baabda3ca63792045d0128bb8d004a5b922 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -1501,7 +1501,7 @@ int zones_store_and_apply_chgsets(knot_changesets_t *chs,
 		return KNOT_ERROR;
 	}
 
-	xfrin_cleanup_successful_update(NULL);
+	xfrin_cleanup_successful_update(zone->contents);
 
 	/* Free changesets, but not the data. */
 	knot_changesets_free(&chs);
diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c
index 5e396e5f5328853e1510fd2f6de2c30e37f339e3..c4ea97b905f81669ac8b79b13f580e7558f6c5eb 100644
--- a/src/knot/updates/ddns.c
+++ b/src/knot/updates/ddns.c
@@ -458,8 +458,7 @@ static int add_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset)
 	return knot_ddns_check_add_rr(changeset, rr_copy);
 }
 
-static int knot_ddns_check_remove_rr(knot_changeset_t *changeset,
-                                     knot_rrset_t *rr, size_t *apex_ns_rem)
+static bool skip_record_removal(knot_changeset_t *changeset, knot_rrset_t *rr)
 {
 	knot_rr_ln_t *rr_node = NULL;
 	WALK_LIST(rr_node, changeset->remove) {
@@ -467,7 +466,7 @@ static int knot_ddns_check_remove_rr(knot_changeset_t *changeset,
 		if (knot_rrset_equal(rr, rrset, KNOT_RRSET_COMPARE_WHOLE)) {
 			// Removing the same RR, drop.
 			knot_rrset_free(&rr, NULL);
-			return KNOT_EOK;
+			return true;
 		}
 	}
 
@@ -479,14 +478,11 @@ static int knot_ddns_check_remove_rr(knot_changeset_t *changeset,
 			knot_rrset_free(&rrset, NULL);
 			knot_rrset_free(&rr, NULL);
 			rem_node((node_t *)rr_node);
-			return KNOT_EOK;
+			return true;
 		}
 	}
 
-	if (apex_ns_rem) {
-		(*apex_ns_rem)++;
-	}
-	return knot_changeset_add_rrset(changeset, rr, KNOT_CHANGESET_REMOVE);
+	return false;
 }
 
 static int rem_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset,
@@ -497,7 +493,14 @@ static int rem_rr_to_chgset(const knot_rrset_t *rr, knot_changeset_t *changeset,
 		return KNOT_ENOMEM;
 	}
 
-	return knot_ddns_check_remove_rr(changeset, rr_copy, apex_ns_rem);
+	if (skip_record_removal(changeset, rr_copy)) {
+		return KNOT_EOK;
+	}
+
+	if (apex_ns_rem) {
+		(*apex_ns_rem)++;
+	}
+	return knot_changeset_add_rrset(changeset, rr_copy, KNOT_CHANGESET_REMOVE);
 }
 
 static int rem_rrset_to_chgset(const knot_rrset_t *rrset,
@@ -671,6 +674,40 @@ static int process_add_soa(const knot_node_t *node,
 	return add_rr_to_chgset(rr, changeset);
 }
 
+static bool node_contains_rr(const knot_node_t *node,
+                             const knot_rrset_t *rr)
+{
+	knot_rrset_t zone_rrset = RRSET_INIT(node, rr->type);
+	if (!knot_rrset_empty(&zone_rrset)) {
+		knot_rrset_t intersection;
+		int ret = knot_rrset_intersection(&zone_rrset, rr,
+		                                  &intersection, NULL);
+		if (ret != KNOT_EOK) {
+			return false;
+		}
+		const bool contains = !knot_rrset_empty(&intersection);
+		knot_rrs_clear(&intersection.rrs, NULL);
+		return contains;
+	} else {
+		return false;
+	}
+}
+
+static void remove_rr_from_changeset(knot_changeset_t *changeset,
+                                     const knot_rrset_t *rr)
+{
+	knot_rr_ln_t *rr_node = NULL;
+	node_t *nxt = NULL;
+	WALK_LIST_DELSAFE(rr_node, nxt, changeset->remove) {
+		knot_rrset_t *rrset = rr_node->rr;
+		if (knot_rrset_equal(rrset, rr, KNOT_RRSET_COMPARE_WHOLE)) {
+			knot_rrset_free(&rrset, NULL);
+			rem_node((node_t *)rr_node);
+			return;
+		}
+	}
+}
+
 static int process_add_normal(const knot_node_t *node,
                               const knot_rrset_t *rr,
                               knot_changeset_t *changeset)
@@ -680,6 +717,11 @@ static int process_add_normal(const knot_node_t *node,
 		return KNOT_EOK;
 	}
 
+	if (node && node_contains_rr(node, rr)) {
+		remove_rr_from_changeset(changeset, rr);
+		return KNOT_EOK;
+	}
+
 	return add_rr_to_chgset(rr, changeset);
 }
 
@@ -719,16 +761,10 @@ static int process_remove(const knot_rrset_t *rr,
 	}
 }
 
-static int knot_ddns_final_soa_to_chgset(const knot_rrset_t *soa,
+static int knot_ddns_final_soa_to_chgset(knot_rrset_t *soa,
                                          knot_changeset_t *changeset)
 {
-	knot_rrset_t *soa_copy = NULL;
-	int ret = knot_rrset_copy(soa, &soa_copy, NULL);
-	if (ret != KNOT_EOK) {
-		return ret;
-	}
-
-	knot_changeset_add_soa(changeset, soa_copy, KNOT_CHANGESET_ADD);
+	knot_changeset_add_soa(changeset, soa, KNOT_CHANGESET_ADD);
 
 	return KNOT_EOK;
 }
@@ -754,6 +790,30 @@ static int knot_ddns_process_rr(const knot_rrset_t *rr,
 	}
 }
 
+static bool skip_soa(const knot_rrset_t *rr, int64_t sn)
+{
+	if (rr->type == KNOT_RRTYPE_SOA
+	    && (rr->rclass == KNOT_CLASS_NONE
+	        || rr->rclass == KNOT_CLASS_ANY
+	        || knot_serial_compare(knot_rrs_soa_serial(&rr->rrs),
+	                               sn) <= 0)) {
+		return true;
+	}
+
+	return false;
+}
+
+static uint16_t ret_to_rcode(int ret)
+{
+	if (ret == KNOT_EMALF) {
+		return KNOT_RCODE_FORMERR;
+	} else if (ret == KNOT_EDENIED) {
+		return KNOT_RCODE_REFUSED;
+	} else {
+		return KNOT_RCODE_SERVFAIL;
+	}
+}
+
 int knot_ddns_process_update(knot_zone_contents_t *zone,
                              const knot_pkt_t *query,
                              knot_changeset_t *changeset,
@@ -804,8 +864,6 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 		/* Check if the entry is correct. */
 		ret = knot_ddns_check_update(rr, query, rcode);
 		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to check update RRSet:%s\n",
-			                knot_strerror(ret));
 			return ret;
 		}
 
@@ -818,36 +876,20 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 		 * be used.
 		 */
 		1 == 1; // multiple SOAs test
-		if (rr->type == KNOT_RRTYPE_SOA
-		    && (rr->rclass == KNOT_CLASS_NONE
-		        || rr->rclass == KNOT_CLASS_ANY
-		        || knot_serial_compare(knot_rrs_soa_serial(&rr->rrs),
-		                               sn) <= 0)) {
-			// This ignores also SOA removals
-			dbg_ddns_verb("Ignoring SOA...\n");
+		if (skip_soa(rr, sn)) {
 			continue;
 		}
 
 		dbg_ddns_verb("Processing RR %p...\n", rr);
 		ret = knot_ddns_process_rr(rr, zone, changeset, &apex_ns_removals);
 		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to process update RR:%s\n",
-			         knot_strerror(ret));
-			if (ret == KNOT_EMALF) {
-				*rcode = KNOT_RCODE_FORMERR;
-			} else if (ret == KNOT_EDENIED) {
-				*rcode = KNOT_RCODE_REFUSED;
-			} else {
-				*rcode = KNOT_RCODE_SERVFAIL;
-			}
+			*rcode = ret_to_rcode(ret);
 			return ret;
 		}
 
 		// we need the RR copy, that's why this code is here
 		if (rr->type == KNOT_RRTYPE_SOA) {
 			int64_t sn_rr = knot_rrs_soa_serial(&rr->rrs);
-			dbg_ddns_verb("Replacing SOA. Old serial: %"PRId64", "
-			              "new serial: %"PRId64"\n", sn_new, sn_rr);
 			assert(knot_serial_compare(sn_rr, sn) > 0);
 			sn_new = sn_rr;
 			soa_end = knot_rrset_cpy(rr, NULL);
@@ -867,9 +909,7 @@ int knot_ddns_process_update(knot_zone_contents_t *zone,
 		/* If not set, create new SOA. */
 		ret = knot_rrset_copy(soa_begin, &soa_end, NULL);
 		if (ret != KNOT_EOK) {
-			dbg_ddns("Failed to copy ending SOA: %s\n",
-			         knot_strerror(ret));
-			*rcode = KNOT_RCODE_SERVFAIL;
+			*rcode = ret_to_rcode(ret);
 			return ret;
 		}
 		knot_rrs_soa_serial_set(&soa_end->rrs, sn_new);
diff --git a/src/knot/updates/xfr-in.c b/src/knot/updates/xfr-in.c
index 8ff77bd8851ba01b846cf3540d91a659d32313a1..b287c500a6119d87dabb9bb4e9f39d493bcfbf69 100644
--- a/src/knot/updates/xfr-in.c
+++ b/src/knot/updates/xfr-in.c
@@ -635,6 +635,7 @@ void xfrin_cleanup_successful_update(knot_zone_contents_t *zone)
 	}
 
 	rrs_list_clear(&zone->old_data, NULL);
+	ptrlist_free(&zone->new_data, NULL);
 }
 
 /*----------------------------------------------------------------------------*/
@@ -732,6 +733,7 @@ void xfrin_rollback_update(knot_zone_contents_t *old_contents,
                            knot_zone_contents_t **new_contents)
 {
 	rrs_list_clear(&old_contents->new_data, NULL);
+	ptrlist_free(&old_contents->old_data, NULL);
 	xfrin_cleanup_failed_update(old_contents, new_contents);
 }
 
@@ -754,8 +756,8 @@ static int xfrin_replace_rrs_with_copy(knot_node_t *node,
 
 	// Add copied RRSet
 	ret = knot_node_add_rrset(node, &new_rr);
+	knot_rrs_clear(&new_rr.rrs, mm);
 	if (ret != KNOT_EOK) {
-		knot_rrs_clear(&new_rr.rrs, mm);
 		return ret;
 	}
 
@@ -821,6 +823,7 @@ static int xfrin_apply_remove(knot_zone_contents_t *contents,
 			clear_new_rrs(node, rr->type);
 			return ret;
 		}
+		assert(removed->rrs.rr_count > 0);
 		knot_rrset_free(&removed, NULL);
 
 		if (rrset.rrs.rr_count > 0) {
diff --git a/src/knot/updates/xfr-in.h b/src/knot/updates/xfr-in.h
index 226c53ea3541d4d2c6c8a82dda33ec938521044a..4f2425f7eca872a4dedc513cbdc25c4782a85a48 100644
--- a/src/knot/updates/xfr-in.h
+++ b/src/knot/updates/xfr-in.h
@@ -215,6 +215,7 @@ int xfrin_replace_rrset_in_node(knot_node_t *node,
                                 knot_zone_contents_t *contents);
 
 void xfrin_zone_contents_free(knot_zone_contents_t **contents);
+void xfrin_cleanup_successful_update(knot_zone_contents_t *zone);
 
 #endif /* _KNOTXFR_IN_H_ */
 
diff --git a/src/knot/zone/node.c b/src/knot/zone/node.c
index fcfd66dd6462118a2ca3968ce4a1a994895ca788..9266a0b59088cce60deaa54f1ee9fa33d3119a5f 100644
--- a/src/knot/zone/node.c
+++ b/src/knot/zone/node.c
@@ -678,8 +678,7 @@ void knot_node_free_rrsets(knot_node_t *node)
 	}
 
 	for (uint16_t i = 0; i < node->rrset_count; ++i) {
-		knot_rrs_clear(&node->rrs[i].rrs, NULL);
-		mm_free(NULL, node->rrs[i].additional);
+		rr_data_clear(&node->rrs[i], NULL);
 	}
 }
 
diff --git a/src/knot/zone/zone-contents.c b/src/knot/zone/zone-contents.c
index 5e6c424db4b7bc64fc066d51225b3c24dbfeee4e..f6e169c4a124c52e4e5b5dfad3e736551b96e83a 100644
--- a/src/knot/zone/zone-contents.c
+++ b/src/knot/zone/zone-contents.c
@@ -161,6 +161,9 @@ static int discover_additionals(struct rr_data *rr_data,
 
 	/* Create new additional nodes. */
 	uint16_t rdcount = rrs->rr_count;
+	if (rr_data->additional) {
+		free(rr_data->additional);
+	}
 	rr_data->additional = malloc(rdcount * sizeof(knot_node_t *));
 	if (rr_data->additional == NULL) {
 		ERR_ALLOC_FAILED;