diff --git a/src/knot/dnssec/nsec-chain.c b/src/knot/dnssec/nsec-chain.c
index ea7af471c6aaac14156e773499478a04a1400406..5ddd1b1a24f75a6140151acdb340f51a2ab313e5 100644
--- a/src/knot/dnssec/nsec-chain.c
+++ b/src/knot/dnssec/nsec-chain.c
@@ -145,7 +145,7 @@ static int connect_nsec_nodes(zone_node_t *a, zone_node_t *b,
 	}
 
 	// Add new NSEC to the changeset (no matter if old was removed)
-	ret = changeset_add_rrset(data->changeset, &new_nsec, 0);
+	ret = changeset_add_addition(data->changeset, &new_nsec, 0);
 	knot_rdataset_clear(&new_nsec.rrs, NULL);
 	return ret;
 }
@@ -223,7 +223,7 @@ int knot_nsec_changeset_remove(const zone_node_t *n,
 	}
 	if (!knot_rrset_empty(&nsec)) {
 		// update changeset
-		result = changeset_rem_rrset(changeset, &nsec, 0);
+		result = changeset_add_removal(changeset, &nsec, 0);
 		if (result != KNOT_EOK) {
 			return result;
 		}
@@ -251,7 +251,7 @@ int knot_nsec_changeset_remove(const zone_node_t *n,
 		}
 
 		// store RRSIG
-		result = changeset_rem_rrset(changeset, &synth_rrsigs, 0);
+		result = changeset_add_removal(changeset, &synth_rrsigs, 0);
 		knot_rdataset_clear(&synth_rrsigs.rrs, NULL);
 	}
 
diff --git a/src/knot/dnssec/zone-nsec.c b/src/knot/dnssec/zone-nsec.c
index 72ab911850653ffe4948fe7df395d37a7cd4648a..b090a4cb6df1839f99815480b011c09f81018416 100644
--- a/src/knot/dnssec/zone-nsec.c
+++ b/src/knot/dnssec/zone-nsec.c
@@ -239,7 +239,7 @@ static int remove_nsec3param(const zone_contents_t *zone, changeset_t *changeset
 	assert(changeset);
 
 	knot_rrset_t rrset = node_rrset(zone->apex, KNOT_RRTYPE_NSEC3PARAM);
-	int ret = changeset_rem_rrset(changeset, &rrset, 0);
+	int ret = changeset_add_removal(changeset, &rrset, 0);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
@@ -253,7 +253,7 @@ static int remove_nsec3param(const zone_contents_t *zone, changeset_t *changeset
 			return ret;
 		}
 
-		ret = changeset_rem_rrset(changeset, &rrsig, 0);
+		ret = changeset_add_removal(changeset, &rrsig, 0);
 		knot_rdataset_clear(&rrsig.rrs, NULL);
 		if (ret != KNOT_EOK) {
 			return ret;
@@ -307,7 +307,7 @@ static int add_nsec3param(const zone_contents_t *zone, changeset_t *changeset,
 		return r;
 	}
 
-	r = changeset_add_rrset(changeset, rrset, 0);
+	r = changeset_add_addition(changeset, rrset, 0);
 	knot_rrset_free(&rrset, NULL);
 	return r;
 }
diff --git a/src/knot/dnssec/zone-sign.c b/src/knot/dnssec/zone-sign.c
index d038460268a08a835155b04945c90fbd86e1ca7b..8a6652e81939e8953363470373ee37b68afec305 100644
--- a/src/knot/dnssec/zone-sign.c
+++ b/src/knot/dnssec/zone-sign.c
@@ -286,7 +286,7 @@ static int remove_expired_rrsigs(const knot_rrset_t *covered,
 	}
 
 	if (!knot_rrset_empty(&to_remove) && result == KNOT_EOK) {
-		result = changeset_rem_rrset(changeset, &to_remove, 0);
+		result = changeset_add_removal(changeset, &to_remove, 0);
 	}
 
 	knot_rdataset_clear(&synth_rrsig.rrs, NULL);
@@ -341,7 +341,7 @@ static int add_missing_rrsigs(const knot_rrset_t *covered,
 	}
 
 	if (!knot_rrset_empty(&to_add) && result == KNOT_EOK) {
-		result = changeset_add_rrset(changeset, &to_add, 0);
+		result = changeset_add_addition(changeset, &to_add, 0);
 	}
 
 	knot_rdataset_clear(&to_add.rrs, NULL);
@@ -374,7 +374,7 @@ static int remove_rrset_rrsigs(const knot_dname_t *owner, uint16_t type,
 		return KNOT_EOK;
 	}
 
-	ret = changeset_rem_rrset(changeset, &synth_rrsig, 0);
+	ret = changeset_add_removal(changeset, &synth_rrsig, 0);
 	knot_rdataset_clear(&synth_rrsig.rrs, NULL);
 
 	return ret;
@@ -460,7 +460,7 @@ static int remove_standalone_rrsigs(const zone_node_t *node,
 			if (ret != KNOT_EOK) {
 				return ret;
 			}
-			ret = changeset_rem_rrset(changeset, &to_remove, 0);
+			ret = changeset_add_removal(changeset, &to_remove, 0);
 			knot_rdataset_clear(&to_remove.rrs, NULL);
 			if (ret != KNOT_EOK) {
 				return ret;
@@ -742,7 +742,7 @@ static int remove_invalid_dnskeys(const knot_rrset_t *soa,
 	}
 
 	if (!knot_rrset_empty(&to_remove) && result == KNOT_EOK) {
-		result = changeset_rem_rrset(changeset, &to_remove, 0);
+		result = changeset_add_removal(changeset, &to_remove, 0);
 	}
 
 	knot_rdataset_clear(&to_remove.rrs, NULL);
@@ -798,7 +798,7 @@ static int add_missing_dnskeys(const knot_rrset_t *soa,
 	}
 
 	if (!knot_rrset_empty(&to_add) && result == KNOT_EOK) {
-		result = changeset_add_rrset(changeset, &to_add, 0);
+		result = changeset_add_addition(changeset, &to_add, 0);
 	}
 
 	knot_rdataset_clear(&to_add.rrs, NULL);
diff --git a/src/knot/nameserver/ixfr.c b/src/knot/nameserver/ixfr.c
index 91bcc82d2546c1976f33279e2d274e52aac767cb..b30e6b38bcce9a5c83c0079a80e216b34226fe18 100644
--- a/src/knot/nameserver/ixfr.c
+++ b/src/knot/nameserver/ixfr.c
@@ -495,13 +495,13 @@ static int solve_soa_add(const knot_rrset_t *rr, changeset_t *change, knot_mm_t
 /*! \brief Adds single RR into remove section of changeset. */
 static int solve_del(const knot_rrset_t *rr, changeset_t *change, knot_mm_t *mm)
 {
-	return changeset_rem_rrset(change, rr, 0);
+	return changeset_add_removal(change, rr, 0);
 }
 
 /*! \brief Adds single RR into add section of changeset. */
 static int solve_add(const knot_rrset_t *rr, changeset_t *change, knot_mm_t *mm)
 {
-	return changeset_add_rrset(change, rr, 0);
+	return changeset_add_addition(change, rr, 0);
 }
 
 /*! \brief Decides what the next IXFR-in state should be. */
diff --git a/src/knot/server/journal.c b/src/knot/server/journal.c
index 22faf20540d699b822615995fd522911770f4c34..92a687e76ae656d3942c3ec1340ca2089d55864c 100644
--- a/src/knot/server/journal.c
+++ b/src/knot/server/journal.c
@@ -742,10 +742,10 @@ static int changesets_unpack(changeset_t *chs)
 		} else {
 			/* Remove RRSets. */
 			if (in_remove_section) {
-				ret = changeset_rem_rrset(chs, &rrset, 0);
+				ret = changeset_add_removal(chs, &rrset, 0);
 			} else {
 				/* Add RRSets. */
-				ret = changeset_add_rrset(chs, &rrset, 0);
+				ret = changeset_add_addition(chs, &rrset, 0);
 			}
 		}
 		knot_rrset_clear(&rrset, NULL);
diff --git a/src/knot/updates/apply.c b/src/knot/updates/apply.c
index c30c885abd5f9c4ad0c9c3310d45297d29f71f5e..f987b042d6f9d460a05b86d3c89c743ca85e3f08 100644
--- a/src/knot/updates/apply.c
+++ b/src/knot/updates/apply.c
@@ -146,9 +146,23 @@ static bool can_remove(const zone_node_t *node, const knot_rrset_t *rr)
 }
 
 /*! \brief Removes single RR from zone contents. */
-static int remove_rr(apply_ctx_t *ctx, zone_tree_t *tree, zone_node_t *node,
-                     const knot_rrset_t *rr, changeset_t *chset)
+int apply_remove_rr(apply_ctx_t *ctx, zone_contents_t *contents,
+                     const knot_rrset_t *rr)
 {
+	// Find node for this owner
+	zone_node_t *node = zone_contents_find_node_for_rr(contents, rr);
+	if (!can_remove(node, rr)) {
+		// Cannot be removed, either no node or nonexistent RR
+		if (ctx->flags & APPLY_STRICT) {
+			// Don't ignore missing RR if strict. Required for IXFR.
+			return KNOT_ENORECORD;
+		}
+		return KNOT_EOK;
+	}
+
+	zone_tree_t *tree = knot_rrset_is_nsec3rel(rr) ?
+		                contents->nsec3_nodes : contents->nodes;
+
 	knot_rrset_t removed_rrset = node_rrset(node, rr->type);
 	knot_rdata_t *old_data = removed_rrset.rrs.data;
 	int ret = replace_rdataset_with_copy(node, rr->type);
@@ -198,23 +212,7 @@ static int apply_remove(apply_ctx_t *ctx, zone_contents_t *contents, changeset_t
 
 	knot_rrset_t rr = changeset_iter_next(&itt);
 	while (!knot_rrset_empty(&rr)) {
-		// Find node for this owner
-		zone_node_t *node = zone_contents_find_node_for_rr(contents, &rr);
-		if (!can_remove(node, &rr)) {
-			// Cannot be removed, either no node or nonexistent RR
-			if (ctx->flags & APPLY_STRICT) {
-				// Don't ignore missing RR if strict. Required for IXFR.
-				changeset_iter_clear(&itt);
-				return KNOT_ENORECORD;
-			}
-
-			rr = changeset_iter_next(&itt);
-			continue;
-		}
-
-		zone_tree_t *tree = knot_rrset_is_nsec3rel(&rr) ?
-		                    contents->nsec3_nodes : contents->nodes;
-		int ret = remove_rr(ctx, tree, node, &rr, chset);
+		int ret = apply_remove_rr(ctx, contents, &rr);
 		if (ret != KNOT_EOK) {
 			changeset_iter_clear(&itt);
 			return ret;
@@ -228,9 +226,15 @@ static int apply_remove(apply_ctx_t *ctx, zone_contents_t *contents, changeset_t
 }
 
 /*! \brief Adds a single RR into zone contents. */
-static int add_rr(apply_ctx_t *ctx, const zone_contents_t *zone, zone_node_t *node,
-                  const knot_rrset_t *rr, changeset_t *chset)
+int apply_add_rr(apply_ctx_t *ctx, zone_contents_t *zone,
+                 const knot_rrset_t *rr)
 {
+	// Get or create node with this owner
+	zone_node_t *node = zone_contents_get_node_for_rr(zone, rr);
+	if (node == NULL) {
+		return KNOT_ENOMEM;
+	}
+
 	knot_rrset_t changed_rrset = node_rrset(node, rr->type);
 	if (!knot_rrset_empty(&changed_rrset)) {
 		// Modifying existing RRSet.
@@ -278,14 +282,7 @@ static int apply_add(apply_ctx_t *ctx, zone_contents_t *contents, changeset_t *c
 
 	knot_rrset_t rr = changeset_iter_next(&itt);
 	while(!knot_rrset_empty(&rr)) {
-		// Get or create node with this owner
-		zone_node_t *node = zone_contents_get_node_for_rr(contents, &rr);
-		if (node == NULL) {
-			changeset_iter_clear(&itt);
-			return KNOT_ENOMEM;
-		}
-
-		int ret = add_rr(ctx, contents, node, &rr, chset);
+		int ret = apply_add_rr(ctx, contents, &rr);
 		if (ret != KNOT_EOK) {
 			changeset_iter_clear(&itt);
 			return ret;
@@ -298,10 +295,10 @@ static int apply_add(apply_ctx_t *ctx, zone_contents_t *contents, changeset_t *c
 }
 
 /*! \brief Replace old SOA with a new one. */
-static int apply_replace_soa(apply_ctx_t *ctx, zone_contents_t *contents, changeset_t *chset)
+int apply_replace_soa(apply_ctx_t *ctx, zone_contents_t *contents, changeset_t *chset)
 {
 	assert(chset->soa_from && chset->soa_to);
-	int ret = remove_rr(ctx, contents->nodes, contents->apex, chset->soa_from, chset);
+	int ret = apply_remove_rr(ctx, contents, chset->soa_from);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
@@ -311,7 +308,7 @@ static int apply_replace_soa(apply_ctx_t *ctx, zone_contents_t *contents, change
 		return KNOT_EINVAL;
 	}
 
-	return add_rr(ctx, contents, contents->apex, chset->soa_to, chset);
+	return apply_add_rr(ctx, contents, chset->soa_to);
 }
 
 /*! \brief Apply single change to zone contents structure. */
@@ -347,7 +344,7 @@ static int apply_single(apply_ctx_t *ctx, zone_contents_t *contents, changeset_t
 /* --------------------- Zone copy and finalization ------------------------- */
 
 /*! \brief Creates a shallow zone contents copy. */
-static int prepare_zone_copy(zone_contents_t *old_contents,
+int apply_prepare_zone_copy(zone_contents_t *old_contents,
                              zone_contents_t **new_contents)
 {
 	if (old_contents == NULL || new_contents == NULL) {
@@ -397,7 +394,7 @@ int apply_changesets(apply_ctx_t *ctx, zone_t *zone, list_t *chsets, zone_conten
 	}
 
 	zone_contents_t *contents_copy = NULL;
-	int ret = prepare_zone_copy(old_contents, &contents_copy);
+	int ret = apply_prepare_zone_copy(old_contents, &contents_copy);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
@@ -441,7 +438,7 @@ int apply_changeset(apply_ctx_t *ctx, zone_t *zone, changeset_t *change, zone_co
 	}
 
 	zone_contents_t *contents_copy = NULL;
-	int ret = prepare_zone_copy(old_contents, &contents_copy);
+	int ret = apply_prepare_zone_copy(old_contents, &contents_copy);
 	if (ret != KNOT_EOK) {
 		return ret;
 	}
@@ -535,12 +532,14 @@ void update_rollback(apply_ctx_t *ctx)
 
 void update_free_zone(zone_contents_t **contents)
 {
-	zone_tree_apply((*contents)->nodes, free_additional, NULL);
-	zone_tree_deep_free(&(*contents)->nodes);
-	zone_tree_deep_free(&(*contents)->nsec3_nodes);
+	if (contents && *contents) {
+		zone_tree_apply((*contents)->nodes, free_additional, NULL);
+		zone_tree_deep_free(&(*contents)->nodes);
+		zone_tree_deep_free(&(*contents)->nsec3_nodes);
 
-	dnssec_nsec3_params_free(&(*contents)->nsec3_params);
+		dnssec_nsec3_params_free(&(*contents)->nsec3_params);
 
-	free(*contents);
-	*contents = NULL;
+		free(*contents);
+		*contents = NULL;
+	}
 }
diff --git a/src/knot/updates/apply.h b/src/knot/updates/apply.h
index fa3f1072afe5288896df3e3562ba5cd400fa8998..a2dbfd8da1f24ffedd1ef54014645a6e87f60135 100644
--- a/src/knot/updates/apply.h
+++ b/src/knot/updates/apply.h
@@ -48,6 +48,20 @@ typedef struct apply_ctx apply_ctx_t;
  */
 void apply_init_ctx(apply_ctx_t *ctx, uint32_t flags);
 
+/*! \brief Removes single RR from zone contents. */
+int apply_remove_rr(apply_ctx_t *ctx, zone_contents_t *contents,
+                    const knot_rrset_t *rr);
+
+/*! \brief Adds a single RR into zone contents. */
+int apply_add_rr(apply_ctx_t *ctx, zone_contents_t *contents,
+                 const knot_rrset_t *rr);
+
+int apply_replace_soa(apply_ctx_t *ctx, zone_contents_t *contents, changeset_t *chset);
+
+/*! \brief Creates a shallow zone contents copy. */
+int apply_prepare_zone_copy(zone_contents_t *old_contents,
+                            zone_contents_t **new_contents);
+
 /*!
  * \brief Applies changesets *with* zone shallow copy.
  *
diff --git a/src/knot/updates/changesets.c b/src/knot/updates/changesets.c
index 090507c111f761d19f1b39f0bb43660a02dfe650..50a8c882ce27831acd2a057fdb87e2afa16d90ae 100644
--- a/src/knot/updates/changesets.c
+++ b/src/knot/updates/changesets.c
@@ -256,7 +256,7 @@ size_t changeset_size(const changeset_t *ch)
 	return size;
 }
 
-int changeset_add_rrset(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags)
+int changeset_add_addition(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags)
 {
 	if (!ch || !rrset) {
 		return KNOT_EINVAL;
@@ -288,7 +288,7 @@ int changeset_add_rrset(changeset_t *ch, const knot_rrset_t *rrset, unsigned fla
 	return ret;
 }
 
-int changeset_rem_rrset(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags)
+int changeset_add_removal(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags)
 {
 	if (!ch || !rrset) {
 		return KNOT_EINVAL;
@@ -320,6 +320,36 @@ int changeset_rem_rrset(changeset_t *ch, const knot_rrset_t *rrset, unsigned fla
 	return ret;
 }
 
+int changeset_remove_addition(changeset_t *ch, const knot_rrset_t *rrset)
+{
+	if (rrset->type == KNOT_RRTYPE_SOA) {
+		/* Do not add SOAs into actual contents. */
+		if (ch->soa_to != NULL) {
+			knot_rrset_free(&ch->soa_to, NULL);
+			ch->soa_to = NULL;
+		}
+		return KNOT_EOK;
+	}
+
+	zone_node_t *n = NULL;
+	return zone_contents_remove_rr(ch->add, rrset, &n);
+}
+
+int changeset_remove_removal(changeset_t *ch, const knot_rrset_t *rrset)
+{
+	if (rrset->type == KNOT_RRTYPE_SOA) {
+		/* Do not add SOAs into actual contents. */
+		if (ch->soa_from != NULL) {
+			knot_rrset_free(&ch->soa_from, NULL);
+			ch->soa_from = NULL;
+		}
+		return KNOT_EOK;
+	}
+
+	zone_node_t *n = NULL;
+	return zone_contents_remove_rr(ch->remove, rrset, &n);
+}
+
 int changeset_merge(changeset_t *ch1, const changeset_t *ch2)
 {
 	changeset_iter_t itt;
@@ -327,7 +357,7 @@ int changeset_merge(changeset_t *ch1, const changeset_t *ch2)
 
 	knot_rrset_t rrset = changeset_iter_next(&itt);
 	while (!knot_rrset_empty(&rrset)) {
-		int ret = changeset_add_rrset(ch1, &rrset, CHANGESET_CHECK);
+		int ret = changeset_add_addition(ch1, &rrset, CHANGESET_CHECK);
 		if (ret != KNOT_EOK) {
 			changeset_iter_clear(&itt);
 			return ret;
@@ -340,7 +370,7 @@ int changeset_merge(changeset_t *ch1, const changeset_t *ch2)
 
 	rrset = changeset_iter_next(&itt);
 	while (!knot_rrset_empty(&rrset)) {
-		int ret = changeset_rem_rrset(ch1, &rrset, CHANGESET_CHECK);
+		int ret = changeset_add_removal(ch1, &rrset, CHANGESET_CHECK);
 		if (ret != KNOT_EOK) {
 			changeset_iter_clear(&itt);
 			return ret;
diff --git a/src/knot/updates/changesets.h b/src/knot/updates/changesets.h
index da831c9fb0975c502beb6c3e691feaaf92c8883f..efb40f8e7f547ffada1a0a31a6b19c5954580126 100644
--- a/src/knot/updates/changesets.h
+++ b/src/knot/updates/changesets.h
@@ -99,7 +99,7 @@ size_t changeset_size(const changeset_t *ch);
  *
  * \return KNOT_E*
  */
-int changeset_add_rrset(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags);
+int changeset_add_addition(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags);
 
 /*!
  * \brief Add RRSet to 'remove' part of changeset.
@@ -110,7 +110,28 @@ int changeset_add_rrset(changeset_t *ch, const knot_rrset_t *rrset, unsigned fla
  *
  * \return KNOT_E*
  */
-int changeset_rem_rrset(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags);
+int changeset_add_removal(changeset_t *ch, const knot_rrset_t *rrset, unsigned flags);
+
+
+/*!
+ * \brief Remove an RRSet from the 'add' part of changeset.
+ *
+ * \param ch                Changeset to add RRSet into.
+ * \param rrset             RRSet to be added.
+ *
+ * \return KNOT_E*
+ */
+int changeset_remove_addition(changeset_t *ch, const knot_rrset_t *rrset);
+
+/*!
+ * \brief Remove an RRSet from the 'remove' part of changeset.
+ *
+ * \param ch                Changeset to add RRSet into.
+ * \param rrset             RRSet to be added.
+ *
+ * \return KNOT_E*
+ */
+int changeset_remove_removal(changeset_t *ch, const knot_rrset_t *rrset);
 
 /*!
  * \brief Merges two changesets together. Legacy, to be removed with new zone API.
diff --git a/src/knot/updates/ddns.c b/src/knot/updates/ddns.c
index 2a7c4df7289423903c2ebb6e941ab5abcf96aa5b..6b932ac01d70828e6f3e7abe6ac7f7f3f07549c4 100644
--- a/src/knot/updates/ddns.c
+++ b/src/knot/updates/ddns.c
@@ -538,15 +538,20 @@ static int process_rem_node(const knot_rrset_t *rr,
 		return KNOT_EOK;
 	}
 
+	zone_node_t *node_copy = node_shallow_copy(node, NULL);
+
 	// Remove all RRSets from node
-	for (int i = 0; i < node->rrset_count; ++i) {
-		knot_rrset_t rrset = node_rrset_at(node, i);
-		int ret = process_rem_rrset(&rrset, node, update);
+	size_t rrset_count = node_copy->rrset_count;
+	for (int i = 0; i < rrset_count; ++i) {
+		knot_rrset_t rrset = node_rrset_at(node_copy, rrset_count - i - 1);
+		int ret = process_rem_rrset(&rrset, node_copy, update);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
 	}
 
+	node_free(&node_copy, NULL);
+
 	return KNOT_EOK;
 }
 
diff --git a/src/knot/updates/zone-update.c b/src/knot/updates/zone-update.c
index 3b16995f8fa6d126892572902d1662986afb68b2..7e048bd73aeb807fec50328d7c6a01ac36e78f12 100644
--- a/src/knot/updates/zone-update.c
+++ b/src/knot/updates/zone-update.c
@@ -24,109 +24,6 @@
 
 #include <urcu.h>
 
-static int add_to_node(zone_node_t *node, const zone_node_t *add_node,
-                       knot_mm_t *mm)
-{
-	for (uint16_t i = 0; i < add_node->rrset_count; ++i) {
-		knot_rrset_t rr = node_rrset_at(add_node, i);
-		if (!knot_rrset_empty(&rr)) {
-			int ret = node_add_rrset(node, &rr, mm);
-			if (ret != KNOT_EOK) {
-				return ret;
-			}
-		}
-	}
-
-	return KNOT_EOK;
-}
-
-static int rem_from_node(zone_node_t *node, const zone_node_t *rem_node,
-                         knot_mm_t *mm)
-{
-	for (uint16_t i = 0; i < rem_node->rrset_count; ++i) {
-		/* Remove each found RR from 'node'. */
-		knot_rrset_t rem_rrset = node_rrset_at(rem_node, i);
-		knot_rdataset_t *to_change = node_rdataset(node, rem_rrset.type);
-		if (to_change) {
-			/* Remove data from synthesized node */
-			int ret = knot_rdataset_subtract(to_change,
-			                                 &rem_rrset.rrs,
-			                                 mm);
-			if (ret != KNOT_EOK) {
-				return ret;
-			}
-			/* Remove whole rdataset if empty */
-			if (to_change->rr_count == 0) {
-				node_remove_rdataset(node, rem_rrset.type);
-			}
-		}
-	}
-
-	return KNOT_EOK;
-}
-
-static int apply_changes_to_node(zone_node_t *synth_node, const zone_node_t *add_node,
-                                 const zone_node_t *rem_node, knot_mm_t *mm)
-{
-	/* Add changes to node */
-	if (!node_empty(add_node)) {
-		int ret = add_to_node(synth_node, add_node, mm);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	/* Remove changes from node */
-	if (!node_empty(rem_node)) {
-		int ret = rem_from_node(synth_node, rem_node, mm);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	return KNOT_EOK;
-}
-
-static int deep_copy_node_data(zone_node_t *node_copy, const zone_node_t *node,
-                               knot_mm_t *mm)
-{
-	/* Clear space for RRs */
-	node_copy->rrs = NULL;
-	node_copy->rrset_count = 0;
-
-	for (uint16_t i = 0; i < node->rrset_count; ++i) {
-		knot_rrset_t rr = node_rrset_at(node, i);
-		int ret = node_add_rrset(node_copy, &rr, mm);
-		if (ret != KNOT_EOK) {
-			return ret;
-		}
-	}
-
-	return KNOT_EOK;
-}
-
-static zone_node_t *node_deep_copy(const zone_node_t *node, knot_mm_t *mm)
-{
-	if (node == NULL) {
-		return NULL;
-	}
-
-	/* Shallow copy old node */
-	zone_node_t *synth_node = node_shallow_copy(node, mm);
-	if (synth_node == NULL) {
-		return NULL;
-	}
-
-	/* Deep copy data inside node copy. */
-	int ret = deep_copy_node_data(synth_node, node, mm);
-	if (ret != KNOT_EOK) {
-		node_free(&synth_node, mm);
-		return NULL;
-	}
-
-	return synth_node;
-}
-
 static int init_incremental(zone_update_t *update, zone_t *zone)
 {
 	if (zone->contents == NULL) {
@@ -138,10 +35,21 @@ static int init_incremental(zone_update_t *update, zone_t *zone)
 		return ret;
 	}
 
+	ret = apply_prepare_zone_copy(zone->contents, &update->new_cont);
+	if (ret != KNOT_EOK) {
+		changeset_clear(&update->change);
+		return ret;
+	}
+
+	//! \todo is there a better way?
+	update->a_ctx.apex = update->new_cont->apex;
+
 	/* Copy base SOA RR. */
 	update->change.soa_from =
 		node_create_rrset(update->zone->contents->apex, KNOT_RRTYPE_SOA);
 	if (update->change.soa_from == NULL) {
+		changeset_clear(&update->change);
+		zone_contents_free(&update->new_cont);
 		return KNOT_ENOMEM;
 	}
 
@@ -158,79 +66,6 @@ static int init_full(zone_update_t *update, zone_t *zone)
 	return KNOT_EOK;
 }
 
-static const zone_node_t *get_synth_node(zone_update_t *update, const knot_dname_t *dname)
-{
-	const zone_node_t *old_node =
-		zone_contents_find_node(update->zone->contents, dname);
-
-	if (old_node == update->zone->contents->apex && update->change.soa_to != NULL) {
-		/* We have an apex and a SOA change, make a copy and apply the change. */
-		zone_node_t *synth_node = node_deep_copy(old_node, &update->mm);
-		if (synth_node == NULL) {
-			return NULL;
-		}
-
-		/* Remove the old SOA */
-		knot_rdataset_t *from = node_rdataset(synth_node, KNOT_RRTYPE_SOA);
-		knot_rdataset_t *what = node_rdataset(old_node, KNOT_RRTYPE_SOA);
-		int ret = knot_rdataset_subtract(from, what, &update->mm);
-		if (ret != KNOT_EOK) {
-			node_free_rrsets(synth_node, &update->mm);
-			node_free(&synth_node, &update->mm);
-			return NULL;
-		}
-
-		/* Add the new SOA */
-		ret = node_add_rrset(synth_node, update->change.soa_to, &update->mm);
-		if (ret != KNOT_EOK) {
-			node_free_rrsets(synth_node, &update->mm);
-			node_free(&synth_node, &update->mm);
-			return NULL;
-		}
-
-		old_node = synth_node;
-	}
-
-	const zone_node_t *add_node =
-		zone_contents_find_node(update->change.add, dname);
-	const zone_node_t *rem_node =
-		zone_contents_find_node(update->change.remove, dname);
-
-	const bool have_change = !node_empty(add_node) || !node_empty(rem_node);
-	if (!have_change) {
-		/* Nothing to apply */
-		return old_node;
-	}
-
-	if (old_node == NULL) {
-		if (add_node && node_empty(rem_node)) {
-			/* Just addition */
-			return add_node;
-		} else {
-			/* Addition and deletion */
-			old_node = add_node;
-			add_node = NULL;
-		}
-	}
-
-	/* We have to apply changes to node. */
-	zone_node_t *synth_node = node_deep_copy(old_node, &update->mm);
-	if (synth_node == NULL) {
-		return NULL;
-	}
-
-	/* Apply changes to node. */
-	int ret = apply_changes_to_node(synth_node, add_node, rem_node,
-	                                &update->mm);
-	if (ret != KNOT_EOK) {
-		node_free_rrsets(synth_node, &update->mm);
-		node_free(&synth_node, &update->mm);
-		return NULL;
-	}
-
-	return synth_node;
-}
-
 /* ------------------------------- API -------------------------------------- */
 
 int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t flags)
@@ -262,11 +97,7 @@ const zone_node_t *zone_update_get_node(zone_update_t *update, const knot_dname_
 		return NULL;
 	}
 
-	if (update->flags & UPDATE_FULL) {
-		return zone_contents_find_node(update->new_cont, dname);
-	} else {
-		return get_synth_node(update, dname);
-	}
+	return zone_contents_find_node(update->new_cont, dname);
 }
 
 const zone_node_t *zone_update_get_apex(zone_update_t *update)
@@ -330,6 +161,7 @@ void zone_update_clear(zone_update_t *update)
 	if (update->flags & UPDATE_INCREMENTAL) {
 		/* Revert any changes on error, do nothing on success. */
 		update_rollback(&update->a_ctx);
+		update_free_zone(&update->new_cont);
 		changeset_clear(&update->change);
 	} else if (update->flags & UPDATE_FULL) {
 		zone_contents_deep_free(&update->new_cont);
@@ -345,7 +177,27 @@ int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset)
 	}
 
 	if (update->flags & UPDATE_INCREMENTAL) {
-		return changeset_add_rrset(&update->change, rrset, CHANGESET_CHECK);
+		int ret = changeset_add_addition(&update->change, rrset, CHANGESET_CHECK);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+
+		if (rrset->type == KNOT_RRTYPE_SOA) {
+			/* replace previous SOA */
+			ret = apply_replace_soa(&update->a_ctx, update->new_cont, &update->change);
+			if (ret != KNOT_EOK) {
+				changeset_remove_addition(&update->change, rrset);
+			}
+			return ret;
+		}
+
+		ret = apply_add_rr(&update->a_ctx, update->new_cont, rrset);
+		if (ret != KNOT_EOK) {
+			changeset_remove_addition(&update->change, rrset);
+			return ret;
+		}
+
+		return KNOT_EOK;
 	} else if (update->flags & UPDATE_FULL) {
 		zone_node_t *n = NULL;
 		return zone_contents_add_rr(update->new_cont, rrset, &n);
@@ -361,7 +213,23 @@ int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset)
 	}
 
 	if (update->flags & UPDATE_INCREMENTAL) {
-		return changeset_rem_rrset(&update->change, rrset, CHANGESET_CHECK);
+		int ret = changeset_add_removal(&update->change, rrset, CHANGESET_CHECK);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+
+		if (rrset->type == KNOT_RRTYPE_SOA) {
+			/* SOA is replaced with addition */
+			return KNOT_EOK;
+		}
+
+		ret = apply_remove_rr(&update->a_ctx, update->new_cont, rrset);
+		if (ret != KNOT_EOK) {
+			changeset_remove_removal(&update->change, rrset);
+			return ret;
+		}
+
+		return KNOT_EOK;
 	} else if (update->flags & UPDATE_FULL) {
 		zone_node_t *n = NULL;
 		knot_rrset_t *rrs_copy = knot_rrset_copy(rrset, &update->mm);
@@ -381,26 +249,24 @@ int zone_update_remove_rrset(zone_update_t *update, knot_dname_t *owner, uint16_
 
 	if (update->flags & UPDATE_INCREMENTAL) {
 		/* Remove the RRSet from the original node */
-		const zone_node_t *node = zone_contents_find_node(update->zone->contents, owner);
+		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
 		if (node != NULL) {
 			knot_rrset_t rrset = node_rrset(node, type);
-			int ret = changeset_rem_rrset(&update->change, &rrset, CHANGESET_CHECK);
+			int ret = changeset_add_removal(&update->change, &rrset, CHANGESET_CHECK);
 			if (ret != KNOT_EOK) {
 				return ret;
 			}
-		}
 
-		/* Remove the RRSet from the additions in the changeset */
-		const zone_node_t *additions = zone_contents_find_node(update->change.add, owner);
-		if (additions != NULL) {
-			knot_rrset_t rrset = node_rrset(additions, type);
-			int ret = changeset_rem_rrset(&update->change, &rrset, CHANGESET_CHECK);
+			if (type == KNOT_RRTYPE_SOA) {
+				/* SOA is replaced with addition */
+				return KNOT_EOK;
+			}
+
+			ret = apply_remove_rr(&update->a_ctx, update->new_cont, &rrset);
 			if (ret != KNOT_EOK) {
 				return ret;
 			}
-		}
-
-		if (node == NULL && additions == NULL) {
+		} else {
 			return KNOT_ENONODE;
 		}
 	} else if (update->flags & UPDATE_FULL) {
@@ -427,33 +293,28 @@ int zone_update_remove_node(zone_update_t *update, const knot_dname_t *owner)
 	}
 
 	if (update->flags & UPDATE_INCREMENTAL) {
-		/* Remove all RRSets from the original node */
-		const zone_node_t *node = zone_contents_find_node(update->zone->contents, owner);
+		/* Remove all RRSets from the new node */
+		const zone_node_t *node = zone_contents_find_node(update->new_cont, owner);
 		if (node != NULL) {
 			size_t rrset_count = node->rrset_count;
 			for (int i = 0; i < rrset_count; ++i) {
 				knot_rrset_t rrset = node_rrset_at(node, rrset_count - 1 - i);
-				int ret = changeset_rem_rrset(&update->change, &rrset, CHANGESET_CHECK);
+				int ret = changeset_add_removal(&update->change, &rrset, CHANGESET_CHECK);
 				if (ret != KNOT_EOK) {
 					return ret;
 				}
-			}
-		}
 
-		/* Remove all RRSets from the additions in the changeset */
-		const zone_node_t *additions = zone_contents_find_node(update->change.add, owner);
-		if (additions != NULL) {
-			size_t rrset_count = additions->rrset_count;
-			for (int i = 0; i < rrset_count; ++i) {
-				knot_rrset_t rrset = node_rrset_at(additions, rrset_count - 1 - i);
-				int ret = changeset_rem_rrset(&update->change, &rrset, CHANGESET_CHECK);
+				if (rrset.type == KNOT_RRTYPE_SOA) {
+					/* SOA is replaced with addition */
+					return KNOT_EOK;
+				}
+
+				ret = apply_remove_rr(&update->a_ctx, update->new_cont, &rrset);
 				if (ret != KNOT_EOK) {
 					return ret;
 				}
 			}
-		}
-
-		if (node == NULL && additions == NULL) {
+		} else {
 			return KNOT_ENONODE;
 		}
 	} else if (update->flags & UPDATE_FULL) {
@@ -598,31 +459,34 @@ static int commit_incremental(conf_t *conf, zone_update_t *update, zone_contents
 		return KNOT_EOK;
 	}
 
+	zone_contents_t *new_contents = update->new_cont;
 	int ret = KNOT_EOK;
 	if (zone_update_to(update) == NULL) {
 		/* No SOA in the update, create one according to the current policy */
 		conf_val_t val = conf_zone_get(conf, C_SERIAL_POLICY, update->zone->name);
 		ret = set_new_soa(update, conf_opt(&val));
 		if (ret != KNOT_EOK) {
+			update_rollback(&update->a_ctx);
+			update_free_zone(&new_contents);
+			changeset_clear(&update->change);
 			return ret;
 		}
-	}
 
-	/* Apply changes. */
-	zone_contents_t *new_contents = NULL;
-	ret = apply_changeset(&update->a_ctx, update->zone, &update->change, &new_contents);
-	if (ret != KNOT_EOK) {
-		changeset_clear(&update->change);
-		return ret;
+		ret = apply_replace_soa(&update->a_ctx, new_contents, &update->change);
+		if (ret != KNOT_EOK) {
+			update_rollback(&update->a_ctx);
+			update_free_zone(&new_contents);
+			changeset_clear(&update->change);
+			return ret;
+		}
 	}
 
-	assert(new_contents);
-
 	conf_val_t val = conf_zone_get(conf, C_DNSSEC_SIGNING, update->zone->name);
 	bool dnssec_enable = (update->flags & UPDATE_SIGN) && conf_bool(&val);
 
 	/* Sign the update. */
 	if (dnssec_enable) {
+		zone_contents_adjust_pointers(new_contents);
 		ret = sign_update(update, new_contents);
 		if (ret != KNOT_EOK) {
 			update_rollback(&update->a_ctx);
@@ -630,6 +494,9 @@ static int commit_incremental(conf_t *conf, zone_update_t *update, zone_contents
 			changeset_clear(&update->change);
 			return ret;
 		}
+	} else {
+		//! \todo refactor apply_* to make this shinier
+		zone_contents_adjust_full(new_contents);
 	}
 
 	/* Write changes to journal if all went well. (DNSSEC merged) */
@@ -700,11 +567,6 @@ int zone_update_commit(conf_t *conf, zone_update_t *update)
 	int64_t size_limit = conf_int(&val);
 
 	if (new_contents != NULL && new_contents->size > size_limit) {
-		if (update->flags & UPDATE_INCREMENTAL) {
-			update_rollback(&update->a_ctx);
-			update_free_zone(&new_contents);
-			changeset_clear(&update->change);
-		}
 		return KNOT_EZONESIZE;
 	}
 
@@ -721,6 +583,7 @@ int zone_update_commit(conf_t *conf, zone_update_t *update)
 		} else if (update->flags & UPDATE_INCREMENTAL) {
 			update_cleanup(&update->a_ctx);
 			update_free_zone(&old_contents);
+			update->new_cont = NULL;
 			changeset_clear(&update->change);
 		}
 	}
@@ -730,37 +593,13 @@ int zone_update_commit(conf_t *conf, zone_update_t *update)
 
 static void select_next_node(zone_update_iter_t *it)
 {
-	int compare = 0;
 	if (it->base_node != NULL) {
-		if (it->add_node != NULL) {
-			/* Both original and new node exists. Choose the 'smaller' node to return. */
-			compare = knot_dname_cmp(it->base_node->owner, it->add_node->owner);
-			if (compare <= 0) {
-				/* Return the original node. */
-				it->next_node = it->base_node;
-				it->base_node = NULL;
-				if (compare == 0) {
-					it->add_node = NULL;
-				}
-			} else {
-				/* Return the new node. */
-				it->next_node = it->add_node;
-				it->add_node = NULL;
-			}
-		} else {
-			/* Return the original node. */
-			it->next_node = it->base_node;
-			it->base_node = NULL;
-		}
+		/* Return the original node. */
+		it->next_node = it->base_node;
+		it->base_node = NULL;
 	} else {
-		if (it->add_node != NULL) {
-			/* Return the new node. */
-			it->next_node = it->add_node;
-			it->add_node = NULL;
-		} else {
-			/* Iteration done. */
-			it->next_node = NULL;
-		}
+		/* Iteration done. */
+		it->next_node = NULL;
 	}
 }
 
@@ -774,7 +613,8 @@ static int iter_init_tree_iters(zone_update_iter_t *it, zone_update_t *update,
 	if (update->flags & UPDATE_FULL) {
 		_contents = update->new_cont;
 	} else if (update->flags & UPDATE_INCREMENTAL) {
-		_contents = update->zone->contents;
+		//_contents = update->zone->contents;
+		_contents = update->new_cont;
 	} else {
 		return KNOT_EINVAL;
 	}
@@ -787,40 +627,9 @@ static int iter_init_tree_iters(zone_update_iter_t *it, zone_update_t *update,
 		return KNOT_ENOMEM;
 	}
 
-	/* Set changeset iterator. */
-	if ((update->flags & UPDATE_INCREMENTAL) && !changeset_empty(&update->change)) {
-		tree = nsec3 ? update->change.add->nsec3_nodes :
-		               update->change.add->nodes;
-		if (tree == NULL) {
-			it->add_it = NULL;
-		} else {
-			hattrie_build_index(tree);
-			it->add_it = hattrie_iter_begin(tree, true);
-			if (it->add_it == NULL) {
-				hattrie_iter_free(it->base_it);
-				return KNOT_ENOMEM;
-			}
-		}
-	} else {
-		it->add_it = NULL;
-	}
-
 	return KNOT_EOK;
 }
 
-static int iter_get_added_node(zone_update_iter_t *it)
-{
-	hattrie_iter_next(it->add_it);
-	if (hattrie_iter_finished(it->add_it)) {
-		hattrie_iter_free(it->add_it);
-		it->add_it = NULL;
-		return KNOT_ENOENT;
-	}
-
-	it->add_node = (zone_node_t *)(*hattrie_iter_val(it->add_it));
-
-	return KNOT_EOK;
-}
 
 static int iter_get_synth_node(zone_update_iter_t *it)
 {
@@ -835,6 +644,7 @@ static int iter_get_synth_node(zone_update_iter_t *it)
 	if (it->update->flags & UPDATE_FULL) {
 		it->base_node = n;
 	} else {
+		//! \todo we can probably use just zone_contents_find_node directly
 		it->base_node = zone_update_get_node(it->update, n->owner);
 		if (it->base_node == NULL) {
 			return KNOT_ENOMEM;
@@ -855,14 +665,11 @@ static int iter_init(zone_update_iter_t *it, zone_update_t *update, const bool n
 		return ret;
 	}
 
-	if (it->add_it != NULL) {
-		it->add_node = (zone_node_t *)(*hattrie_iter_val(it->add_it));
-		assert(it->add_node);
-	}
 	if (it->base_it != NULL) {
 		it->base_node = (zone_node_t *)(*hattrie_iter_val(it->base_it));
 		assert(it->base_node);
 		if (it->update->flags & UPDATE_INCREMENTAL) {
+			//! \todo we can probably use just zone_contents_find_node directly
 			it->base_node = zone_update_get_node(it->update, it->base_node->owner);
 			if (it->base_node == NULL) {
 				return KNOT_ENOMEM;
@@ -870,6 +677,7 @@ static int iter_init(zone_update_iter_t *it, zone_update_t *update, const bool n
 		}
 	}
 
+	//! get rid of this, use base_node directly, and better yet rename it to something more fitting
 	select_next_node(it);
 	return KNOT_EOK;
 }
@@ -919,13 +727,7 @@ int zone_update_iter_next(zone_update_iter_t *it)
 		}
 	}
 
-	if (it->add_it != NULL && it->add_node == NULL) {
-		int ret = iter_get_added_node(it);
-		if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
-			return ret;
-		}
-	}
-
+	//! get rid of this, use base_node directly, and better yet rename it to something more fitting
 	select_next_node(it);
 	return KNOT_EOK;
 }
diff --git a/src/knot/updates/zone-update.h b/src/knot/updates/zone-update.h
index d472641e79fad3cae5b9c21bc04ab3232c57bc96..d31330734e40f14d396cae07ce6de9be9230c48a 100644
--- a/src/knot/updates/zone-update.h
+++ b/src/knot/updates/zone-update.h
@@ -43,10 +43,8 @@ typedef struct zone_update {
 
 typedef struct {
 	zone_update_t *update;          /*!< The update we're iterating over. */
-	hattrie_iter_t *base_it;        /*!< Iterator for the original zone in the case of INCREMENTAL update or the new zone in case of FULL update. */
-	hattrie_iter_t *add_it;         /*!< Iterator for the added nodes in the changeset. Available in the INCREMENTAL update only. */
-	const zone_node_t *base_node;   /*!< The original node (INCREMENTAL update) or new node (FULL update). */
-	const zone_node_t *add_node;    /*!< The additions to that node (INCREMENTAL update only). */
+	hattrie_iter_t *base_it;        /*!< Iterator for the new zone. */
+	const zone_node_t *base_node;   /*!< The new node. */
 	const zone_node_t *next_node;   /*!< The smaller of t_node and ch_node (INCREMENTAL update) or next new node (FULL update). */
 	bool nsec3;                     /*!< Set when we're using the NSEC3 node tree. */
 } zone_update_iter_t;
diff --git a/src/knot/zone/zone-diff.c b/src/knot/zone/zone-diff.c
index ffe0820454eaa658178b00cf55126ea08fefdff7..98c84f55bb4e2db68ef91baa667a921af507fd0f 100644
--- a/src/knot/zone/zone-diff.c
+++ b/src/knot/zone/zone-diff.c
@@ -80,7 +80,7 @@ static int add_node(const zone_node_t *node, changeset_t *changeset)
 	/* Add all rrsets from node. */
 	for (unsigned i = 0; i < node->rrset_count; i++) {
 		knot_rrset_t rrset = node_rrset_at(node, i);
-		int ret = changeset_add_rrset(changeset, &rrset, 0);
+		int ret = changeset_add_addition(changeset, &rrset, 0);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
@@ -94,7 +94,7 @@ static int remove_node(const zone_node_t *node, changeset_t *changeset)
 	/* Remove all the RRSets of the node. */
 	for (unsigned i = 0; i < node->rrset_count; i++) {
 		knot_rrset_t rrset = node_rrset_at(node, i);
-		int ret = changeset_rem_rrset(changeset, &rrset, 0);
+		int ret = changeset_add_removal(changeset, &rrset, 0);
 		if (ret != KNOT_EOK) {
 			return ret;
 		}
@@ -175,7 +175,7 @@ static int diff_rrsets(const knot_rrset_t *rrset1, const knot_rrset_t *rrset2,
 	}
 
 	if (!knot_rrset_empty(&to_remove)) {
-		int ret = changeset_rem_rrset(changeset, &to_remove, 0);
+		int ret = changeset_add_removal(changeset, &to_remove, 0);
 		knot_rdataset_clear(&to_remove.rrs, NULL);
 		if (ret != KNOT_EOK) {
 			knot_rdataset_clear(&to_add.rrs, NULL);
@@ -184,7 +184,7 @@ static int diff_rrsets(const knot_rrset_t *rrset1, const knot_rrset_t *rrset2,
 	}
 
 	if (!knot_rrset_empty(&to_add)) {
-		int ret = changeset_add_rrset(changeset, &to_add, 0);
+		int ret = changeset_add_addition(changeset, &to_add, 0);
 		knot_rdataset_clear(&to_add.rrs, NULL);
 		return ret;
 	}
@@ -244,7 +244,7 @@ static int knot_zone_diff_node(zone_node_t **node_ptr, void *data)
 			node_rrset(node_in_second_tree, rrset.type);
 		if (knot_rrset_empty(&rrset_from_second_node)) {
 			/* RRSet has been removed. Make a copy and remove. */
-			int ret = changeset_rem_rrset(
+			int ret = changeset_add_removal(
 				param->changeset, &rrset, 0);
 			if (ret != KNOT_EOK) {
 				return ret;
@@ -271,7 +271,7 @@ static int knot_zone_diff_node(zone_node_t **node_ptr, void *data)
 		knot_rrset_t rrset_from_first_node = node_rrset(node, rrset.type);
 		if (knot_rrset_empty(&rrset_from_first_node)) {
 			/* RRSet has been added. Make a copy and add. */
-			int ret = changeset_add_rrset(
+			int ret = changeset_add_addition(
 				param->changeset, &rrset, 0);
 			if (ret != KNOT_EOK) {
 				return ret;
diff --git a/tests/changeset.c b/tests/changeset.c
index 7955957febb84c53631c8614b750b88e942a5e3d..4670e77c81d944eb372cf2728cf7d3daf50ee4a1 100644
--- a/tests/changeset.c
+++ b/tests/changeset.c
@@ -49,20 +49,20 @@ int main(int argc, char *argv[])
 	uint8_t data[8] = "\7teststr";
 	knot_rrset_add_rdata(apex_txt_rr, data, sizeof(data), 3600, NULL);
 
-	int ret = changeset_add_rrset(ch, apex_txt_rr, CHANGESET_CHECK);
+	int ret = changeset_add_addition(ch, apex_txt_rr, CHANGESET_CHECK);
 	ok(ret == KNOT_EOK, "changeset: add RRSet");
 	ok(changeset_size(ch) == 1, "changeset: size add");
-	ret = changeset_rem_rrset(ch, apex_txt_rr, CHANGESET_CHECK);
+	ret = changeset_add_removal(ch, apex_txt_rr, CHANGESET_CHECK);
 	ok(ret == KNOT_EOK, "changeset: rem RRSet");
 	ok(changeset_size(ch) == 1, "changeset: size remove");
 	ok(!changeset_empty(ch), "changeset: empty");
-	changeset_add_rrset(ch, apex_txt_rr, CHANGESET_CHECK);
+	changeset_add_addition(ch, apex_txt_rr, CHANGESET_CHECK);
 
 	// Add another RR to node.
 	knot_rrset_t *apex_spf_rr = knot_rrset_new(d, KNOT_RRTYPE_SPF, KNOT_CLASS_IN, NULL);
 	assert(apex_spf_rr);
 	knot_rrset_add_rdata(apex_spf_rr, data, sizeof(data), 3600, NULL);
-	ret = changeset_add_rrset(ch, apex_spf_rr, CHANGESET_CHECK);
+	ret = changeset_add_addition(ch, apex_spf_rr, CHANGESET_CHECK);
 	ok(ret == KNOT_EOK, "changeset: add multiple");
 
 	// Add another node.
@@ -72,7 +72,7 @@ int main(int argc, char *argv[])
 	knot_rrset_t *other_rr = knot_rrset_new(d, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, NULL);
 	assert(other_rr);
 	knot_rrset_add_rdata(other_rr, data, sizeof(data), 3600, NULL);
-	ret = changeset_add_rrset(ch, other_rr, CHANGESET_CHECK);
+	ret = changeset_add_addition(ch, other_rr, CHANGESET_CHECK);
 	ok(ret == KNOT_EOK, "changeset: remove multiple");
 
 	// Test add traversal.
@@ -93,8 +93,8 @@ int main(int argc, char *argv[])
 	changeset_iter_clear(&it);
 	ok(knot_rrset_empty(&iter), "changeset: traversal: skip non-terminals");
 
-	changeset_rem_rrset(ch, apex_txt_rr, CHANGESET_CHECK);
-	changeset_rem_rrset(ch, apex_txt_rr, CHANGESET_CHECK);
+	changeset_add_removal(ch, apex_txt_rr, CHANGESET_CHECK);
+	changeset_add_removal(ch, apex_txt_rr, CHANGESET_CHECK);
 
 	// Test remove traversal.
 	ret = changeset_iter_rem(&it, ch, false);
@@ -127,7 +127,7 @@ int main(int argc, char *argv[])
 	knot_dname_free(&apex_txt_rr->owner, NULL);
 	apex_txt_rr->owner = knot_dname_from_str_alloc("something.test.");
 	assert(apex_txt_rr->owner);
-	ret = changeset_add_rrset(ch2, apex_txt_rr, CHANGESET_CHECK);
+	ret = changeset_add_addition(ch2, apex_txt_rr, CHANGESET_CHECK);
 	assert(ret == KNOT_EOK);
 
 	// Add something to remove section.
@@ -135,7 +135,7 @@ int main(int argc, char *argv[])
 	apex_txt_rr->owner =
 		knot_dname_from_str_alloc("and.now.for.something.completely.different.test.");
 	assert(apex_txt_rr->owner);
-	ret = changeset_rem_rrset(ch2, apex_txt_rr, CHANGESET_CHECK);
+	ret = changeset_add_removal(ch2, apex_txt_rr, CHANGESET_CHECK);
 	assert(ret == KNOT_EOK);
 
 	// Test merge.
diff --git a/tests/journal.c b/tests/journal.c
index 2873a0c59a320d192950e00b6de9d6615ce144bf..a1e700e49eb504e20f199c72f8a4bd2b7b73c1e6 100644
--- a/tests/journal.c
+++ b/tests/journal.c
@@ -100,7 +100,7 @@ static void init_random_changeset(changeset_t *ch, const uint32_t from, const ui
 	for (size_t i = 0; i < size / 2; ++i) {
 		knot_rrset_t rr;
 		init_random_rr(&rr, apex);
-		int ret = changeset_add_rrset(ch, &rr, 0);
+		int ret = changeset_add_addition(ch, &rr, 0);
 		(void)ret;
 		assert(ret == KNOT_EOK);
 		knot_rrset_clear(&rr, NULL);
@@ -110,7 +110,7 @@ static void init_random_changeset(changeset_t *ch, const uint32_t from, const ui
 	for (size_t i = 0; i < size / 2; ++i) {
 		knot_rrset_t rr;
 		init_random_rr(&rr, apex);
-		int ret = changeset_rem_rrset(ch, &rr, 0);
+		int ret = changeset_add_removal(ch, &rr, 0);
 		(void)ret;
 		assert(ret == KNOT_EOK);
 		knot_rrset_clear(&rr, NULL);
diff --git a/tests/zone_update.c b/tests/zone_update.c
index ef3f2cb582a883db8a676d811f071460c28f88cd..e079319a97923369bf224e0b093aa2680523a823 100644
--- a/tests/zone_update.c
+++ b/tests/zone_update.c
@@ -190,10 +190,6 @@ void test_incremental(zone_t *zone, zs_scanner_t *sc)
 	ok(update.zone == zone && changeset_empty(&update.change) && update.mm.alloc,
 	   "incremental zone update: init");
 
-	ok(zone->contents->apex == zone_update_get_apex(&update) &&
-	   zone_update_no_change(&update),
-	   "incremental zone update: no change");
-
 	if (zs_set_input_string(sc, add_str, strlen(add_str)) != 0 ||
 	    zs_parse_all(sc) != 0) {
 		assert(0);
@@ -236,7 +232,7 @@ void test_incremental(zone_t *zone, zs_scanner_t *sc)
 	/* Node Removal */
 	ret = zone_update_remove_node(&update, rem_node_name);
 	synth_node = zone_update_get_node(&update, rem_node_name);
-	ok(ret == KNOT_EOK && synth_node && node_empty(synth_node),
+	ok(ret == KNOT_EOK && !synth_node,// && node_empty(synth_node),
 	   "incremental zone update: node removal");
 	knot_dname_free(&rem_node_name, NULL);