Repository: StackExchange/dnscontrol
Pull request #1851 (2022-12-11 19:36:19)

REFACTOR: Add diff2 enable flag to all DNS providers


Patch:
diff --git a/commands/commands.go b/commands/commands.go
index bdc9dea0..b01f043a 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 
 	"github.com/StackExchange/dnscontrol/v3/models"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/js"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/urfave/cli/v2"
@@ -57,6 +58,11 @@ func Run(v string) int {
 			Usage:       "Enable JS fetch(), dangerous on untrusted code!",
 			Destination: &js.EnableFetch,
 		},
+		&cli.BoolFlag{
+			Name:        "diff2",
+			Usage:       "Enable replacement diff algorithm",
+			Destination: &diff2.EnableDiff2,
+		},
 	}
 	sort.Sort(cli.CommandsByName(commands))
 	app.Commands = commands
diff --git a/pkg/diff2/flag.go b/pkg/diff2/flag.go
new file mode 100644
index 00000000..6576d927
--- /dev/null
+++ b/pkg/diff2/flag.go
@@ -0,0 +1,4 @@
+package diff2
+
+// EnableDiff2 is true to activate the experimental diff2 algorithm.
+var EnableDiff2 bool
diff --git a/providers/akamaiedgedns/akamaiEdgeDnsProvider.go b/providers/akamaiedgedns/akamaiEdgeDnsProvider.go
index 1e613943..c6a80ca3 100644
--- a/providers/akamaiedgedns/akamaiEdgeDnsProvider.go
+++ b/providers/akamaiedgedns/akamaiEdgeDnsProvider.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -121,101 +122,108 @@ func (a *edgeDNSProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
 	models.PostProcessRecords(existingRecords)
 	txtutil.SplitSingleLongTxt(dc.Records)
 
-	keysToUpdate, err := (diff.New(dc)).ChangedGroups(existingRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	existingRecordsMap := make(map[models.RecordKey][]*models.RecordConfig)
-	for _, r := range existingRecords {
-		key := models.RecordKey{NameFQDN: r.NameFQDN, Type: r.Type}
-		existingRecordsMap[key] = append(existingRecordsMap[key], r)
-	}
+		keysToUpdate, err := (diff.New(dc)).ChangedGroups(existingRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	desiredRecordsMap := dc.Records.GroupedByKey()
+		existingRecordsMap := make(map[models.RecordKey][]*models.RecordConfig)
+		for _, r := range existingRecords {
+			key := models.RecordKey{NameFQDN: r.NameFQDN, Type: r.Type}
+			existingRecordsMap[key] = append(existingRecordsMap[key], r)
+		}
 
-	// Deletes must occur first. For example, if replacing a existing CNAME with an A of the same name:
-	//    DELETE CNAME foo.example.net
-	// must occur before
-	//    CREATE A foo.example.net
-	// because both an A and a CNAME for the same name is not allowed.
+		desiredRecordsMap := dc.Records.GroupedByKey()
 
-	corrections := []*models.Correction{}     // deletes first
-	lastCorrections := []*models.Correction{} // creates and replaces last
+		// Deletes must occur first. For example, if replacing a existing CNAME with an A of the same name:
+		//    DELETE CNAME foo.example.net
+		// must occur before
+		//    CREATE A foo.example.net
+		// because both an A and a CNAME for the same name is not allowed.
 
-	for key, msg := range keysToUpdate {
-		existing, okExisting := existingRecordsMap[key]
-		desired, okDesired := desiredRecordsMap[key]
+		lastCorrections := []*models.Correction{} // creates and replaces last
 
-		if okExisting && !okDesired {
-			// In the existing map but not in the desired map: Delete
-			corrections = append(corrections, &models.Correction{
-				Msg: strings.Join(msg, "\n   "),
-				F: func() error {
-					return deleteRecordset(existing, dc.Name)
-				},
-			})
-			printer.Debugf("deleteRecordset: %s %s\n", key.NameFQDN, key.Type)
-			for _, rdata := range existing {
-				printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
+		for key, msg := range keysToUpdate {
+			existing, okExisting := existingRecordsMap[key]
+			desired, okDesired := desiredRecordsMap[key]
+
+			if okExisting && !okDesired {
+				// In the existing map but not in the desired map: Delete
+				corrections = append(corrections, &models.Correction{
+					Msg: strings.Join(msg, "\n   "),
+					F: func() error {
+						return deleteRecordset(existing, dc.Name)
+					},
+				})
+				printer.Debugf("deleteRecordset: %s %s\n", key.NameFQDN, key.Type)
+				for _, rdata := range existing {
+					printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
+				}
+			} else if !okExisting && okDesired {
+				// Not in the existing map but in the desired map: Create
+				lastCorrections = append(lastCorrections, &models.Correction{
+					Msg: strings.Join(msg, "\n   "),
+					F: func() error {
+						return createRecordset(desired, dc.Name)
+					},
+				})
+				printer.Debugf("createRecordset: %s %s\n", key.NameFQDN, key.Type)
+				for _, rdata := range desired {
+					printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
+				}
+			} else if okExisting && okDesired {
+				// In the existing map and in the desired map: Replace
+				lastCorrections = append(lastCorrections, &models.Correction{
+					Msg: strings.Join(msg, "\n   "),
+					F: func() error {
+						return replaceRecordset(desired, dc.Name)
+					},
+				})
+				printer.Debugf("replaceRecordset: %s %s\n", key.NameFQDN, key.Type)
+				for _, rdata := range desired {
+					printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
+				}
 			}
-		} else if !okExisting && okDesired {
-			// Not in the existing map but in the desired map: Create
-			lastCorrections = append(lastCorrections, &models.Correction{
-				Msg: strings.Join(msg, "\n   "),
+		}
+
+		// Deletes first, then creates and replaces
+		corrections = append(corrections, lastCorrections...)
+
+		// AutoDnsSec correction
+		existingAutoDNSSecEnabled, err := isAutoDNSSecEnabled(dc.Name)
+		if err != nil {
+			return nil, err
+		}
+
+		desiredAutoDNSSecEnabled := dc.AutoDNSSEC == "on"
+
+		if !existingAutoDNSSecEnabled && desiredAutoDNSSecEnabled {
+			// Existing false (disabled), Desired true (enabled)
+			corrections = append(corrections, &models.Correction{
+				Msg: "Enable AutoDnsSec\n",
 				F: func() error {
-					return createRecordset(desired, dc.Name)
+					return autoDNSSecEnable(true, dc.Name)
 				},
 			})
-			printer.Debugf("createRecordset: %s %s\n", key.NameFQDN, key.Type)
-			for _, rdata := range desired {
-				printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
-			}
-		} else if okExisting && okDesired {
-			// In the existing map and in the desired map: Replace
-			lastCorrections = append(lastCorrections, &models.Correction{
-				Msg: strings.Join(msg, "\n   "),
+			printer.Debugf("autoDNSSecEnable: Enable AutoDnsSec for zone %s\n", dc.Name)
+		} else if existingAutoDNSSecEnabled && !desiredAutoDNSSecEnabled {
+			// Existing true (enabled), Desired false (disabled)
+			corrections = append(corrections, &models.Correction{
+				Msg: "Disable AutoDnsSec\n",
 				F: func() error {
-					return replaceRecordset(desired, dc.Name)
+					return autoDNSSecEnable(false, dc.Name)
 				},
 			})
-			printer.Debugf("replaceRecordset: %s %s\n", key.NameFQDN, key.Type)
-			for _, rdata := range desired {
-				printer.Debugf("  Rdata: %s\n", rdata.GetTargetCombined())
-			}
+			printer.Debugf("autoDNSSecEnable: Disable AutoDnsSec for zone %s\n", dc.Name)
 		}
-	}
-
-	// Deletes first, then creates and replaces
-	corrections = append(corrections, lastCorrections...)
 
-	// AutoDnsSec correction
-	existingAutoDNSSecEnabled, err := isAutoDNSSecEnabled(dc.Name)
-	if err != nil {
-		return nil, err
+		return corrections, nil
 	}
 
-	desiredAutoDNSSecEnabled := dc.AutoDNSSEC == "on"
-
-	if !existingAutoDNSSecEnabled && desiredAutoDNSSecEnabled {
-		// Existing false (disabled), Desired true (enabled)
-		corrections = append(corrections, &models.Correction{
-			Msg: "Enable AutoDnsSec\n",
-			F: func() error {
-				return autoDNSSecEnable(true, dc.Name)
-			},
-		})
-		printer.Debugf("autoDNSSecEnable: Enable AutoDnsSec for zone %s\n", dc.Name)
-	} else if existingAutoDNSSecEnabled && !desiredAutoDNSSecEnabled {
-		// Existing true (enabled), Desired false (disabled)
-		corrections = append(corrections, &models.Correction{
-			Msg: "Disable AutoDnsSec\n",
-			F: func() error {
-				return autoDNSSecEnable(false, dc.Name)
-			},
-		})
-		printer.Debugf("autoDNSSecEnable: Disable AutoDnsSec for zone %s\n", dc.Name)
-	}
+	// Insert Future diff2 version here.
 
 	return corrections, nil
 }
diff --git a/providers/autodns/autoDnsProvider.go b/providers/autodns/autoDnsProvider.go
index e23a2b6a..d6f8a0b3 100644
--- a/providers/autodns/autoDnsProvider.go
+++ b/providers/autodns/autoDnsProvider.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -91,92 +92,98 @@ func (api *autoDNSProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo
 	models.PostProcessRecords(existingRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	unchanged, create, del, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
-	for _, m := range unchanged {
-		changes = append(changes, m.Desired)
-	}
-
-	for _, m := range del {
-		// Just notify, these records don't have to be deleted explicitly
-		printer.Debugf(m.String())
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, m := range create {
-		printer.Debugf(m.String())
-		changes = append(changes, m.Desired)
-	}
+		differ := diff.New(dc)
+		unchanged, create, del, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, m := range modify {
-		printer.Debugf("mod")
-		printer.Debugf(m.String())
-		changes = append(changes, m.Desired)
-	}
+		for _, m := range unchanged {
+			changes = append(changes, m.Desired)
+		}
 
-	var corrections []*models.Correction
+		for _, m := range del {
+			// Just notify, these records don't have to be deleted explicitly
+			printer.Debugf(m.String())
+		}
 
-	if len(create) > 0 || len(del) > 0 || len(modify) > 0 {
-		corrections = append(corrections,
-			&models.Correction{
-				Msg: "Zone update for " + domain,
-				F: func() error {
-					zoneTTL := uint32(0)
-					nameServers := []*models.Nameserver{}
-					resourceRecords := []*ResourceRecord{}
-
-					for _, record := range changes {
-						// NS records for the APEX should be handled differently
-						if record.Type == "NS" && record.Name == "@" {
-							nameServers = append(nameServers, &models.Nameserver{
-								Name: strings.TrimSuffix(record.GetTargetField(), "."),
-							})
-
-							zoneTTL = record.TTL
-						} else {
-							resourceRecord := &ResourceRecord{
-								Name:  record.Name,
-								TTL:   int64(record.TTL),
-								Type:  record.Type,
-								Value: record.GetTargetField(),
-							}
+		for _, m := range create {
+			printer.Debugf(m.String())
+			changes = append(changes, m.Desired)
+		}
 
-							if resourceRecord.Name == "@" {
-								resourceRecord.Name = ""
-							}
+		for _, m := range modify {
+			printer.Debugf("mod")
+			printer.Debugf(m.String())
+			changes = append(changes, m.Desired)
+		}
 
-							if record.Type == "MX" {
-								resourceRecord.Pref = int32(record.MxPreference)
+		if len(create) > 0 || len(del) > 0 || len(modify) > 0 {
+			corrections = append(corrections,
+				&models.Correction{
+					Msg: "Zone update for " + domain,
+					F: func() error {
+						zoneTTL := uint32(0)
+						nameServers := []*models.Nameserver{}
+						resourceRecords := []*ResourceRecord{}
+
+						for _, record := range changes {
+							// NS records for the APEX should be handled differently
+							if record.Type == "NS" && record.Name == "@" {
+								nameServers = append(nameServers, &models.Nameserver{
+									Name: strings.TrimSuffix(record.GetTargetField(), "."),
+								})
+
+								zoneTTL = record.TTL
+							} else {
+								resourceRecord := &ResourceRecord{
+									Name:  record.Name,
+									TTL:   int64(record.TTL),
+									Type:  record.Type,
+									Value: record.GetTargetField(),
+								}
+
+								if resourceRecord.Name == "@" {
+									resourceRecord.Name = ""
+								}
+
+								if record.Type == "MX" {
+									resourceRecord.Pref = int32(record.MxPreference)
+								}
+
+								if record.Type == "SRV" {
+									resourceRecord.Value = fmt.Sprintf(
+										"%d %d %d %s",
+										record.SrvPriority,
+										record.SrvWeight,
+										record.SrvPort,
+										record.GetTargetField(),
+									)
+								}
+
+								resourceRecords = append(resourceRecords, resourceRecord)
 							}
+						}
 
-							if record.Type == "SRV" {
-								resourceRecord.Value = fmt.Sprintf(
-									"%d %d %d %s",
-									record.SrvPriority,
-									record.SrvWeight,
-									record.SrvPort,
-									record.GetTargetField(),
-								)
-							}
+						err := api.updateZone(domain, resourceRecords, nameServers, zoneTTL)
 
-							resourceRecords = append(resourceRecords, resourceRecord)
+						if err != nil {
+							return fmt.Errorf(err.Error())
 						}
-					}
-
-					err := api.updateZone(domain, resourceRecords, nameServers, zoneTTL)
 
-					if err != nil {
-						return fmt.Errorf(err.Error())
-					}
+						return nil
+					},
+				})
+		}
 
-					return nil
-				},
-			})
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/axfrddns/axfrddnsProvider.go b/providers/axfrddns/axfrddnsProvider.go
index d8b70ff4..97943986 100644
--- a/providers/axfrddns/axfrddnsProvider.go
+++ b/providers/axfrddns/axfrddnsProvider.go
@@ -25,6 +25,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -346,100 +347,107 @@ func (c *axfrddnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
 	models.PostProcessRecords(foundRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	buf := &bytes.Buffer{}
-	// Print a list of changes. Generate an actual change that is the zone
-	changes := false
-	for _, i := range create {
-		changes = true
-		fmt.Fprintln(buf, i)
-	}
-	for _, i := range del {
-		changes = true
-		fmt.Fprintln(buf, i)
-	}
-	for _, i := range mod {
-		changes = true
-		fmt.Fprintln(buf, i)
-	}
-	msg := fmt.Sprintf("DDNS UPDATES to '%s' (primary master: '%s'). Changes:\n%s", dc.Name, c.master, buf)
-
-	corrections := []*models.Correction{}
-	if changes {
-
-		corrections = append(corrections,
-			&models.Correction{
-				Msg: msg,
-				F: func() error {
-
-					// An RFC2136-compliant server must silently ignore an
-					// update that inserts a non-CNAME RRset when a CNAME RR
-					// with the same name is present in the zone (and
-					// vice-versa). Therefore we prefer to first remove records
-					// and then insert new ones.
-					//
-					// Compliant servers must also silently ignore an update
-					// that removes the last NS record of a zone. Therefore we
-					// don't want to remove all NS records before inserting a
-					// new one. For the particular case of NS record, we prefer
-					// to insert new records before ot remove old ones.
-					//
-					// This remarks does not apply for "modified" NS records, as
-					// updates are processed one-by-one.
-					//
-					// This provider does not allow modifying the TTL of an NS
-					// record in a zone that defines only one NS. That would
-					// would require removing the single NS record, before
-					// adding the new one. But who does that anyway?
-
-					update := new(dns.Msg)
-					update.SetUpdate(dc.Name + ".")
-					update.Id = uint16(c.rand.Intn(math.MaxUint16))
-					for _, c := range create {
-						if c.Desired.Type == "NS" {
-							update.Insert([]dns.RR{c.Desired.ToRR()})
+		differ := diff.New(dc)
+		_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
+		if err != nil {
+			return nil, err
+		}
+
+		buf := &bytes.Buffer{}
+		// Print a list of changes. Generate an actual change that is the zone
+		changes := false
+		for _, i := range create {
+			changes = true
+			fmt.Fprintln(buf, i)
+		}
+		for _, i := range del {
+			changes = true
+			fmt.Fprintln(buf, i)
+		}
+		for _, i := range mod {
+			changes = true
+			fmt.Fprintln(buf, i)
+		}
+		msg := fmt.Sprintf("DDNS UPDATES to '%s' (primary master: '%s'). Changes:\n%s", dc.Name, c.master, buf)
+
+		if changes {
+
+			corrections = append(corrections,
+				&models.Correction{
+					Msg: msg,
+					F: func() error {
+
+						// An RFC2136-compliant server must silently ignore an
+						// update that inserts a non-CNAME RRset when a CNAME RR
+						// with the same name is present in the zone (and
+						// vice-versa). Therefore we prefer to first remove records
+						// and then insert new ones.
+						//
+						// Compliant servers must also silently ignore an update
+						// that removes the last NS record of a zone. Therefore we
+						// don't want to remove all NS records before inserting a
+						// new one. For the particular case of NS record, we prefer
+						// to insert new records before ot remove old ones.
+						//
+						// This remarks does not apply for "modified" NS records, as
+						// updates are processed one-by-one.
+						//
+						// This provider does not allow modifying the TTL of an NS
+						// record in a zone that defines only one NS. That would
+						// would require removing the single NS record, before
+						// adding the new one. But who does that anyway?
+
+						update := new(dns.Msg)
+						update.SetUpdate(dc.Name + ".")
+						update.Id = uint16(c.rand.Intn(math.MaxUint16))
+						for _, c := range create {
+							if c.Desired.Type == "NS" {
+								update.Insert([]dns.RR{c.Desired.ToRR()})
+							}
 						}
-					}
-					for _, c := range del {
-						update.Remove([]dns.RR{c.Existing.ToRR()})
-					}
-					for _, c := range mod {
-						update.Remove([]dns.RR{c.Existing.ToRR()})
-						update.Insert([]dns.RR{c.Desired.ToRR()})
-					}
-					for _, c := range create {
-						if c.Desired.Type != "NS" {
+						for _, c := range del {
+							update.Remove([]dns.RR{c.Existing.ToRR()})
+						}
+						for _, c := range mod {
+							update.Remove([]dns.RR{c.Existing.ToRR()})
 							update.Insert([]dns.RR{c.Desired.ToRR()})
 						}
-					}
-
-					client := new(dns.Client)
-					client.Net = c.updateMode
-					client.Timeout = dnsTimeout
-					if c.updateKey != nil {
-						client.TsigSecret =
-							map[string]string{c.updateKey.id: c.updateKey.secret}
-						update.SetTsig(c.updateKey.id, c.updateKey.algo, 300, time.Now().Unix())
-					}
-
-					msg, _, err := client.Exchange(update, c.master)
-					if err != nil {
-						return err
-					}
-					if msg.MsgHdr.Rcode != 0 {
-						return fmt.Errorf("[Error] AXFRDDNS: nameserver refused to update the zone: %s (%d)",
-							dns.RcodeToString[msg.MsgHdr.Rcode],
-							msg.MsgHdr.Rcode)
-					}
-
-					return nil
-				},
-			})
+						for _, c := range create {
+							if c.Desired.Type != "NS" {
+								update.Insert([]dns.RR{c.Desired.ToRR()})
+							}
+						}
+
+						client := new(dns.Client)
+						client.Net = c.updateMode
+						client.Timeout = dnsTimeout
+						if c.updateKey != nil {
+							client.TsigSecret =
+								map[string]string{c.updateKey.id: c.updateKey.secret}
+							update.SetTsig(c.updateKey.id, c.updateKey.algo, 300, time.Now().Unix())
+						}
+
+						msg, _, err := client.Exchange(update, c.master)
+						if err != nil {
+							return err
+						}
+						if msg.MsgHdr.Rcode != 0 {
+							return fmt.Errorf("[Error] AXFRDDNS: nameserver refused to update the zone: %s (%d)",
+								dns.RcodeToString[msg.MsgHdr.Rcode],
+								msg.MsgHdr.Rcode)
+						}
+
+						return nil
+					},
+				})
+		}
+		return corrections, nil
 	}
+
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
diff --git a/providers/azuredns/azureDnsProvider.go b/providers/azuredns/azureDnsProvider.go
index 7a7c5d03..9d44e8fd 100644
--- a/providers/azuredns/azureDnsProvider.go
+++ b/providers/azuredns/azureDnsProvider.go
@@ -13,6 +13,7 @@ import (
 	"github.com/Azure/go-autorest/autorest/to"
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -188,8 +189,6 @@ func (a *azurednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
 		return nil, err
 	}
 
-	var corrections []*models.Correction
-
 	existingRecords, records, zoneName, err := a.getExistingRecords(dc.Name)
 	if err != nil {
 		return nil, err
@@ -197,133 +196,141 @@ func (a *azurednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
 
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	namesToUpdate, err := differ.ChangedGroups(existingRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	if len(namesToUpdate) == 0 {
-		return nil, nil
-	}
+		differ := diff.New(dc)
+		namesToUpdate, err := differ.ChangedGroups(existingRecords)
+		if err != nil {
+			return nil, err
+		}
+
+		if len(namesToUpdate) == 0 {
+			return nil, nil
+		}
 
-	updates := map[models.RecordKey][]*models.RecordConfig{}
+		updates := map[models.RecordKey][]*models.RecordConfig{}
 
-	for k := range namesToUpdate {
-		updates[k] = nil
-		for _, rc := range dc.Records {
-			if rc.Key() == k {
-				updates[k] = append(updates[k], rc)
+		for k := range namesToUpdate {
+			updates[k] = nil
+			for _, rc := range dc.Records {
+				if rc.Key() == k {
+					updates[k] = append(updates[k], rc)
+				}
 			}
 		}
-	}
 
-	for k, recs := range updates {
-		if len(recs) == 0 {
-			var rrset *adns.RecordSet
-			for _, r := range records {
-				if strings.TrimSuffix(*r.Properties.Fqdn, ".") == k.NameFQDN {
-					n1, err := nativeToRecordType(r.Type)
+		for k, recs := range updates {
+			if len(recs) == 0 {
+				var rrset *adns.RecordSet
+				for _, r := range records {
+					if strings.TrimSuffix(*r.Properties.Fqdn, ".") == k.NameFQDN {
+						n1, err := nativeToRecordType(r.Type)
+						if err != nil {
+							return nil, err
+						}
+						n2, err := nativeToRecordType(to.StringPtr(k.Type))
+						if err != nil {
+							return nil, err
+						}
+						if n1 == n2 {
+							rrset = r
+							break
+						}
+					}
+				}
+				if rrset != nil {
+					corrections = append(corrections,
+						&models.Correction{
+							Msg: strings.Join(namesToUpdate[k], "\n"),
+							F: func() error {
+								ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
+								defer cancel()
+								rt, err := nativeToRecordType(rrset.Type)
+								if err != nil {
+									return err
+								}
+								_, err = a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, *rrset.Name, rt, nil)
+								if err != nil {
+									return err
+								}
+								return nil
+							},
+						})
+				} else {
+					return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type)
+				}
+			} else {
+				rrset, recordType, err := a.recordToNative(k, recs)
+				if err != nil {
+					return nil, err
+				}
+				var recordName string
+				for _, r := range recs {
+					i := int64(r.TTL)
+					rrset.Properties.TTL = &i // TODO: make sure that ttls are consistent within a set
+					recordName = r.Name
+				}
+
+				for _, r := range records {
+					existingRecordType, err := nativeToRecordType(r.Type)
 					if err != nil {
 						return nil, err
 					}
-					n2, err := nativeToRecordType(to.StringPtr(k.Type))
+					changedRecordType, err := nativeToRecordType(to.StringPtr(k.Type))
 					if err != nil {
 						return nil, err
 					}
-					if n1 == n2 {
-						rrset = r
-						break
+					if strings.TrimSuffix(*r.Properties.Fqdn, ".") == k.NameFQDN && (changedRecordType == adns.RecordTypeCNAME || existingRecordType == adns.RecordTypeCNAME) {
+						if existingRecordType == adns.RecordTypeA || existingRecordType == adns.RecordTypeAAAA || changedRecordType == adns.RecordTypeA || changedRecordType == adns.RecordTypeAAAA { //CNAME cannot coexist with an A or AA
+							corrections = append(corrections,
+								&models.Correction{
+									Msg: strings.Join(namesToUpdate[k], "\n"),
+									F: func() error {
+										ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
+										defer cancel()
+										_, err := a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, recordName, existingRecordType, nil)
+										if err != nil {
+											return err
+										}
+										return nil
+									},
+								})
+						}
 					}
 				}
-			}
-			if rrset != nil {
+
 				corrections = append(corrections,
 					&models.Correction{
 						Msg: strings.Join(namesToUpdate[k], "\n"),
 						F: func() error {
 							ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
 							defer cancel()
-							rt, err := nativeToRecordType(rrset.Type)
-							if err != nil {
-								return err
-							}
-							_, err = a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, *rrset.Name, rt, nil)
+							_, err := a.recordsClient.CreateOrUpdate(ctx, *a.resourceGroup, zoneName, recordName, recordType, *rrset, nil)
 							if err != nil {
 								return err
 							}
 							return nil
 						},
 					})
-			} else {
-				return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type)
-			}
-		} else {
-			rrset, recordType, err := a.recordToNative(k, recs)
-			if err != nil {
-				return nil, err
 			}
-			var recordName string
-			for _, r := range recs {
-				i := int64(r.TTL)
-				rrset.Properties.TTL = &i // TODO: make sure that ttls are consistent within a set
-				recordName = r.Name
-			}
-
-			for _, r := range records {
-				existingRecordType, err := nativeToRecordType(r.Type)
-				if err != nil {
-					return nil, err
-				}
-				changedRecordType, err := nativeToRecordType(to.StringPtr(k.Type))
-				if err != nil {
-					return nil, err
-				}
-				if strings.TrimSuffix(*r.Properties.Fqdn, ".") == k.NameFQDN && (changedRecordType == adns.RecordTypeCNAME || existingRecordType == adns.RecordTypeCNAME) {
-					if existingRecordType == adns.RecordTypeA || existingRecordType == adns.RecordTypeAAAA || changedRecordType == adns.RecordTypeA || changedRecordType == adns.RecordTypeAAAA { //CNAME cannot coexist with an A or AA
-						corrections = append(corrections,
-							&models.Correction{
-								Msg: strings.Join(namesToUpdate[k], "\n"),
-								F: func() error {
-									ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
-									defer cancel()
-									_, err := a.recordsClient.Delete(ctx, *a.resourceGroup, zoneName, recordName, existingRecordType, nil)
-									if err != nil {
-										return err
-									}
-									return nil
-								},
-							})
-					}
-				}
-			}
-
-			corrections = append(corrections,
-				&models.Correction{
-					Msg: strings.Join(namesToUpdate[k], "\n"),
-					F: func() error {
-						ctx, cancel := context.WithTimeout(context.Background(), 6000*time.Second)
-						defer cancel()
-						_, err := a.recordsClient.CreateOrUpdate(ctx, *a.resourceGroup, zoneName, recordName, recordType, *rrset, nil)
-						if err != nil {
-							return err
-						}
-						return nil
-					},
-				})
 		}
+
+		// Sort the records for cosmetic reasons: It just makes a long list
+		// of deletes or adds easier to read if they are in sorted order.
+		// That said, it may be risky to sort them (sort key is the text
+		// message "Msg") if there are deletes that must happen before adds.
+		// Reading the above code it isn't clear that any of the updates are
+		// order-dependent.  That said, all the tests pass.
+		// If in the future this causes a bug, we can either just remove
+		// this next line, or (even better) put any order-dependent
+		// operations in a single models.Correction{}.
+		sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) })
+
+		return corrections, nil
 	}
 
-	// Sort the records for cosmetic reasons: It just makes a long list
-	// of deletes or adds easier to read if they are in sorted order.
-	// That said, it may be risky to sort them (sort key is the text
-	// message "Msg") if there are deletes that must happen before adds.
-	// Reading the above code it isn't clear that any of the updates are
-	// order-dependent.  That said, all the tests pass.
-	// If in the future this causes a bug, we can either just remove
-	// this next line, or (even better) put any order-dependent
-	// operations in a single models.Correction{}.
-	sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) })
+	// Insert Future diff2 version here.
 
 	return corrections, nil
 }
diff --git a/providers/bind/bindProvider.go b/providers/bind/bindProvider.go
index 7a8d4112..096b0b28 100644
--- a/providers/bind/bindProvider.go
+++ b/providers/bind/bindProvider.go
@@ -24,6 +24,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/prettyzone"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
@@ -243,72 +244,79 @@ func (c *bindProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.
 	models.PostProcessRecords(foundRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	buf := &bytes.Buffer{}
-	// Print a list of changes. Generate an actual change that is the zone
-	changes := false
-	for _, i := range create {
-		changes = true
-		if c.zoneFileFound {
-			fmt.Fprintln(buf, i)
+		differ := diff.New(dc)
+		_, create, del, mod, err := differ.IncrementalDiff(foundRecords)
+		if err != nil {
+			return nil, err
 		}
-	}
-	for _, i := range del {
-		changes = true
-		if c.zoneFileFound {
-			fmt.Fprintln(buf, i)
+
+		buf := &bytes.Buffer{}
+		// Print a list of changes. Generate an actual change that is the zone
+		changes := false
+		for _, i := range create {
+			changes = true
+			if c.zoneFileFound {
+				fmt.Fprintln(buf, i)
+			}
 		}
-	}
-	for _, i := range mod {
-		changes = true
+		for _, i := range del {
+			changes = true
+			if c.zoneFileFound {
+				fmt.Fprintln(buf, i)
+			}
+		}
+		for _, i := range mod {
+			changes = true
+			if c.zoneFileFound {
+				fmt.Fprintln(buf, i)
+			}
+		}
+
+		var msg string
 		if c.zoneFileFound {
-			fmt.Fprintln(buf, i)
+			msg = fmt.Sprintf("GENERATE_ZONEFILE: '%s'. Changes:\n%s", dc.Name, buf)
+		} else {
+			msg = fmt.Sprintf("GENERATE_ZONEFILE: '%s' (new file with %d records)\n", dc.Name, len(create))
 		}
-	}
 
-	var msg string
-	if c.zoneFileFound {
-		msg = fmt.Sprintf("GENERATE_ZONEFILE: '%s'. Changes:\n%s", dc.Name, buf)
-	} else {
-		msg = fmt.Sprintf("GENERATE_ZONEFILE: '%s' (new file with %d records)\n", dc.Name, len(create))
-	}
+		if changes {
+
+			// We only change the serial number if there is a change.
+			desiredSoa.SoaSerial = nextSerial
+
+			corrections = append(corrections,
+				&models.Correction{
+					Msg: msg,
+					F: func() error {
+						printer.Printf("WRITING ZONEFILE: %v\n", c.zonefile)
+						zf, err := os.Create(c.zonefile)
+						if err != nil {
+							return fmt.Errorf("could not create zonefile: %w", err)
+						}
+						// Beware that if there are any fake types, then they will
+						// be commented out on write, but we don't reverse that when
+						// reading, so there will be a diff on every invocation.
+						err = prettyzone.WriteZoneFileRC(zf, dc.Records, dc.Name, 0, comments)
+
+						if err != nil {
+							return fmt.Errorf("failed WriteZoneFile: %w", err)
+						}
+						err = zf.Close()
+						if err != nil {
+							return fmt.Errorf("closing: %w", err)
+						}
+						return nil
+					},
+				})
+		}
 
-	corrections := []*models.Correction{}
-	if changes {
-
-		// We only change the serial number if there is a change.
-		desiredSoa.SoaSerial = nextSerial
-
-		corrections = append(corrections,
-			&models.Correction{
-				Msg: msg,
-				F: func() error {
-					printer.Printf("WRITING ZONEFILE: %v\n", c.zonefile)
-					zf, err := os.Create(c.zonefile)
-					if err != nil {
-						return fmt.Errorf("could not create zonefile: %w", err)
-					}
-					// Beware that if there are any fake types, then they will
-					// be commented out on write, but we don't reverse that when
-					// reading, so there will be a diff on every invocation.
-					err = prettyzone.WriteZoneFileRC(zf, dc.Records, dc.Name, 0, comments)
-
-					if err != nil {
-						return fmt.Errorf("failed WriteZoneFile: %w", err)
-					}
-					err = zf.Close()
-					if err != nil {
-						return fmt.Errorf("closing: %w", err)
-					}
-					return nil
-				},
-			})
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go
index b5c598ea..14194b8b 100644
--- a/providers/cloudflare/cloudflareProvider.go
+++ b/providers/cloudflare/cloudflareProvider.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/transform"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -222,100 +223,108 @@ func (c *cloudflareProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
 	// Therefore, whether the string is 1 octet or thousands, just store it as
 	// one string in the first element of .TxtStrings.
 
-	differ := diff.New(dc, getProxyMetadata)
-	_, create, del, mod, err := differ.IncrementalDiff(records)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	corrections := []*models.Correction{}
+		differ := diff.New(dc, getProxyMetadata)
+		_, create, del, mod, err := differ.IncrementalDiff(records)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, d := range del {
-		ex := d.Existing
-		if ex.Type == "PAGE_RULE" {
-			corrections = append(corrections, &models.Correction{
-				Msg: d.String(),
-				F:   func() error { return c.deletePageRule(ex.Original.(cloudflare.PageRule).ID, id) },
-			})
-		} else if ex.Type == "WORKER_ROUTE" {
-			corrections = append(corrections, &models.Correction{
-				Msg: d.String(),
-				F:   func() error { return c.deleteWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, id) },
-			})
-		} else {
-			corr := c.deleteRec(ex.Original.(cloudflare.DNSRecord), id)
-			// DS records must always have a corresponding NS record.
-			// Therefore, we remove DS records before any NS records.
-			if d.Existing.Type == "DS" {
-				corrections = append([]*models.Correction{corr}, corrections...)
+		corrections := []*models.Correction{}
+
+		for _, d := range del {
+			ex := d.Existing
+			if ex.Type == "PAGE_RULE" {
+				corrections = append(corrections, &models.Correction{
+					Msg: d.String(),
+					F:   func() error { return c.deletePageRule(ex.Original.(cloudflare.PageRule).ID, id) },
+				})
+			} else if ex.Type == "WORKER_ROUTE" {
+				corrections = append(corrections, &models.Correction{
+					Msg: d.String(),
+					F:   func() error { return c.deleteWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, id) },
+				})
 			} else {
-				corrections = append(corrections, corr)
+				corr := c.deleteRec(ex.Original.(cloudflare.DNSRecord), id)
+				// DS records must always have a corresponding NS record.
+				// Therefore, we remove DS records before any NS records.
+				if d.Existing.Type == "DS" {
+					corrections = append([]*models.Correction{corr}, corrections...)
+				} else {
+					corrections = append(corrections, corr)
+				}
 			}
 		}
-	}
-	for _, d := range create {
-		des := d.Desired
-		if des.Type == "PAGE_RULE" {
-			corrections = append(corrections, &models.Correction{
-				Msg: d.String(),
-				F:   func() error { return c.createPageRule(id, des.GetTargetField()) },
-			})
-		} else if des.Type == "WORKER_ROUTE" {
-			corrections = append(corrections, &models.Correction{
-				Msg: d.String(),
-				F:   func() error { return c.createWorkerRoute(id, des.GetTargetField()) },
-			})
-		} else {
-			corr := c.createRec(des, id)
-			// DS records must always have a corresponding NS record.
-			// Therefore, we create NS records before any DS records.
-			if d.Desired.Type == "NS" {
-				corrections = append(corr, corrections...)
+		for _, d := range create {
+			des := d.Desired
+			if des.Type == "PAGE_RULE" {
+				corrections = append(corrections, &models.Correction{
+					Msg: d.String(),
+					F:   func() error { return c.createPageRule(id, des.GetTargetField()) },
+				})
+			} else if des.Type == "WORKER_ROUTE" {
+				corrections = append(corrections, &models.Correction{
+					Msg: d.String(),
+					F:   func() error { return c.createWorkerRoute(id, des.GetTargetField()) },
+				})
 			} else {
-				corrections = append(corrections, corr...)
+				corr := c.createRec(des, id)
+				// DS records must always have a corresponding NS record.
+				// Therefore, we create NS records before any DS records.
+				if d.Desired.Type == "NS" {
+					corrections = append(corr, corrections...)
+				} else {
+					corrections = append(corrections, corr...)
+				}
 			}
 		}
-	}
 
-	for _, d := range mod {
-		rec := d.Desired
-		ex := d.Existing
-		if rec.Type == "PAGE_RULE" {
-			corrections = append(corrections, &models.Correction{
-				Msg: d.String(),
-				F:   func() error { return c.updatePageRule(ex.Original.(cloudflare.PageRule).ID, id, rec.GetTargetField()) },
-			})
-		} else if rec.Type == "WORKER_ROUTE" {
-			corrections = append(corrections, &models.Correction{
-				Msg: d.String(),
-				F: func() error {
-					return c.updateWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, id, rec.GetTargetField())
-				},
-			})
-		} else {
-			e := ex.Original.(cloudflare.DNSRecord)
-			proxy := e.Proxiable && rec.Metadata[metaProxy] != "off"
+		for _, d := range mod {
+			rec := d.Desired
+			ex := d.Existing
+			if rec.Type == "PAGE_RULE" {
+				corrections = append(corrections, &models.Correction{
+					Msg: d.String(),
+					F:   func() error { return c.updatePageRule(ex.Original.(cloudflare.PageRule).ID, id, rec.GetTargetField()) },
+				})
+			} else if rec.Type == "WORKER_ROUTE" {
+				corrections = append(corrections, &models.Correction{
+					Msg: d.String(),
+					F: func() error {
+						return c.updateWorkerRoute(ex.Original.(cloudflare.WorkerRoute).ID, id, rec.GetTargetField())
+					},
+				})
+			} else {
+				e := ex.Original.(cloudflare.DNSRecord)
+				proxy := e.Proxiable && rec.Metadata[metaProxy] != "off"
+				corrections = append(corrections, &models.Correction{
+					Msg: d.String(),
+					F:   func() error { return c.modifyRecord(id, e.ID, proxy, rec) },
+				})
+			}
+		}
+
+		// Add universalSSL change to corrections when needed
+		if changed, newState, err := c.checkUniversalSSL(dc, id); err == nil && changed {
+			var newStateString string
+			if newState {
+				newStateString = "enabled"
+			} else {
+				newStateString = "disabled"
+			}
 			corrections = append(corrections, &models.Correction{
-				Msg: d.String(),
-				F:   func() error { return c.modifyRecord(id, e.ID, proxy, rec) },
+				Msg: fmt.Sprintf("Universal SSL will be %s for this domain.", newStateString),
+				F:   func() error { return c.changeUniversalSSL(id, newState) },
 			})
 		}
-	}
 
-	// Add universalSSL change to corrections when needed
-	if changed, newState, err := c.checkUniversalSSL(dc, id); err == nil && changed {
-		var newStateString string
-		if newState {
-			newStateString = "enabled"
-		} else {
-			newStateString = "disabled"
-		}
-		corrections = append(corrections, &models.Correction{
-			Msg: fmt.Sprintf("Universal SSL will be %s for this domain.", newStateString),
-			F:   func() error { return c.changeUniversalSSL(id, newState) },
-		})
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/cloudns/cloudnsProvider.go b/providers/cloudns/cloudnsProvider.go
index 4e586469..bba381aa 100644
--- a/providers/cloudns/cloudnsProvider.go
+++ b/providers/cloudns/cloudnsProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"github.com/miekg/dns/dnsutil"
 )
@@ -103,85 +104,91 @@ func (c *cloudnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
 		record.TTL = fixTTL(record.TTL)
 	}
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// Deletes first so changing type works etc.
-	for _, m := range del {
-		id := m.Existing.Original.(*domainRecord).ID
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, ClouDNS ID: %s", m.String(), id),
-			F: func() error {
-				return c.deleteRecord(domainID, id)
-			},
-		}
-		// at ClouDNS, we MUST have a NS for a DS
-		// So, when deleting, we must delete the DS first, otherwise deleting the NS throws an error
-		if m.Existing.Type == "DS" {
-			// type DS is prepended - so executed first
-			corrections = append([]*models.Correction{corr}, corrections...)
-		} else {
-			corrections = append(corrections, corr)
-		}
-	}
-
-	var createCorrections []*models.Correction
-	for _, m := range create {
-		req, err := toReq(m.Desired)
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
 		if err != nil {
 			return nil, err
 		}
 
-		// ClouDNS does not require the trailing period to be specified when creating an NS record where the A or AAAA record exists in the zone.
-		// So, modify it to remove the trailing period.
-		if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") {
-			req["record"] = strings.TrimSuffix(req["record"], ".")
+		// Deletes first so changing type works etc.
+		for _, m := range del {
+			id := m.Existing.Original.(*domainRecord).ID
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, ClouDNS ID: %s", m.String(), id),
+				F: func() error {
+					return c.deleteRecord(domainID, id)
+				},
+			}
+			// at ClouDNS, we MUST have a NS for a DS
+			// So, when deleting, we must delete the DS first, otherwise deleting the NS throws an error
+			if m.Existing.Type == "DS" {
+				// type DS is prepended - so executed first
+				corrections = append([]*models.Correction{corr}, corrections...)
+			} else {
+				corrections = append(corrections, corr)
+			}
 		}
 
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				return c.createRecord(domainID, req)
-			},
-		}
-		// at ClouDNS, we MUST have a NS for a DS
-		// So, when creating, we must create the NS first, otherwise creating the DS throws an error
-		if m.Desired.Type == "NS" {
-			// type NS is prepended - so executed first
-			createCorrections = append([]*models.Correction{corr}, createCorrections...)
-		} else {
-			createCorrections = append(createCorrections, corr)
+		var createCorrections []*models.Correction
+		for _, m := range create {
+			req, err := toReq(m.Desired)
+			if err != nil {
+				return nil, err
+			}
+
+			// ClouDNS does not require the trailing period to be specified when creating an NS record where the A or AAAA record exists in the zone.
+			// So, modify it to remove the trailing period.
+			if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") {
+				req["record"] = strings.TrimSuffix(req["record"], ".")
+			}
+
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					return c.createRecord(domainID, req)
+				},
+			}
+			// at ClouDNS, we MUST have a NS for a DS
+			// So, when creating, we must create the NS first, otherwise creating the DS throws an error
+			if m.Desired.Type == "NS" {
+				// type NS is prepended - so executed first
+				createCorrections = append([]*models.Correction{corr}, createCorrections...)
+			} else {
+				createCorrections = append(createCorrections, corr)
+			}
 		}
-	}
-	corrections = append(corrections, createCorrections...)
-
-	for _, m := range modify {
-		id := m.Existing.Original.(*domainRecord).ID
-		req, err := toReq(m.Desired)
-		if err != nil {
-			return nil, err
-		}
-
-		// ClouDNS does not require the trailing period to be specified when updating an NS record where the A or AAAA record exists in the zone.
-		// So, modify it to remove the trailing period.
-		if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") {
-			req["record"] = strings.TrimSuffix(req["record"], ".")
+		corrections = append(corrections, createCorrections...)
+
+		for _, m := range modify {
+			id := m.Existing.Original.(*domainRecord).ID
+			req, err := toReq(m.Desired)
+			if err != nil {
+				return nil, err
+			}
+
+			// ClouDNS does not require the trailing period to be specified when updating an NS record where the A or AAAA record exists in the zone.
+			// So, modify it to remove the trailing period.
+			if req["record-type"] == "NS" && strings.HasSuffix(req["record"], domainID+".") {
+				req["record"] = strings.TrimSuffix(req["record"], ".")
+			}
+
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, ClouDNS ID: %s: ", m.String(), id),
+				F: func() error {
+					return c.modifyRecord(domainID, id, req)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
 
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, ClouDNS ID: %s: ", m.String(), id),
-			F: func() error {
-				return c.modifyRecord(domainID, id, req)
-			},
-		}
-		corrections = append(corrections, corr)
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/cscglobal/dns.go b/providers/cscglobal/dns.go
index 846b04a4..f7427752 100644
--- a/providers/cscglobal/dns.go
+++ b/providers/cscglobal/dns.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 )
 
 // GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
@@ -116,74 +117,82 @@ func (client *providerClient) GenerateDomainCorrections(dc *models.DomainConfig,
 	models.PostProcessRecords(foundRecords)
 	//txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// How to generate corrections?
-
-	// (1) Most providers take individual deletes, creates, and
-	// modifications:
-
-	// // Generate changes.
-	//	corrections := []*models.Correction{}
-	//	for _, del := range dels {
-	//		corrections = append(corrections, client.deleteRec(client.dnsserver, dc.Name, del))
-	//	}
-	//	for _, cre := range creates {
-	//		corrections = append(corrections, client.createRec(client.dnsserver, dc.Name, cre)...)
-	//	}
-	//	for _, m := range modifications {
-	//		corrections = append(corrections, client.modifyRec(client.dnsserver, dc.Name, m))
-	//	}
-	//	return corrections, nil
-
-	// (2) Some providers upload the entire zone every time.  Look at
-	// GetDomainCorrections for BIND and NAMECHEAP for inspiration.
-
-	// (3) Others do something entirely different. Like CSCGlobal:
-
-	// CSCGlobal has a unique API.  A list of edits is sent in one API
-	// call. Edits aren't permitted if an existing edit is being
-	// processed. Therefore, before we do an edit we block until the
-	// previous edit is done executing.
-
-	var edits []zoneResourceRecordEdit
-	var descriptions []string
-	for _, del := range dels {
-		edits = append(edits, makePurge(dc.Name, del))
-		descriptions = append(descriptions, del.String())
-	}
-	for _, cre := range creates {
-		edits = append(edits, makeAdd(dc.Name, cre))
-		descriptions = append(descriptions, cre.String())
-	}
-	for _, m := range modifications {
-		edits = append(edits, makeEdit(dc.Name, m))
-		descriptions = append(descriptions, m.String())
-	}
-	corrections := []*models.Correction{}
-	if len(edits) > 0 {
-		c := &models.Correction{
-			Msg: "\t" + strings.Join(descriptions, "\n\t"),
-			F: func() error {
-				// CSCGlobal's API only permits one pending update at a time.
-				// Therefore we block until any outstanding updates are done.
-				// We also clear out any failures, since (and I can't believe
-				// I'm writing this) any time something fails, the failure has
-				// to be cleared out with an additional API call.
-
-				err := client.clearRequests(dc.Name)
-				if err != nil {
-					return err
-				}
-				return client.sendZoneEditRequest(dc.Name, edits)
-			},
+		differ := diff.New(dc)
+		_, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords)
+		if err != nil {
+			return nil, err
 		}
-		corrections = append(corrections, c)
+
+		// How to generate corrections?
+
+		// (1) Most providers take individual deletes, creates, and
+		// modifications:
+
+		// // Generate changes.
+		//	corrections := []*models.Correction{}
+		//	for _, del := range dels {
+		//		corrections = append(corrections, client.deleteRec(client.dnsserver, dc.Name, del))
+		//	}
+		//	for _, cre := range creates {
+		//		corrections = append(corrections, client.createRec(client.dnsserver, dc.Name, cre)...)
+		//	}
+		//	for _, m := range modifications {
+		//		corrections = append(corrections, client.modifyRec(client.dnsserver, dc.Name, m))
+		//	}
+		//	return corrections, nil
+
+		// (2) Some providers upload the entire zone every time.  Look at
+		// GetDomainCorrections for BIND and NAMECHEAP for inspiration.
+
+		// (3) Others do something entirely different. Like CSCGlobal:
+
+		// CSCGlobal has a unique API.  A list of edits is sent in one API
+		// call. Edits aren't permitted if an existing edit is being
+		// processed. Therefore, before we do an edit we block until the
+		// previous edit is done executing.
+
+		var edits []zoneResourceRecordEdit
+		var descriptions []string
+		for _, del := range dels {
+			edits = append(edits, makePurge(dc.Name, del))
+			descriptions = append(descriptions, del.String())
+		}
+		for _, cre := range creates {
+			edits = append(edits, makeAdd(dc.Name, cre))
+			descriptions = append(descriptions, cre.String())
+		}
+		for _, m := range modifications {
+			edits = append(edits, makeEdit(dc.Name, m))
+			descriptions = append(descriptions, m.String())
+		}
+		corrections := []*models.Correction{}
+		if len(edits) > 0 {
+			c := &models.Correction{
+				Msg: "\t" + strings.Join(descriptions, "\n\t"),
+				F: func() error {
+					// CSCGlobal's API only permits one pending update at a time.
+					// Therefore we block until any outstanding updates are done.
+					// We also clear out any failures, since (and I can't believe
+					// I'm writing this) any time something fails, the failure has
+					// to be cleared out with an additional API call.
+
+					err := client.clearRequests(dc.Name)
+					if err != nil {
+						return err
+					}
+					return client.sendZoneEditRequest(dc.Name, edits)
+				},
+			}
+			corrections = append(corrections, c)
+		}
+		return corrections, nil
 	}
+
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/desec/desecProvider.go b/providers/desec/desecProvider.go
index 0c40e0d2..00008a53 100644
--- a/providers/desec/desecProvider.go
+++ b/providers/desec/desecProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -164,82 +165,88 @@ func PrepDesiredRecords(dc *models.DomainConfig, minTTL uint32) {
 // made.
 func (c *desecProvider) GenerateDomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) {
 
-	var corrections = []*models.Correction{}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// diff existing vs. current.
-	differ := diff.New(dc)
-	keysToUpdate, err := differ.ChangedGroups(existing)
-	if err != nil {
-		return nil, err
-	}
-	if len(keysToUpdate) == 0 {
-		return nil, nil
-	}
+		// diff existing vs. current.
+		differ := diff.New(dc)
+		keysToUpdate, err := differ.ChangedGroups(existing)
+		if err != nil {
+			return nil, err
+		}
+		if len(keysToUpdate) == 0 {
+			return nil, nil
+		}
 
-	desiredRecords := dc.Records.GroupedByKey()
-	var rrs []resourceRecord
-	buf := &bytes.Buffer{}
-	// For any key with an update, delete or replace those records.
-	for label := range keysToUpdate {
-		if _, ok := desiredRecords[label]; !ok {
-			//we could not find this RecordKey in the desiredRecords
-			//this means it must be deleted
-			for i, msg := range keysToUpdate[label] {
-				if i == 0 {
-					rc := resourceRecord{}
-					rc.Type = label.Type
-					rc.Records = make([]string, 0) // empty array of records should delete this rrset
-					rc.TTL = 3600
-					shortname := dnsutil.TrimDomainName(label.NameFQDN, dc.Name)
-					if shortname == "@" {
-						shortname = ""
+		desiredRecords := dc.Records.GroupedByKey()
+		var rrs []resourceRecord
+		buf := &bytes.Buffer{}
+		// For any key with an update, delete or replace those records.
+		for label := range keysToUpdate {
+			if _, ok := desiredRecords[label]; !ok {
+				//we could not find this RecordKey in the desiredRecords
+				//this means it must be deleted
+				for i, msg := range keysToUpdate[label] {
+					if i == 0 {
+						rc := resourceRecord{}
+						rc.Type = label.Type
+						rc.Records = make([]string, 0) // empty array of records should delete this rrset
+						rc.TTL = 3600
+						shortname := dnsutil.TrimDomainName(label.NameFQDN, dc.Name)
+						if shortname == "@" {
+							shortname = ""
+						}
+						rc.Subname = shortname
+						fmt.Fprintln(buf, msg)
+						rrs = append(rrs, rc)
+					} else {
+						//just add the message
+						fmt.Fprintln(buf, msg)
 					}
-					rc.Subname = shortname
-					fmt.Fprintln(buf, msg)
-					rrs = append(rrs, rc)
-				} else {
-					//just add the message
-					fmt.Fprintln(buf, msg)
 				}
-			}
-		} else {
-			//it must be an update or create, both can be done with the same api call.
-			ns := recordsToNative(desiredRecords[label], dc.Name)
-			if len(ns) > 1 {
-				panic("we got more than one resource record to create / modify")
-			}
-			for i, msg := range keysToUpdate[label] {
-				if i == 0 {
-					rrs = append(rrs, ns[0])
-					fmt.Fprintln(buf, msg)
-				} else {
-					//noop just for printing the additional messages
-					fmt.Fprintln(buf, msg)
+			} else {
+				//it must be an update or create, both can be done with the same api call.
+				ns := recordsToNative(desiredRecords[label], dc.Name)
+				if len(ns) > 1 {
+					panic("we got more than one resource record to create / modify")
+				}
+				for i, msg := range keysToUpdate[label] {
+					if i == 0 {
+						rrs = append(rrs, ns[0])
+						fmt.Fprintln(buf, msg)
+					} else {
+						//noop just for printing the additional messages
+						fmt.Fprintln(buf, msg)
+					}
 				}
 			}
 		}
+		msg := fmt.Sprintf("Changes:\n%s", buf)
+		corrections = append(corrections,
+			&models.Correction{
+				Msg: msg,
+				F: func() error {
+					rc := rrs
+					err := c.upsertRR(rc, dc.Name)
+					if err != nil {
+						return err
+					}
+					return nil
+				},
+			})
+
+		// NB(tlim): This sort is just to make updates look pretty. It is
+		// cosmetic.  The risk here is that there may be some updates that
+		// require a specific order (for example a delete before an add).
+		// However the code doesn't seem to have such situation.  All tests
+		// pass.  That said, if this breaks anything, the easiest fix might
+		// be to just remove the sort.
+		sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) })
+
+		return corrections, nil
 	}
-	msg := fmt.Sprintf("Changes:\n%s", buf)
-	corrections = append(corrections,
-		&models.Correction{
-			Msg: msg,
-			F: func() error {
-				rc := rrs
-				err := c.upsertRR(rc, dc.Name)
-				if err != nil {
-					return err
-				}
-				return nil
-			},
-		})
-
-	// NB(tlim): This sort is just to make updates look pretty. It is
-	// cosmetic.  The risk here is that there may be some updates that
-	// require a specific order (for example a delete before an add).
-	// However the code doesn't seem to have such situation.  All tests
-	// pass.  That said, if this breaks anything, the easiest fix might
-	// be to just remove the sort.
-	sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) })
+
+	// Insert Future diff2 version here.
 
 	return corrections, nil
 }
diff --git a/providers/digitalocean/digitaloceanProvider.go b/providers/digitalocean/digitaloceanProvider.go
index 7a8dc68e..4fcec4b8 100644
--- a/providers/digitalocean/digitaloceanProvider.go
+++ b/providers/digitalocean/digitaloceanProvider.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"github.com/digitalocean/godo"
@@ -145,68 +146,74 @@ func (api *digitaloceanProvider) GetDomainCorrections(dc *models.DomainConfig) (
 	models.PostProcessRecords(existingRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
+
+		differ := diff.New(dc)
+		_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	var corrections = []*models.Correction{}
-
-	// Deletes first so changing type works etc.
-	for _, m := range delete {
-		id := m.Existing.Original.(*godo.DomainRecord).ID
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, DO ID: %d", m.String(), id),
-			F: func() error {
-			retry:
-				resp, err := api.client.Domains.DeleteRecord(ctx, dc.Name, id)
-				if err != nil {
-					if pauseAndRetry(resp) {
-						goto retry
+		// Deletes first so changing type works etc.
+		for _, m := range delete {
+			id := m.Existing.Original.(*godo.DomainRecord).ID
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, DO ID: %d", m.String(), id),
+				F: func() error {
+				retry:
+					resp, err := api.client.Domains.DeleteRecord(ctx, dc.Name, id)
+					if err != nil {
+						if pauseAndRetry(resp) {
+							goto retry
+						}
 					}
-				}
-				return err
-			},
+					return err
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
-	}
-	for _, m := range create {
-		req := toReq(dc, m.Desired)
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-			retry:
-				_, resp, err := api.client.Domains.CreateRecord(ctx, dc.Name, req)
-				if err != nil {
-					if pauseAndRetry(resp) {
-						goto retry
+		for _, m := range create {
+			req := toReq(dc, m.Desired)
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+				retry:
+					_, resp, err := api.client.Domains.CreateRecord(ctx, dc.Name, req)
+					if err != nil {
+						if pauseAndRetry(resp) {
+							goto retry
+						}
 					}
-				}
-				return err
-			},
+					return err
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
-	}
-	for _, m := range modify {
-		id := m.Existing.Original.(*godo.DomainRecord).ID
-		req := toReq(dc, m.Desired)
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, DO ID: %d", m.String(), id),
-			F: func() error {
-			retry:
-				_, resp, err := api.client.Domains.EditRecord(ctx, dc.Name, id, req)
-				if err != nil {
-					if pauseAndRetry(resp) {
-						goto retry
+		for _, m := range modify {
+			id := m.Existing.Original.(*godo.DomainRecord).ID
+			req := toReq(dc, m.Desired)
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, DO ID: %d", m.String(), id),
+				F: func() error {
+				retry:
+					_, resp, err := api.client.Domains.EditRecord(ctx, dc.Name, id, req)
+					if err != nil {
+						if pauseAndRetry(resp) {
+							goto retry
+						}
 					}
-				}
-				return err
-			},
+					return err
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/dnsimple/dnsimpleProvider.go b/providers/dnsimple/dnsimpleProvider.go
index 138814d2..5f576e26 100644
--- a/providers/dnsimple/dnsimpleProvider.go
+++ b/providers/dnsimple/dnsimpleProvider.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	dnsimpleapi "github.com/dnsimple/dnsimple-go/dnsimple"
@@ -141,6 +142,7 @@ func (c *dnsimpleProvider) GetZoneRecords(domain string) (models.Records, error)
 // GetDomainCorrections returns corrections that update a domain.
 func (c *dnsimpleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
 	var corrections []*models.Correction
+
 	err := dc.Punycode()
 	if err != nil {
 		return nil, err
@@ -163,37 +165,44 @@ func (c *dnsimpleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
 	// Normalize
 	models.PostProcessRecords(actual)
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(actual)
-	if err != nil {
-		return nil, err
-	}
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, del := range del {
-		rec := del.Existing.Original.(dnsimpleapi.ZoneRecord)
-		corrections = append(corrections, &models.Correction{
-			Msg: del.String(),
-			F:   c.deleteRecordFunc(rec.ID, dc.Name),
-		})
-	}
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(actual)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, cre := range create {
-		rec := cre.Desired
-		corrections = append(corrections, &models.Correction{
-			Msg: cre.String(),
-			F:   c.createRecordFunc(rec, dc.Name),
-		})
-	}
+		for _, del := range del {
+			rec := del.Existing.Original.(dnsimpleapi.ZoneRecord)
+			corrections = append(corrections, &models.Correction{
+				Msg: del.String(),
+				F:   c.deleteRecordFunc(rec.ID, dc.Name),
+			})
+		}
+
+		for _, cre := range create {
+			rec := cre.Desired
+			corrections = append(corrections, &models.Correction{
+				Msg: cre.String(),
+				F:   c.createRecordFunc(rec, dc.Name),
+			})
+		}
 
-	for _, mod := range modify {
-		old := mod.Existing.Original.(dnsimpleapi.ZoneRecord)
-		rec := mod.Desired
-		corrections = append(corrections, &models.Correction{
-			Msg: mod.String(),
-			F:   c.updateRecordFunc(&old, rec, dc.Name),
-		})
+		for _, mod := range modify {
+			old := mod.Existing.Original.(dnsimpleapi.ZoneRecord)
+			rec := mod.Desired
+			corrections = append(corrections, &models.Correction{
+				Msg: mod.String(),
+				F:   c.updateRecordFunc(&old, rec, dc.Name),
+			})
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/dnsmadeeasy/dnsMadeEasyProvider.go b/providers/dnsmadeeasy/dnsMadeEasyProvider.go
index 1f1e2c85..8bfb504d 100644
--- a/providers/dnsmadeeasy/dnsMadeEasyProvider.go
+++ b/providers/dnsmadeeasy/dnsMadeEasyProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 )
@@ -100,73 +101,79 @@ func (api *dnsMadeEasyProvider) GetDomainCorrections(dc *models.DomainConfig) ([
 	models.PostProcessRecords(existingRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	var deleteRecordIds []int
-	deleteDescription := []string{"Batch deletion of records:"}
-	for _, m := range del {
-		originalRecordID := m.Existing.Original.(*recordResponseDataEntry).ID
-		deleteRecordIds = append(deleteRecordIds, originalRecordID)
-		deleteDescription = append(deleteDescription, m.String())
-	}
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	if len(deleteRecordIds) > 0 {
-		corr := &models.Correction{
-			Msg: strings.Join(deleteDescription, "\n\t"),
-			F: func() error {
-				return api.deleteRecords(domain.ID, deleteRecordIds)
-			},
+		var deleteRecordIds []int
+		deleteDescription := []string{"Batch deletion of records:"}
+		for _, m := range del {
+			originalRecordID := m.Existing.Original.(*recordResponseDataEntry).ID
+			deleteRecordIds = append(deleteRecordIds, originalRecordID)
+			deleteDescription = append(deleteDescription, m.String())
 		}
-		corrections = append(corrections, corr)
-	}
 
-	var createRecords []recordRequestData
-	createDescription := []string{"Batch creation of records:"}
-	for _, m := range create {
-		record := fromRecordConfig(m.Desired)
-		createRecords = append(createRecords, *record)
-		createDescription = append(createDescription, m.String())
-	}
+		if len(deleteRecordIds) > 0 {
+			corr := &models.Correction{
+				Msg: strings.Join(deleteDescription, "\n\t"),
+				F: func() error {
+					return api.deleteRecords(domain.ID, deleteRecordIds)
+				},
+			}
+			corrections = append(corrections, corr)
+		}
 
-	if len(createRecords) > 0 {
-		corr := &models.Correction{
-			Msg: strings.Join(createDescription, "\n\t"),
-			F: func() error {
-				return api.createRecords(domain.ID, createRecords)
-			},
+		var createRecords []recordRequestData
+		createDescription := []string{"Batch creation of records:"}
+		for _, m := range create {
+			record := fromRecordConfig(m.Desired)
+			createRecords = append(createRecords, *record)
+			createDescription = append(createDescription, m.String())
 		}
-		corrections = append(corrections, corr)
-	}
 
-	var modifyRecords []recordRequestData
-	modifyDescription := []string{"Batch modification of records:"}
-	for _, m := range modify {
-		originalRecord := m.Existing.Original.(*recordResponseDataEntry)
+		if len(createRecords) > 0 {
+			corr := &models.Correction{
+				Msg: strings.Join(createDescription, "\n\t"),
+				F: func() error {
+					return api.createRecords(domain.ID, createRecords)
+				},
+			}
+			corrections = append(corrections, corr)
+		}
 
-		record := fromRecordConfig(m.Desired)
-		record.ID = originalRecord.ID
-		record.GtdLocation = originalRecord.GtdLocation
+		var modifyRecords []recordRequestData
+		modifyDescription := []string{"Batch modification of records:"}
+		for _, m := range modify {
+			originalRecord := m.Existing.Original.(*recordResponseDataEntry)
 
-		modifyRecords = append(modifyRecords, *record)
-		modifyDescription = append(modifyDescription, m.String())
-	}
+			record := fromRecordConfig(m.Desired)
+			record.ID = originalRecord.ID
+			record.GtdLocation = originalRecord.GtdLocation
 
-	if len(modifyRecords) > 0 {
-		corr := &models.Correction{
-			Msg: strings.Join(modifyDescription, "\n\t"),
-			F: func() error {
-				return api.updateRecords(domain.ID, modifyRecords)
-			},
+			modifyRecords = append(modifyRecords, *record)
+			modifyDescription = append(modifyDescription, m.String())
 		}
-		corrections = append(corrections, corr)
+
+		if len(modifyRecords) > 0 {
+			corr := &models.Correction{
+				Msg: strings.Join(modifyDescription, "\n\t"),
+				F: func() error {
+					return api.updateRecords(domain.ID, modifyRecords)
+				},
+			}
+			corrections = append(corrections, corr)
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/domainnameshop/dns.go b/providers/domainnameshop/dns.go
index 7b699f30..43258226 100644
--- a/providers/domainnameshop/dns.go
+++ b/providers/domainnameshop/dns.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 )
 
 func (api *domainNameShopProvider) GetZoneRecords(domain string) (models.Records, error) {
@@ -46,62 +47,68 @@ func (api *domainNameShopProvider) GetDomainCorrections(dc *models.DomainConfig)
 		record.TTL = fixTTL(record.TTL)
 	}
 
-	differ := diff.New(dc)
-	_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	var corrections = []*models.Correction{}
+		differ := diff.New(dc)
+		_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	// Delete record
-	for _, r := range delete {
-		domainID := r.Existing.Original.(*domainNameShopRecord).DomainID
-		recordID := strconv.Itoa(r.Existing.Original.(*domainNameShopRecord).ID)
+		// Delete record
+		for _, r := range delete {
+			domainID := r.Existing.Original.(*domainNameShopRecord).DomainID
+			recordID := strconv.Itoa(r.Existing.Original.(*domainNameShopRecord).ID)
 
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, record id: %s", r.String(), recordID),
-			F:   func() error { return api.deleteRecord(domainID, recordID) },
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, record id: %s", r.String(), recordID),
+				F:   func() error { return api.deleteRecord(domainID, recordID) },
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
-	}
 
-	// Create records
-	for _, r := range create {
-		// Retrieve the domain name that is targeted. I.e. example.com instead of sub.example.com
-		domainName := strings.Replace(r.Desired.GetLabelFQDN(), r.Desired.GetLabel()+".", "", -1)
+		// Create records
+		for _, r := range create {
+			// Retrieve the domain name that is targeted. I.e. example.com instead of sub.example.com
+			domainName := strings.Replace(r.Desired.GetLabelFQDN(), r.Desired.GetLabel()+".", "", -1)
 
-		dnsR, err := api.fromRecordConfig(domainName, r.Desired)
-		if err != nil {
-			return nil, err
-		}
+			dnsR, err := api.fromRecordConfig(domainName, r.Desired)
+			if err != nil {
+				return nil, err
+			}
 
-		corr := &models.Correction{
-			Msg: r.String(),
-			F:   func() error { return api.CreateRecord(domainName, dnsR) },
+			corr := &models.Correction{
+				Msg: r.String(),
+				F:   func() error { return api.CreateRecord(domainName, dnsR) },
+			}
+
+			corrections = append(corrections, corr)
 		}
 
-		corrections = append(corrections, corr)
-	}
+		for _, r := range modify {
+			domainName := strings.Replace(r.Desired.GetLabelFQDN(), r.Desired.GetLabel()+".", "", -1)
 
-	for _, r := range modify {
-		domainName := strings.Replace(r.Desired.GetLabelFQDN(), r.Desired.GetLabel()+".", "", -1)
+			dnsR, err := api.fromRecordConfig(domainName, r.Desired)
+			if err != nil {
+				return nil, err
+			}
 
-		dnsR, err := api.fromRecordConfig(domainName, r.Desired)
-		if err != nil {
-			return nil, err
-		}
+			dnsR.ID = r.Existing.Original.(*domainNameShopRecord).ID
 
-		dnsR.ID = r.Existing.Original.(*domainNameShopRecord).ID
+			corr := &models.Correction{
+				Msg: r.String(),
+				F:   func() error { return api.UpdateRecord(dnsR) },
+			}
 
-		corr := &models.Correction{
-			Msg: r.String(),
-			F:   func() error { return api.UpdateRecord(dnsR) },
+			corrections = append(corrections, corr)
 		}
 
-		corrections = append(corrections, corr)
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/exoscale/exoscaleProvider.go b/providers/exoscale/exoscaleProvider.go
index a7cdfdba..4773fd70 100644
--- a/providers/exoscale/exoscaleProvider.go
+++ b/providers/exoscale/exoscaleProvider.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 )
@@ -189,39 +190,45 @@ func (c *exoscaleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
 	// Normalize
 	models.PostProcessRecords(existingRecords)
 
-	differ := diff.New(dc)
-	_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	var corrections = []*models.Correction{}
+		differ := diff.New(dc)
+		_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, del := range delete {
-		record := del.Existing.Original.(*egoscale.DNSDomainRecord)
-		corrections = append(corrections, &models.Correction{
-			Msg: del.String(),
-			F:   c.deleteRecordFunc(*record.ID, domainID),
-		})
-	}
+		for _, del := range delete {
+			record := del.Existing.Original.(*egoscale.DNSDomainRecord)
+			corrections = append(corrections, &models.Correction{
+				Msg: del.String(),
+				F:   c.deleteRecordFunc(*record.ID, domainID),
+			})
+		}
 
-	for _, cre := range create {
-		rc := cre.Desired
-		corrections = append(corrections, &models.Correction{
-			Msg: cre.String(),
-			F:   c.createRecordFunc(rc, domainID),
-		})
-	}
+		for _, cre := range create {
+			rc := cre.Desired
+			corrections = append(corrections, &models.Correction{
+				Msg: cre.String(),
+				F:   c.createRecordFunc(rc, domainID),
+			})
+		}
 
-	for _, mod := range modify {
-		old := mod.Existing.Original.(*egoscale.DNSDomainRecord)
-		new := mod.Desired
-		corrections = append(corrections, &models.Correction{
-			Msg: mod.String(),
-			F:   c.updateRecordFunc(old, new, domainID),
-		})
+		for _, mod := range modify {
+			old := mod.Existing.Original.(*egoscale.DNSDomainRecord)
+			new := mod.Desired
+			corrections = append(corrections, &models.Correction{
+				Msg: mod.String(),
+				F:   c.updateRecordFunc(old, new, domainID),
+			})
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/gandiv5/gandi_v5Provider.go b/providers/gandiv5/gandi_v5Provider.go
index b186fb67..fa9d43bb 100644
--- a/providers/gandiv5/gandi_v5Provider.go
+++ b/providers/gandiv5/gandi_v5Provider.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -226,113 +227,119 @@ func (client *gandiv5Provider) GenerateDomainCorrections(dc *models.DomainConfig
 		debugRecords("GenDC input", existing)
 	}
 
-	var corrections = []*models.Correction{}
-
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	// diff existing vs. current.
-	differ := diff.New(dc)
-	keysToUpdate, err := differ.ChangedGroups(existing)
-	if err != nil {
-		return nil, err
-	}
-	if client.debug {
-		diff.DebugKeyMapMap("GenDC diff", keysToUpdate)
-	}
-	if len(keysToUpdate) == 0 {
-		return nil, nil
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
+
+		// diff existing vs. current.
+		differ := diff.New(dc)
+		keysToUpdate, err := differ.ChangedGroups(existing)
+		if err != nil {
+			return nil, err
+		}
+		if client.debug {
+			diff.DebugKeyMapMap("GenDC diff", keysToUpdate)
+		}
+		if len(keysToUpdate) == 0 {
+			return nil, nil
+		}
 
-	// Regroup data by FQDN.  ChangedGroups returns data grouped by label:RType tuples.
-	affectedLabels, msgsForLabel := gatherAffectedLabels(keysToUpdate)
-	_, desiredRecords := dc.Records.GroupedByFQDN()
-	doesLabelExist := existing.FQDNMap()
+		// Regroup data by FQDN.  ChangedGroups returns data grouped by label:RType tuples.
+		affectedLabels, msgsForLabel := gatherAffectedLabels(keysToUpdate)
+		_, desiredRecords := dc.Records.GroupedByFQDN()
+		doesLabelExist := existing.FQDNMap()
 
-	g := gandi.NewLiveDNSClient(config.Config{
-		APIKey:    client.apikey,
-		SharingID: client.sharingid,
-		Debug:     client.debug,
-	})
+		g := gandi.NewLiveDNSClient(config.Config{
+			APIKey:    client.apikey,
+			SharingID: client.sharingid,
+			Debug:     client.debug,
+		})
 
-	// For any key with an update, delete or replace those records.
-	for label := range affectedLabels {
-		if len(desiredRecords[label]) == 0 {
-			// No records matching this key?  This can only mean that all
-			// the records were deleted. Delete them.
-
-			msgs := strings.Join(msgsForLabel[label], "\n")
-			domain := dc.Name
-			shortname := dnsutil.TrimDomainName(label, dc.Name)
-			corrections = append(corrections,
-				&models.Correction{
-					Msg: msgs,
-					F: func() error {
-						err := g.DeleteDomainRecordsByName(domain, shortname)
-						if err != nil {
-							return err
-						}
-						return nil
-					},
-				})
-
-		} else {
-			// Replace all the records at a label with our new records.
-
-			// Generate the new data in Gandi's format.
-			ns := recordsToNative(desiredRecords[label], dc.Name)
-
-			if doesLabelExist[label] {
-				// Records exist for this label. Replace them with what we have.
-
-				msg := strings.Join(msgsForLabel[label], "\n")
+		// For any key with an update, delete or replace those records.
+		for label := range affectedLabels {
+			if len(desiredRecords[label]) == 0 {
+				// No records matching this key?  This can only mean that all
+				// the records were deleted. Delete them.
+
+				msgs := strings.Join(msgsForLabel[label], "\n")
 				domain := dc.Name
 				shortname := dnsutil.TrimDomainName(label, dc.Name)
 				corrections = append(corrections,
 					&models.Correction{
-						Msg: msg,
+						Msg: msgs,
 						F: func() error {
-							res, err := g.UpdateDomainRecordsByName(domain, shortname, ns)
+							err := g.DeleteDomainRecordsByName(domain, shortname)
 							if err != nil {
-								return fmt.Errorf("%+v: %w", res, err)
+								return err
 							}
 							return nil
 						},
 					})
 
 			} else {
-				// First time putting data on this label. Create it.
+				// Replace all the records at a label with our new records.
+
+				// Generate the new data in Gandi's format.
+				ns := recordsToNative(desiredRecords[label], dc.Name)
+
+				if doesLabelExist[label] {
+					// Records exist for this label. Replace them with what we have.
 
-				// We have to create the label one rtype at a time.
-				for _, n := range ns {
 					msg := strings.Join(msgsForLabel[label], "\n")
 					domain := dc.Name
 					shortname := dnsutil.TrimDomainName(label, dc.Name)
-					rtype := n.RrsetType
-					ttl := n.RrsetTTL
-					values := n.RrsetValues
 					corrections = append(corrections,
 						&models.Correction{
 							Msg: msg,
 							F: func() error {
-								res, err := g.CreateDomainRecord(domain, shortname, rtype, ttl, values)
+								res, err := g.UpdateDomainRecordsByName(domain, shortname, ns)
 								if err != nil {
 									return fmt.Errorf("%+v: %w", res, err)
 								}
 								return nil
 							},
 						})
+
+				} else {
+					// First time putting data on this label. Create it.
+
+					// We have to create the label one rtype at a time.
+					for _, n := range ns {
+						msg := strings.Join(msgsForLabel[label], "\n")
+						domain := dc.Name
+						shortname := dnsutil.TrimDomainName(label, dc.Name)
+						rtype := n.RrsetType
+						ttl := n.RrsetTTL
+						values := n.RrsetValues
+						corrections = append(corrections,
+							&models.Correction{
+								Msg: msg,
+								F: func() error {
+									res, err := g.CreateDomainRecord(domain, shortname, rtype, ttl, values)
+									if err != nil {
+										return fmt.Errorf("%+v: %w", res, err)
+									}
+									return nil
+								},
+							})
+					}
 				}
 			}
 		}
+
+		// NB(tlim): This sort is just to make updates look pretty. It is
+		// cosmetic.  The risk here is that there may be some updates that
+		// require a specific order (for example a delete before an add).
+		// However the code doesn't seem to have such situation.  All tests
+		// pass.  That said, if this breaks anything, the easiest fix might
+		// be to just remove the sort.
+		sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) })
+
+		return corrections, nil
 	}
 
-	// NB(tlim): This sort is just to make updates look pretty. It is
-	// cosmetic.  The risk here is that there may be some updates that
-	// require a specific order (for example a delete before an add).
-	// However the code doesn't seem to have such situation.  All tests
-	// pass.  That said, if this breaks anything, the easiest fix might
-	// be to just remove the sort.
-	sort.Slice(corrections, func(i, j int) bool { return diff.CorrectionLess(corrections, i, j) })
+	// Insert Future diff2 version here.
 
 	return corrections, nil
 }
diff --git a/providers/gcloud/gcloudProvider.go b/providers/gcloud/gcloudProvider.go
index 4f8abef6..de46d1b2 100644
--- a/providers/gcloud/gcloudProvider.go
+++ b/providers/gcloud/gcloudProvider.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -204,77 +205,85 @@ func (g *gcloudProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*model
 	models.PostProcessRecords(existingRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	// first collect keys that have changed
-	differ := diff.New(dc)
-	_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, fmt.Errorf("incdiff error: %w", err)
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	changedKeys := map[key]bool{}
-	desc := ""
-	for _, c := range create {
-		desc += fmt.Sprintln(c)
-		changedKeys[keyForRec(c.Desired)] = true
-	}
-	for _, d := range delete {
-		desc += fmt.Sprintln(d)
-		changedKeys[keyForRec(d.Existing)] = true
-	}
-	for _, m := range modify {
-		desc += fmt.Sprintln(m)
-		changedKeys[keyForRec(m.Existing)] = true
-	}
-	if len(changedKeys) == 0 {
-		return nil, nil
-	}
-	chg := &gdns.Change{Kind: "dns#change"}
-	for ck := range changedKeys {
-		// remove old version (if present)
-		if old, ok := oldRRs[ck]; ok {
-			chg.Deletions = append(chg.Deletions, old)
+		// first collect keys that have changed
+		differ := diff.New(dc)
+		_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, fmt.Errorf("incdiff error: %w", err)
 		}
-		// collect records to replace with
-		newRRs := &gdns.ResourceRecordSet{
-			Name: ck.Name,
-			Type: ck.Type,
-			Kind: "dns#resourceRecordSet",
+
+		changedKeys := map[key]bool{}
+		desc := ""
+		for _, c := range create {
+			desc += fmt.Sprintln(c)
+			changedKeys[keyForRec(c.Desired)] = true
 		}
-		for _, r := range dc.Records {
-			if keyForRec(r) == ck {
-				newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombined())
-				newRRs.Ttl = int64(r.TTL)
-			}
+		for _, d := range delete {
+			desc += fmt.Sprintln(d)
+			changedKeys[keyForRec(d.Existing)] = true
 		}
-		if len(newRRs.Rrdatas) > 0 {
-			chg.Additions = append(chg.Additions, newRRs)
+		for _, m := range modify {
+			desc += fmt.Sprintln(m)
+			changedKeys[keyForRec(m.Existing)] = true
 		}
-	}
-
-	// FIXME(tlim): Google will return an error if too many changes are
-	// specified in a single request. We should split up very large
-	// batches.  This can be reliably reproduced with the 1201
-	// integration test.  The error you get is:
-	// googleapi: Error 403: The change would exceed quota for additions per change., quotaExceeded
-	//log.Printf("PAUSE STT = %+v %v\n", err, resp)
-	//log.Printf("PAUSE ERR = %+v %v\n", err, resp)
-
-	runChange := func() error {
-	retry:
-		resp, err := g.client.Changes.Create(g.project, zoneName, chg).Do()
-		if retryNeeded(resp, err) {
-			goto retry
+		if len(changedKeys) == 0 {
+			return nil, nil
 		}
-		if err != nil {
-			return fmt.Errorf("runChange error: %w", err)
+		chg := &gdns.Change{Kind: "dns#change"}
+		for ck := range changedKeys {
+			// remove old version (if present)
+			if old, ok := oldRRs[ck]; ok {
+				chg.Deletions = append(chg.Deletions, old)
+			}
+			// collect records to replace with
+			newRRs := &gdns.ResourceRecordSet{
+				Name: ck.Name,
+				Type: ck.Type,
+				Kind: "dns#resourceRecordSet",
+			}
+			for _, r := range dc.Records {
+				if keyForRec(r) == ck {
+					newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombined())
+					newRRs.Ttl = int64(r.TTL)
+				}
+			}
+			if len(newRRs.Rrdatas) > 0 {
+				chg.Additions = append(chg.Additions, newRRs)
+			}
 		}
-		return nil
+
+		// FIXME(tlim): Google will return an error if too many changes are
+		// specified in a single request. We should split up very large
+		// batches.  This can be reliably reproduced with the 1201
+		// integration test.  The error you get is:
+		// googleapi: Error 403: The change would exceed quota for additions per change., quotaExceeded
+		//log.Printf("PAUSE STT = %+v %v\n", err, resp)
+		//log.Printf("PAUSE ERR = %+v %v\n", err, resp)
+
+		runChange := func() error {
+		retry:
+			resp, err := g.client.Changes.Create(g.project, zoneName, chg).Do()
+			if retryNeeded(resp, err) {
+				goto retry
+			}
+			if err != nil {
+				return fmt.Errorf("runChange error: %w", err)
+			}
+			return nil
+		}
+
+		return []*models.Correction{{
+			Msg: desc,
+			F:   runChange,
+		}}, nil
 	}
 
-	return []*models.Correction{{
-		Msg: desc,
-		F:   runChange,
-	}}, nil
+	// Insert Future diff2 version here.
+
+	return corrections, nil
 }
 
 func nativeToRecord(set *gdns.ResourceRecordSet, rec, origin string) (*models.RecordConfig, error) {
diff --git a/providers/gcore/gcoreProvider.go b/providers/gcore/gcoreProvider.go
index 635fa713..546c043d 100644
--- a/providers/gcore/gcoreProvider.go
+++ b/providers/gcore/gcoreProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 
 	dnssdk "github.com/G-Core/gcore-dns-sdk-go"
@@ -151,86 +152,92 @@ func generateChangeMsg(updates []string) string {
 // made.
 func (c *gcoreProvider) GenerateDomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) {
 
-	var corrections = []*models.Correction{}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// diff existing vs. current.
-	differ := diff.New(dc)
-	keysToUpdate, err := differ.ChangedGroups(existing)
-	if err != nil {
-		return nil, err
-	}
-	if len(keysToUpdate) == 0 {
-		return nil, nil
-	}
-
-	desiredRecords := dc.Records.GroupedByKey()
-	existingRecords := existing.GroupedByKey()
-
-	// First pass: delete records to avoid coexisting of conflicting types
-	for label := range keysToUpdate {
-		if _, ok := desiredRecords[label]; !ok {
-			// record deleted in update
-			// Copy all params to avoid overwrites
-			zone := dc.Name
-			name := label.NameFQDN
-			typ := label.Type
-			msg := generateChangeMsg(keysToUpdate[label])
-			corrections = append(corrections, &models.Correction{
-				Msg: msg,
-				F: func() error {
-					return c.provider.DeleteRRSet(c.ctx, zone, name, typ)
-				},
-			})
+		// diff existing vs. current.
+		differ := diff.New(dc)
+		keysToUpdate, err := differ.ChangedGroups(existing)
+		if err != nil {
+			return nil, err
+		}
+		if len(keysToUpdate) == 0 {
+			return nil, nil
 		}
-	}
 
-	// Second pass: create and update records
-	for label := range keysToUpdate {
-		if _, ok := desiredRecords[label]; !ok {
-			// record deleted in update
-			// do nothing here
-
-		} else if _, ok := existingRecords[label]; !ok {
-			// record created in update
-			record := recordsToNative(desiredRecords[label], label)
-			if record == nil {
-				panic("No records matching label")
+		desiredRecords := dc.Records.GroupedByKey()
+		existingRecords := existing.GroupedByKey()
+
+		// First pass: delete records to avoid coexisting of conflicting types
+		for label := range keysToUpdate {
+			if _, ok := desiredRecords[label]; !ok {
+				// record deleted in update
+				// Copy all params to avoid overwrites
+				zone := dc.Name
+				name := label.NameFQDN
+				typ := label.Type
+				msg := generateChangeMsg(keysToUpdate[label])
+				corrections = append(corrections, &models.Correction{
+					Msg: msg,
+					F: func() error {
+						return c.provider.DeleteRRSet(c.ctx, zone, name, typ)
+					},
+				})
 			}
+		}
 
-			// Copy all params to avoid overwrites
-			zone := dc.Name
-			name := label.NameFQDN
-			typ := label.Type
-			rec := *record
-			msg := generateChangeMsg(keysToUpdate[label])
-			corrections = append(corrections, &models.Correction{
-				Msg: msg,
-				F: func() error {
-					return c.provider.CreateRRSet(c.ctx, zone, name, typ, rec)
-				},
-			})
-
-		} else {
-			// record modified in update
-			record := recordsToNative(desiredRecords[label], label)
-			if record == nil {
-				panic("No records matching label")
+		// Second pass: create and update records
+		for label := range keysToUpdate {
+			if _, ok := desiredRecords[label]; !ok {
+				// record deleted in update
+				// do nothing here
+
+			} else if _, ok := existingRecords[label]; !ok {
+				// record created in update
+				record := recordsToNative(desiredRecords[label], label)
+				if record == nil {
+					panic("No records matching label")
+				}
+
+				// Copy all params to avoid overwrites
+				zone := dc.Name
+				name := label.NameFQDN
+				typ := label.Type
+				rec := *record
+				msg := generateChangeMsg(keysToUpdate[label])
+				corrections = append(corrections, &models.Correction{
+					Msg: msg,
+					F: func() error {
+						return c.provider.CreateRRSet(c.ctx, zone, name, typ, rec)
+					},
+				})
+
+			} else {
+				// record modified in update
+				record := recordsToNative(desiredRecords[label], label)
+				if record == nil {
+					panic("No records matching label")
+				}
+
+				// Copy all params to avoid overwrites
+				zone := dc.Name
+				name := label.NameFQDN
+				typ := label.Type
+				rec := *record
+				msg := generateChangeMsg(keysToUpdate[label])
+				corrections = append(corrections, &models.Correction{
+					Msg: msg,
+					F: func() error {
+						return c.provider.UpdateRRSet(c.ctx, zone, name, typ, rec)
+					},
+				})
 			}
-
-			// Copy all params to avoid overwrites
-			zone := dc.Name
-			name := label.NameFQDN
-			typ := label.Type
-			rec := *record
-			msg := generateChangeMsg(keysToUpdate[label])
-			corrections = append(corrections, &models.Correction{
-				Msg: msg,
-				F: func() error {
-					return c.provider.UpdateRRSet(c.ctx, zone, name, typ, rec)
-				},
-			})
 		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
diff --git a/providers/hedns/hednsProvider.go b/providers/hedns/hednsProvider.go
index 37bbc426..75afbca9 100644
--- a/providers/hedns/hednsProvider.go
+++ b/providers/hedns/hednsProvider.go
@@ -17,6 +17,7 @@ import (
 	"github.com/PuerkitoBio/goquery"
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"github.com/pquerna/otp/totp"
@@ -177,7 +178,6 @@ func (c *hednsProvider) GetNameservers(_ string) ([]*models.Nameserver, error) {
 
 // GetDomainCorrections returns a list of corrections for the  domain.
 func (c *hednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
-	var corrections []*models.Correction
 
 	err := dc.Punycode()
 	if err != nil {
@@ -204,48 +204,56 @@ func (c *hednsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models
 	models.PostProcessRecords(prunedRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, toCreate, toDelete, toModify, err := differ.IncrementalDiff(prunedRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, del := range toDelete {
-		record := del.Existing
-		corrections = append(corrections, &models.Correction{
-			Msg: del.String(),
-			F:   func() error { return c.deleteZoneRecord(record) },
-		})
-	}
+		differ := diff.New(dc)
+		_, toCreate, toDelete, toModify, err := differ.IncrementalDiff(prunedRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, cre := range toCreate {
-		record := cre.Desired
-		record.Original = Record{
-			ZoneName:   dc.Name,
-			ZoneID:     zoneID,
-			RecordName: cre.Desired.Name,
+		for _, del := range toDelete {
+			record := del.Existing
+			corrections = append(corrections, &models.Correction{
+				Msg: del.String(),
+				F:   func() error { return c.deleteZoneRecord(record) },
+			})
 		}
-		corrections = append(corrections, &models.Correction{
-			Msg: cre.String(),
-			F:   func() error { return c.editZoneRecord(record, true) },
-		})
-	}
-
-	for _, mod := range toModify {
-		record := mod.Desired
-		record.Original = Record{
-			ZoneName:   dc.Name,
-			ZoneID:     zoneID,
-			RecordID:   mod.Existing.Original.(Record).RecordID,
-			RecordName: mod.Desired.Name,
+
+		for _, cre := range toCreate {
+			record := cre.Desired
+			record.Original = Record{
+				ZoneName:   dc.Name,
+				ZoneID:     zoneID,
+				RecordName: cre.Desired.Name,
+			}
+			corrections = append(corrections, &models.Correction{
+				Msg: cre.String(),
+				F:   func() error { return c.editZoneRecord(record, true) },
+			})
 		}
-		corrections = append(corrections, &models.Correction{
-			Msg: mod.String(),
-			F:   func() error { return c.editZoneRecord(record, false) },
-		})
+
+		for _, mod := range toModify {
+			record := mod.Desired
+			record.Original = Record{
+				ZoneName:   dc.Name,
+				ZoneID:     zoneID,
+				RecordID:   mod.Existing.Original.(Record).RecordID,
+				RecordName: mod.Desired.Name,
+			}
+			corrections = append(corrections, &models.Correction{
+				Msg: mod.String(),
+				F:   func() error { return c.editZoneRecord(record, false) },
+			})
+		}
+
+		return corrections, err
 	}
 
-	return corrections, err
+	// Insert Future diff2 version here.
+
+	return corrections, nil
 }
 
 // GetZoneRecords returns all the records for the given domain
diff --git a/providers/hetzner/hetznerProvider.go b/providers/hetzner/hetznerProvider.go
index 3526f81b..f244a64e 100644
--- a/providers/hetzner/hetznerProvider.go
+++ b/providers/hetzner/hetznerProvider.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 )
@@ -101,66 +102,72 @@ func (api *hetznerProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo
 	models.PostProcessRecords(existingRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	zone, err := api.getZone(domain)
-	if err != nil {
-		return nil, err
-	}
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, m := range del {
-		record := m.Existing.Original.(*record)
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				return api.deleteRecord(*record)
-			},
+		zone, err := api.getZone(domain)
+		if err != nil {
+			return nil, err
 		}
-		corrections = append(corrections, corr)
-	}
-
-	var createRecords []record
-	createDescription := []string{"Batch creation of records:"}
-	for _, m := range create {
-		record := fromRecordConfig(m.Desired, zone)
-		createRecords = append(createRecords, *record)
-		createDescription = append(createDescription, m.String())
-	}
-	if len(createRecords) > 0 {
-		corr := &models.Correction{
-			Msg: strings.Join(createDescription, "\n\t"),
-			F: func() error {
-				return api.bulkCreateRecords(createRecords)
-			},
+
+		for _, m := range del {
+			record := m.Existing.Original.(*record)
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					return api.deleteRecord(*record)
+				},
+			}
+			corrections = append(corrections, corr)
+		}
+
+		var createRecords []record
+		createDescription := []string{"Batch creation of records:"}
+		for _, m := range create {
+			record := fromRecordConfig(m.Desired, zone)
+			createRecords = append(createRecords, *record)
+			createDescription = append(createDescription, m.String())
+		}
+		if len(createRecords) > 0 {
+			corr := &models.Correction{
+				Msg: strings.Join(createDescription, "\n\t"),
+				F: func() error {
+					return api.bulkCreateRecords(createRecords)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
-	}
-
-	var modifyRecords []record
-	modifyDescription := []string{"Batch modification of records:"}
-	for _, m := range modify {
-		id := m.Existing.Original.(*record).ID
-		record := fromRecordConfig(m.Desired, zone)
-		record.ID = id
-		modifyRecords = append(modifyRecords, *record)
-		modifyDescription = append(modifyDescription, m.String())
-	}
-	if len(modifyRecords) > 0 {
-		corr := &models.Correction{
-			Msg: strings.Join(modifyDescription, "\n\t"),
-			F: func() error {
-				return api.bulkUpdateRecords(modifyRecords)
-			},
+
+		var modifyRecords []record
+		modifyDescription := []string{"Batch modification of records:"}
+		for _, m := range modify {
+			id := m.Existing.Original.(*record).ID
+			record := fromRecordConfig(m.Desired, zone)
+			record.ID = id
+			modifyRecords = append(modifyRecords, *record)
+			modifyDescription = append(modifyDescription, m.String())
+		}
+		if len(modifyRecords) > 0 {
+			corr := &models.Correction{
+				Msg: strings.Join(modifyDescription, "\n\t"),
+				F: func() error {
+					return api.bulkUpdateRecords(modifyRecords)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/hexonet/records.go b/providers/hexonet/records.go
index 59150670..ce1d3a56 100644
--- a/providers/hexonet/records.go
+++ b/providers/hexonet/records.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 )
@@ -73,62 +74,70 @@ func (n *HXClient) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Corr
 	models.PostProcessRecords(actual)
 	txtutil.SplitSingleLongTxt(dc.Records)
 
-	differ := diff.New(dc)
-	_, create, del, mod, err := differ.IncrementalDiff(actual)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	corrections := []*models.Correction{}
-
-	buf := &bytes.Buffer{}
-	// Print a list of changes. Generate an actual change that is the zone
-	changes := false
-	params := map[string]interface{}{}
-	delrridx := 0
-	addrridx := 0
-	for _, cre := range create {
-		changes = true
-		fmt.Fprintln(buf, cre)
-		rec := cre.Desired
-		recordString, err := n.createRecordString(rec, dc.Name)
+		differ := diff.New(dc)
+		_, create, del, mod, err := differ.IncrementalDiff(actual)
 		if err != nil {
-			return corrections, err
+			return nil, err
 		}
-		params[fmt.Sprintf("ADDRR%d", addrridx)] = recordString
-		addrridx++
-	}
-	for _, d := range del {
-		changes = true
-		fmt.Fprintln(buf, d)
-		rec := d.Existing.Original.(*HXRecord)
-		params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(rec, dc.Name)
-		delrridx++
-	}
-	for _, chng := range mod {
-		changes = true
-		fmt.Fprintln(buf, chng)
-		old := chng.Existing.Original.(*HXRecord)
-		new := chng.Desired
-		params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(old, dc.Name)
-		newRecordString, err := n.createRecordString(new, dc.Name)
-		if err != nil {
-			return corrections, err
+
+		corrections := []*models.Correction{}
+
+		buf := &bytes.Buffer{}
+		// Print a list of changes. Generate an actual change that is the zone
+		changes := false
+		params := map[string]interface{}{}
+		delrridx := 0
+		addrridx := 0
+		for _, cre := range create {
+			changes = true
+			fmt.Fprintln(buf, cre)
+			rec := cre.Desired
+			recordString, err := n.createRecordString(rec, dc.Name)
+			if err != nil {
+				return corrections, err
+			}
+			params[fmt.Sprintf("ADDRR%d", addrridx)] = recordString
+			addrridx++
 		}
-		params[fmt.Sprintf("ADDRR%d", addrridx)] = newRecordString
-		addrridx++
-		delrridx++
-	}
-	msg := fmt.Sprintf("GENERATE_ZONEFILE: %s\n", dc.Name) + buf.String()
+		for _, d := range del {
+			changes = true
+			fmt.Fprintln(buf, d)
+			rec := d.Existing.Original.(*HXRecord)
+			params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(rec, dc.Name)
+			delrridx++
+		}
+		for _, chng := range mod {
+			changes = true
+			fmt.Fprintln(buf, chng)
+			old := chng.Existing.Original.(*HXRecord)
+			new := chng.Desired
+			params[fmt.Sprintf("DELRR%d", delrridx)] = n.deleteRecordString(old, dc.Name)
+			newRecordString, err := n.createRecordString(new, dc.Name)
+			if err != nil {
+				return corrections, err
+			}
+			params[fmt.Sprintf("ADDRR%d", addrridx)] = newRecordString
+			addrridx++
+			delrridx++
+		}
+		msg := fmt.Sprintf("GENERATE_ZONEFILE: %s\n", dc.Name) + buf.String()
 
-	if changes {
-		corrections = append(corrections, &models.Correction{
-			Msg: msg,
-			F: func() error {
-				return n.updateZoneBy(params, dc.Name)
-			},
-		})
+		if changes {
+			corrections = append(corrections, &models.Correction{
+				Msg: msg,
+				F: func() error {
+					return n.updateZoneBy(params, dc.Name)
+				},
+			})
+		}
+		return corrections, nil
 	}
+
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/hostingde/hostingdeProvider.go b/providers/hostingde/hostingdeProvider.go
index 49bc6234..8caca490 100644
--- a/providers/hostingde/hostingdeProvider.go
+++ b/providers/hostingde/hostingdeProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 )
 
@@ -125,35 +126,43 @@ func (hp *hostingdeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
 		return nil, err
 	}
 
-	differ := diff.New(dc)
-	_, create, del, mod, err := differ.IncrementalDiff(records)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// NOPURGE
-	if dc.KeepUnknown {
-		del = []diff.Correlation{}
-	}
+		differ := diff.New(dc)
+		_, create, del, mod, err := differ.IncrementalDiff(records)
+		if err != nil {
+			return nil, err
+		}
 
-	msg := []string{}
-	for _, c := range append(del, append(create, mod...)...) {
-		msg = append(msg, c.String())
-	}
+		// NOPURGE
+		if dc.KeepUnknown {
+			del = []diff.Correlation{}
+		}
 
-	if len(create) == 0 && len(del) == 0 && len(mod) == 0 {
-		return nil, nil
-	}
+		msg := []string{}
+		for _, c := range append(del, append(create, mod...)...) {
+			msg = append(msg, c.String())
+		}
+
+		if len(create) == 0 && len(del) == 0 && len(mod) == 0 {
+			return nil, nil
+		}
 
-	corrections := []*models.Correction{
-		{
-			Msg: fmt.Sprintf("\n%s", strings.Join(msg, "\n")),
-			F: func() error {
-				return hp.updateRecords(dc.Name, create, del, mod)
+		corrections := []*models.Correction{
+			{
+				Msg: fmt.Sprintf("\n%s", strings.Join(msg, "\n")),
+				F: func() error {
+					return hp.updateRecords(dc.Name, create, del, mod)
+				},
 			},
-		},
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/inwx/inwxProvider.go b/providers/inwx/inwxProvider.go
index ac2f2ab8..76994fb9 100644
--- a/providers/inwx/inwxProvider.go
+++ b/providers/inwx/inwxProvider.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -247,31 +248,37 @@ func (api *inwxAPI) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Cor
 		return nil, err
 	}
 
-	corrections := []*models.Correction{}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, d := range create {
-		des := d.Desired
-		corrections = append(corrections, &models.Correction{
-			Msg: d.String(),
-			F:   func() error { return api.createRecord(dc.Name, des) },
-		})
-	}
-	for _, d := range del {
-		existingID := d.Existing.Original.(goinwx.NameserverRecord).ID
-		corrections = append(corrections, &models.Correction{
-			Msg: d.String(),
-			F:   func() error { return api.deleteRecord(existingID) },
-		})
-	}
-	for _, d := range mod {
-		rec := d.Desired
-		existingID := d.Existing.Original.(goinwx.NameserverRecord).ID
-		corrections = append(corrections, &models.Correction{
-			Msg: d.String(),
-			F:   func() error { return api.updateRecord(existingID, rec) },
-		})
+		for _, d := range create {
+			des := d.Desired
+			corrections = append(corrections, &models.Correction{
+				Msg: d.String(),
+				F:   func() error { return api.createRecord(dc.Name, des) },
+			})
+		}
+		for _, d := range del {
+			existingID := d.Existing.Original.(goinwx.NameserverRecord).ID
+			corrections = append(corrections, &models.Correction{
+				Msg: d.String(),
+				F:   func() error { return api.deleteRecord(existingID) },
+			})
+		}
+		for _, d := range mod {
+			rec := d.Desired
+			existingID := d.Existing.Original.(goinwx.NameserverRecord).ID
+			corrections = append(corrections, &models.Correction{
+				Msg: d.String(),
+				F:   func() error { return api.updateRecord(existingID, rec) },
+			})
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/linode/linodeProvider.go b/providers/linode/linodeProvider.go
index fb30502f..b3546367 100644
--- a/providers/linode/linodeProvider.go
+++ b/providers/linode/linodeProvider.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"github.com/miekg/dns/dnsutil"
 	"golang.org/x/oauth2"
@@ -155,72 +156,78 @@ func (api *linodeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
 		record.TTL = fixTTL(record.TTL)
 	}
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// Deletes first so changing type works etc.
-	for _, m := range del {
-		id := m.Existing.Original.(*domainRecord).ID
-		if id == 0 { // Skip ID 0, these are the default nameservers always present
-			continue
-		}
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, Linode ID: %d", m.String(), id),
-			F: func() error {
-				return api.deleteRecord(domainID, id)
-			},
-		}
-		corrections = append(corrections, corr)
-	}
-	for _, m := range create {
-		req, err := toReq(dc, m.Desired)
-		if err != nil {
-			return nil, err
-		}
-		j, err := json.Marshal(req)
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
 		if err != nil {
 			return nil, err
 		}
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s: %s", m.String(), string(j)),
-			F: func() error {
-				record, err := api.createRecord(domainID, req)
-				if err != nil {
-					return err
-				}
-				// TTL isn't saved when creating a record, so we will need to modify it immediately afterwards
-				return api.modifyRecord(domainID, record.ID, req)
-			},
-		}
-		corrections = append(corrections, corr)
-	}
-	for _, m := range modify {
-		id := m.Existing.Original.(*domainRecord).ID
-		if id == 0 { // Skip ID 0, these are the default nameservers always present
-			continue
-		}
-		req, err := toReq(dc, m.Desired)
-		if err != nil {
-			return nil, err
+
+		// Deletes first so changing type works etc.
+		for _, m := range del {
+			id := m.Existing.Original.(*domainRecord).ID
+			if id == 0 { // Skip ID 0, these are the default nameservers always present
+				continue
+			}
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, Linode ID: %d", m.String(), id),
+				F: func() error {
+					return api.deleteRecord(domainID, id)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		j, err := json.Marshal(req)
-		if err != nil {
-			return nil, err
+		for _, m := range create {
+			req, err := toReq(dc, m.Desired)
+			if err != nil {
+				return nil, err
+			}
+			j, err := json.Marshal(req)
+			if err != nil {
+				return nil, err
+			}
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s: %s", m.String(), string(j)),
+				F: func() error {
+					record, err := api.createRecord(domainID, req)
+					if err != nil {
+						return err
+					}
+					// TTL isn't saved when creating a record, so we will need to modify it immediately afterwards
+					return api.modifyRecord(domainID, record.ID, req)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, Linode ID: %d: %s", m.String(), id, string(j)),
-			F: func() error {
-				return api.modifyRecord(domainID, id, req)
-			},
+		for _, m := range modify {
+			id := m.Existing.Original.(*domainRecord).ID
+			if id == 0 { // Skip ID 0, these are the default nameservers always present
+				continue
+			}
+			req, err := toReq(dc, m.Desired)
+			if err != nil {
+				return nil, err
+			}
+			j, err := json.Marshal(req)
+			if err != nil {
+				return nil, err
+			}
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, Linode ID: %d: %s", m.String(), id, string(j)),
+				F: func() error {
+					return api.modifyRecord(domainID, id, req)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/msdns/corrections.go b/providers/msdns/corrections.go
index b3c326b3..f56572ec 100644
--- a/providers/msdns/corrections.go
+++ b/providers/msdns/corrections.go
@@ -3,6 +3,7 @@ package msdns
 import (
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 )
 
@@ -13,25 +14,33 @@ func (client *msdnsProvider) GenerateDomainCorrections(dc *models.DomainConfig,
 	models.PostProcessRecords(foundRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
+
+		differ := diff.New(dc)
+		_, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords)
+		if err != nil {
+			return nil, err
+		}
+
+		// Generate changes.
+		corrections := []*models.Correction{}
+		for _, del := range dels {
+			corrections = append(corrections, client.deleteRec(client.dnsserver, dc.Name, del))
+		}
+		for _, cre := range creates {
+			corrections = append(corrections, client.createRec(client.dnsserver, dc.Name, cre)...)
+		}
+		for _, m := range modifications {
+			corrections = append(corrections, client.modifyRec(client.dnsserver, dc.Name, m))
+		}
+		return corrections, nil
 
-	// Generate changes.
-	corrections := []*models.Correction{}
-	for _, del := range dels {
-		corrections = append(corrections, client.deleteRec(client.dnsserver, dc.Name, del))
-	}
-	for _, cre := range creates {
-		corrections = append(corrections, client.createRec(client.dnsserver, dc.Name, cre)...)
-	}
-	for _, m := range modifications {
-		corrections = append(corrections, client.modifyRec(client.dnsserver, dc.Name, m))
 	}
-	return corrections, nil
 
+	// Insert Future diff2 version here.
+
+	return corrections, nil
 }
 
 func (client *msdnsProvider) deleteRec(dnsserver, domainname string, cor diff.Correlation) *models.Correction {
diff --git a/providers/namecheap/namecheapProvider.go b/providers/namecheap/namecheapProvider.go
index c49d6738..b7f7c7ac 100644
--- a/providers/namecheap/namecheapProvider.go
+++ b/providers/namecheap/namecheapProvider.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	nc "github.com/billputer/go-namecheap"
@@ -190,41 +191,49 @@ func (n *namecheapProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo
 	// Normalize
 	models.PostProcessRecords(actual)
 
-	differ := diff.New(dc)
-	_, create, delete, modify, err := differ.IncrementalDiff(actual)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// // because namecheap doesn't have selective create, delete, modify,
-	// // we bundle them all up to send at once.  We *do* want to see the
-	// // changes though
+		differ := diff.New(dc)
+		_, create, delete, modify, err := differ.IncrementalDiff(actual)
+		if err != nil {
+			return nil, err
+		}
 
-	var desc []string
-	for _, i := range create {
-		desc = append(desc, "\n"+i.String())
-	}
-	for _, i := range delete {
-		desc = append(desc, "\n"+i.String())
-	}
-	for _, i := range modify {
-		desc = append(desc, "\n"+i.String())
-	}
+		// // because namecheap doesn't have selective create, delete, modify,
+		// // we bundle them all up to send at once.  We *do* want to see the
+		// // changes though
 
-	msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc)
-	corrections := []*models.Correction{}
-
-	// only create corrections if there are changes
-	if len(desc) > 0 {
-		corrections = append(corrections,
-			&models.Correction{
-				Msg: msg,
-				F: func() error {
-					return n.generateRecords(dc)
-				},
-			})
+		var desc []string
+		for _, i := range create {
+			desc = append(desc, "\n"+i.String())
+		}
+		for _, i := range delete {
+			desc = append(desc, "\n"+i.String())
+		}
+		for _, i := range modify {
+			desc = append(desc, "\n"+i.String())
+		}
+
+		msg := fmt.Sprintf("GENERATE_ZONE: %s (%d records)%s", dc.Name, len(dc.Records), desc)
+		corrections := []*models.Correction{}
+
+		// only create corrections if there are changes
+		if len(desc) > 0 {
+			corrections = append(corrections,
+				&models.Correction{
+					Msg: msg,
+					F: func() error {
+						return n.generateRecords(dc)
+					},
+				})
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/namedotcom/records.go b/providers/namedotcom/records.go
index 2d81a8a2..f3d6e24f 100644
--- a/providers/namedotcom/records.go
+++ b/providers/namedotcom/records.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/namedotcom/go/namecom"
 )
 
@@ -53,36 +54,44 @@ func (n *namedotcomProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
 	// Normalize
 	models.PostProcessRecords(actual)
 
-	differ := diff.New(dc)
-	_, create, del, mod, err := differ.IncrementalDiff(actual)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	corrections := []*models.Correction{}
+		differ := diff.New(dc)
+		_, create, del, mod, err := differ.IncrementalDiff(actual)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, d := range del {
-		rec := d.Existing.Original.(*namecom.Record)
-		c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.ID, dc.Name) }}
-		corrections = append(corrections, c)
-	}
-	for _, cre := range create {
-		rec := cre.Desired
-		c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }}
-		corrections = append(corrections, c)
-	}
-	for _, chng := range mod {
-		old := chng.Existing.Original.(*namecom.Record)
-		new := chng.Desired
-		c := &models.Correction{Msg: chng.String(), F: func() error {
-			err := n.deleteRecord(old.ID, dc.Name)
-			if err != nil {
-				return err
-			}
-			return n.createRecord(new, dc.Name)
-		}}
-		corrections = append(corrections, c)
+		corrections := []*models.Correction{}
+
+		for _, d := range del {
+			rec := d.Existing.Original.(*namecom.Record)
+			c := &models.Correction{Msg: d.String(), F: func() error { return n.deleteRecord(rec.ID, dc.Name) }}
+			corrections = append(corrections, c)
+		}
+		for _, cre := range create {
+			rec := cre.Desired
+			c := &models.Correction{Msg: cre.String(), F: func() error { return n.createRecord(rec, dc.Name) }}
+			corrections = append(corrections, c)
+		}
+		for _, chng := range mod {
+			old := chng.Existing.Original.(*namecom.Record)
+			new := chng.Desired
+			c := &models.Correction{Msg: chng.String(), F: func() error {
+				err := n.deleteRecord(old.ID, dc.Name)
+				if err != nil {
+					return err
+				}
+				return n.createRecord(new, dc.Name)
+			}}
+			corrections = append(corrections, c)
+		}
+		return corrections, nil
 	}
+
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/netcup/netcupProvider.go b/providers/netcup/netcupProvider.go
index 328bd0c3..6de9921b 100644
--- a/providers/netcup/netcupProvider.go
+++ b/providers/netcup/netcupProvider.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 )
 
@@ -100,48 +101,54 @@ func (api *netcupProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mod
 	// no need for txtutil.SplitSingleLongTxt in function GetDomainCorrections
 	// txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// Deletes first so changing type works etc.
-	for _, m := range del {
-		req := m.Existing.Original.(*record)
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, Netcup ID: %s", m.String(), req.ID),
-			F: func() error {
-				return api.deleteRecord(domain, req)
-			},
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
 		}
-		corrections = append(corrections, corr)
-	}
 
-	for _, m := range create {
-		req := fromRecordConfig(m.Desired)
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				return api.createRecord(domain, req)
-			},
+		// Deletes first so changing type works etc.
+		for _, m := range del {
+			req := m.Existing.Original.(*record)
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, Netcup ID: %s", m.String(), req.ID),
+				F: func() error {
+					return api.deleteRecord(domain, req)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
-	}
-	for _, m := range modify {
-		id := m.Existing.Original.(*record).ID
-		req := fromRecordConfig(m.Desired)
-		req.ID = id
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, Netcup ID: %s: ", m.String(), id),
-			F: func() error {
-				return api.modifyRecord(domain, req)
-			},
+
+		for _, m := range create {
+			req := fromRecordConfig(m.Desired)
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					return api.createRecord(domain, req)
+				},
+			}
+			corrections = append(corrections, corr)
+		}
+		for _, m := range modify {
+			id := m.Existing.Original.(*record).ID
+			req := fromRecordConfig(m.Desired)
+			req.ID = id
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, Netcup ID: %s: ", m.String(), id),
+				F: func() error {
+					return api.modifyRecord(domain, req)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
diff --git a/providers/netlify/netlifyProvider.go b/providers/netlify/netlifyProvider.go
index 1feaeafb..219c9519 100644
--- a/providers/netlify/netlifyProvider.go
+++ b/providers/netlify/netlifyProvider.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -176,7 +177,7 @@ func removeOtherApexNS(dc *models.DomainConfig) {
 }
 
 func (n *netlifyProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
-	var corrections []*models.Correction
+
 	err := dc.Punycode()
 	if err != nil {
 		return nil, err
@@ -192,58 +193,66 @@ func (n *netlifyProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
 	txtutil.SplitSingleLongTxt(dc.Records) // Auto split long TXT records
 	removeOtherApexNS(dc)
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(records)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	zone, err := n.getZone(dc.Name)
-	if err != nil {
-		return nil, err
-	}
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(records)
+		if err != nil {
+			return nil, err
+		}
 
-	// Deletes first so changing type works etc.
-	for _, m := range del {
-		id := m.Existing.Original.(*dnsRecord).ID
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				return n.deleteDNSRecord(zone.ID, id)
-			},
+		zone, err := n.getZone(dc.Name)
+		if err != nil {
+			return nil, err
 		}
-		corrections = append(corrections, corr)
-	}
 
-	for _, m := range create {
-		req := toReq(m.Desired)
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				_, err := n.createDNSRecord(zone.ID, req)
-				return err
-			},
+		// Deletes first so changing type works etc.
+		for _, m := range del {
+			id := m.Existing.Original.(*dnsRecord).ID
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					return n.deleteDNSRecord(zone.ID, id)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
-	}
 
-	for _, m := range modify {
-		id := m.Existing.Original.(*dnsRecord).ID
-		req := toReq(m.Desired)
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				if err := n.deleteDNSRecord(zone.ID, id); err != nil {
+		for _, m := range create {
+			req := toReq(m.Desired)
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					_, err := n.createDNSRecord(zone.ID, req)
 					return err
-				}
+				},
+			}
+			corrections = append(corrections, corr)
+		}
 
-				_, err := n.createDNSRecord(zone.ID, req)
-				return err
-			},
+		for _, m := range modify {
+			id := m.Existing.Original.(*dnsRecord).ID
+			req := toReq(m.Desired)
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					if err := n.deleteDNSRecord(zone.ID, id); err != nil {
+						return err
+					}
+
+					_, err := n.createDNSRecord(zone.ID, req)
+					return err
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/ns1/ns1Provider.go b/providers/ns1/ns1Provider.go
index 16724855..086e2f98 100644
--- a/providers/ns1/ns1Provider.go
+++ b/providers/ns1/ns1Provider.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"gopkg.in/ns1/ns1-go.v2/rest"
 	"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
@@ -162,45 +163,53 @@ func (n *nsone) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correct
 	//  Normalize
 	models.PostProcessRecords(existingRecords)
 
-	differ := diff.New(dc)
-	changedGroups, err := differ.ChangedGroups(existingRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	corrections := []*models.Correction{}
+		differ := diff.New(dc)
+		changedGroups, err := differ.ChangedGroups(existingRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	if dnssecCorrections := n.getDomainCorrectionsDNSSEC(domain, dc.AutoDNSSEC); dnssecCorrections != nil {
-		corrections = append(corrections, dnssecCorrections)
-	}
+		corrections := []*models.Correction{}
 
-	// each name/type is given to the api as a unit.
-	for k, descs := range changedGroups {
-		key := k
-
-		desc := strings.Join(descs, "\n")
-		_, current := existingGrouped[k]
-		recs, wanted := desiredGrouped[k]
-		if wanted && !current {
-			// pure addition
-			corrections = append(corrections, &models.Correction{
-				Msg: desc,
-				F:   func() error { return n.add(recs, dc.Name) },
-			})
-		} else if current && !wanted {
-			// pure deletion
-			corrections = append(corrections, &models.Correction{
-				Msg: desc,
-				F:   func() error { return n.remove(key, dc.Name) },
-			})
-		} else {
-			// modification
-			corrections = append(corrections, &models.Correction{
-				Msg: desc,
-				F:   func() error { return n.modify(recs, dc.Name) },
-			})
+		if dnssecCorrections := n.getDomainCorrectionsDNSSEC(domain, dc.AutoDNSSEC); dnssecCorrections != nil {
+			corrections = append(corrections, dnssecCorrections)
+		}
+
+		// each name/type is given to the api as a unit.
+		for k, descs := range changedGroups {
+			key := k
+
+			desc := strings.Join(descs, "\n")
+			_, current := existingGrouped[k]
+			recs, wanted := desiredGrouped[k]
+			if wanted && !current {
+				// pure addition
+				corrections = append(corrections, &models.Correction{
+					Msg: desc,
+					F:   func() error { return n.add(recs, dc.Name) },
+				})
+			} else if current && !wanted {
+				// pure deletion
+				corrections = append(corrections, &models.Correction{
+					Msg: desc,
+					F:   func() error { return n.remove(key, dc.Name) },
+				})
+			} else {
+				// modification
+				corrections = append(corrections, &models.Correction{
+					Msg: desc,
+					F:   func() error { return n.modify(recs, dc.Name) },
+				})
+			}
 		}
+		return corrections, nil
 	}
+
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/oracle/oracleProvider.go b/providers/oracle/oracleProvider.go
index c65ed058..95b77518 100644
--- a/providers/oracle/oracleProvider.go
+++ b/providers/oracle/oracleProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -239,75 +240,83 @@ func (o *oracleProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*model
 		}
 	}
 
-	differ := diff.New(dc)
-	_, create, dels, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	/*
-		Oracle's API doesn't have a way to update an existing record.
-		You can either update an existing RRSet, Domain (FQDN), or Zone in which you have to supply
-		the entire desired state, or you can patch specifying ADD/REMOVE actions.
-		Oracle's API is also increadibly slow, so updating individual RRSets is unbearably slow
-		for any size zone.
-	*/
-
-	corrections := []*models.Correction{}
-
-	if len(create) > 0 {
-		createRecords := models.Records{}
-		desc := ""
-		for _, d := range create {
-			createRecords = append(createRecords, d.Desired)
-			desc += d.String() + "\n"
+		differ := diff.New(dc)
+		_, create, dels, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
+		}
+
+		/*
+			Oracle's API doesn't have a way to update an existing record.
+			You can either update an existing RRSet, Domain (FQDN), or Zone in which you have to supply
+			the entire desired state, or you can patch specifying ADD/REMOVE actions.
+			Oracle's API is also increadibly slow, so updating individual RRSets is unbearably slow
+			for any size zone.
+		*/
+
+		corrections := []*models.Correction{}
+
+		if len(create) > 0 {
+			createRecords := models.Records{}
+			desc := ""
+			for _, d := range create {
+				createRecords = append(createRecords, d.Desired)
+				desc += d.String() + "\n"
+			}
+			desc = desc[:len(desc)-1]
+
+			corrections = append(corrections, &models.Correction{
+				Msg: desc,
+				F: func() error {
+					return o.patch(createRecords, nil, domain)
+				},
+			})
 		}
-		desc = desc[:len(desc)-1]
-
-		corrections = append(corrections, &models.Correction{
-			Msg: desc,
-			F: func() error {
-				return o.patch(createRecords, nil, domain)
-			},
-		})
-	}
 
-	if len(dels) > 0 {
-		deleteRecords := models.Records{}
-		desc := ""
-		for _, d := range dels {
-			deleteRecords = append(deleteRecords, d.Existing)
-			desc += d.String() + "\n"
+		if len(dels) > 0 {
+			deleteRecords := models.Records{}
+			desc := ""
+			for _, d := range dels {
+				deleteRecords = append(deleteRecords, d.Existing)
+				desc += d.String() + "\n"
+			}
+			desc = desc[:len(desc)-1]
+
+			corrections = append(corrections, &models.Correction{
+				Msg: desc,
+				F: func() error {
+					return o.patch(nil, deleteRecords, domain)
+				},
+			})
 		}
-		desc = desc[:len(desc)-1]
-
-		corrections = append(corrections, &models.Correction{
-			Msg: desc,
-			F: func() error {
-				return o.patch(nil, deleteRecords, domain)
-			},
-		})
-	}
 
-	if len(modify) > 0 {
-		createRecords := models.Records{}
-		deleteRecords := models.Records{}
-		desc := ""
-		for _, d := range modify {
-			createRecords = append(createRecords, d.Desired)
-			deleteRecords = append(deleteRecords, d.Existing)
-			desc += d.String() + "\n"
+		if len(modify) > 0 {
+			createRecords := models.Records{}
+			deleteRecords := models.Records{}
+			desc := ""
+			for _, d := range modify {
+				createRecords = append(createRecords, d.Desired)
+				deleteRecords = append(deleteRecords, d.Existing)
+				desc += d.String() + "\n"
+			}
+			desc = desc[:len(desc)-1]
+
+			corrections = append(corrections, &models.Correction{
+				Msg: desc,
+				F: func() error {
+					return o.patch(createRecords, deleteRecords, domain)
+				},
+			})
 		}
-		desc = desc[:len(desc)-1]
-
-		corrections = append(corrections, &models.Correction{
-			Msg: desc,
-			F: func() error {
-				return o.patch(createRecords, deleteRecords, domain)
-			},
-		})
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/ovh/ovhProvider.go b/providers/ovh/ovhProvider.go
index e6e91325..0379744b 100644
--- a/providers/ovh/ovhProvider.go
+++ b/providers/ovh/ovhProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"github.com/ovh/go-ovh/ovh"
 )
@@ -128,48 +129,56 @@ func (c *ovhProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.C
 	// Normalize
 	models.PostProcessRecords(actual)
 
-	differ := diff.New(dc)
-	_, create, delete, modify, err := differ.IncrementalDiff(actual)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	corrections := []*models.Correction{}
+		differ := diff.New(dc)
+		_, create, delete, modify, err := differ.IncrementalDiff(actual)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, del := range delete {
-		rec := del.Existing.Original.(*Record)
-		corrections = append(corrections, &models.Correction{
-			Msg: del.String(),
-			F:   c.deleteRecordFunc(rec.ID, dc.Name),
-		})
-	}
+		corrections := []*models.Correction{}
 
-	for _, cre := range create {
-		rec := cre.Desired
-		corrections = append(corrections, &models.Correction{
-			Msg: cre.String(),
-			F:   c.createRecordFunc(rec, dc.Name),
-		})
-	}
+		for _, del := range delete {
+			rec := del.Existing.Original.(*Record)
+			corrections = append(corrections, &models.Correction{
+				Msg: del.String(),
+				F:   c.deleteRecordFunc(rec.ID, dc.Name),
+			})
+		}
 
-	for _, mod := range modify {
-		oldR := mod.Existing.Original.(*Record)
-		newR := mod.Desired
-		corrections = append(corrections, &models.Correction{
-			Msg: mod.String(),
-			F:   c.updateRecordFunc(oldR, newR, dc.Name),
-		})
-	}
+		for _, cre := range create {
+			rec := cre.Desired
+			corrections = append(corrections, &models.Correction{
+				Msg: cre.String(),
+				F:   c.createRecordFunc(rec, dc.Name),
+			})
+		}
+
+		for _, mod := range modify {
+			oldR := mod.Existing.Original.(*Record)
+			newR := mod.Desired
+			corrections = append(corrections, &models.Correction{
+				Msg: mod.String(),
+				F:   c.updateRecordFunc(oldR, newR, dc.Name),
+			})
+		}
 
-	if len(corrections) > 0 {
-		corrections = append(corrections, &models.Correction{
-			Msg: "REFRESH zone " + dc.Name,
-			F: func() error {
-				return c.refreshZone(dc.Name)
-			},
-		})
+		if len(corrections) > 0 {
+			corrections = append(corrections, &models.Correction{
+				Msg: "REFRESH zone " + dc.Name,
+				F: func() error {
+					return c.refreshZone(dc.Name)
+				},
+			})
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/packetframe/packetframeProvider.go b/providers/packetframe/packetframeProvider.go
index 53c4e413..907c042e 100644
--- a/providers/packetframe/packetframeProvider.go
+++ b/providers/packetframe/packetframeProvider.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 )
 
@@ -123,63 +124,69 @@ func (api *packetframeProvider) GetDomainCorrections(dc *models.DomainConfig) ([
 	// Normalize
 	models.PostProcessRecords(existingRecords)
 
-	differ := diff.New(dc)
-	_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, m := range create {
-		req, err := toReq(zone.ID, dc, m.Desired)
+		differ := diff.New(dc)
+		_, create, delete, modify, err := differ.IncrementalDiff(existingRecords)
 		if err != nil {
 			return nil, err
 		}
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				_, err := api.createRecord(req)
-				return err
-			},
-		}
-		corrections = append(corrections, corr)
-	}
 
-	for _, m := range delete {
-		original := m.Existing.Original.(*domainRecord)
-		if original.ID == "0" { // Skip the default nameservers
-			continue
+		for _, m := range create {
+			req, err := toReq(zone.ID, dc, m.Desired)
+			if err != nil {
+				return nil, err
+			}
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					_, err := api.createRecord(req)
+					return err
+				},
+			}
+			corrections = append(corrections, corr)
 		}
 
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				err := api.deleteRecord(zone.ID, original.ID)
-				return err
-			},
+		for _, m := range delete {
+			original := m.Existing.Original.(*domainRecord)
+			if original.ID == "0" { // Skip the default nameservers
+				continue
+			}
+
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					err := api.deleteRecord(zone.ID, original.ID)
+					return err
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
-	}
 
-	for _, m := range modify {
-		original := m.Existing.Original.(*domainRecord)
-		if original.ID == "0" { // Skip the default nameservers
-			continue
+		for _, m := range modify {
+			original := m.Existing.Original.(*domainRecord)
+			if original.ID == "0" { // Skip the default nameservers
+				continue
+			}
+
+			req, _ := toReq(zone.ID, dc, m.Desired)
+			req.ID = original.ID
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					err := api.modifyRecord(req)
+					return err
+				},
+			}
+			corrections = append(corrections, corr)
 		}
 
-		req, _ := toReq(zone.ID, dc, m.Desired)
-		req.ID = original.ID
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				err := api.modifyRecord(req)
-				return err
-			},
-		}
-		corrections = append(corrections, corr)
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/porkbun/porkbunProvider.go b/providers/porkbun/porkbunProvider.go
index e46cc859..16d6ac9e 100644
--- a/providers/porkbun/porkbunProvider.go
+++ b/providers/porkbun/porkbunProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 )
@@ -98,57 +99,63 @@ func (c *porkbunProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
 		record.TTL = fixTTL(record.TTL)
 	}
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	// Deletes first so changing type works etc.
-	for _, m := range del {
-		id := m.Existing.Original.(*domainRecord).ID
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, porkbun ID: %s", m.String(), id),
-			F: func() error {
-				return c.deleteRecord(dc.Name, id)
-			},
-		}
-		corrections = append(corrections, corr)
-	}
-
-	for _, m := range create {
-		req, err := toReq(m.Desired)
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
 		if err != nil {
 			return nil, err
 		}
 
-		corr := &models.Correction{
-			Msg: m.String(),
-			F: func() error {
-				return c.createRecord(dc.Name, req)
-			},
+		// Deletes first so changing type works etc.
+		for _, m := range del {
+			id := m.Existing.Original.(*domainRecord).ID
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, porkbun ID: %s", m.String(), id),
+				F: func() error {
+					return c.deleteRecord(dc.Name, id)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
-	}
 
-	for _, m := range modify {
-		id := m.Existing.Original.(*domainRecord).ID
-		req, err := toReq(m.Desired)
-		if err != nil {
-			return nil, err
+		for _, m := range create {
+			req, err := toReq(m.Desired)
+			if err != nil {
+				return nil, err
+			}
+
+			corr := &models.Correction{
+				Msg: m.String(),
+				F: func() error {
+					return c.createRecord(dc.Name, req)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
 
-		corr := &models.Correction{
-			Msg: fmt.Sprintf("%s, porkbun ID: %s: ", m.String(), id),
-			F: func() error {
-				return c.modifyRecord(dc.Name, id, req)
-			},
+		for _, m := range modify {
+			id := m.Existing.Original.(*domainRecord).ID
+			req, err := toReq(m.Desired)
+			if err != nil {
+				return nil, err
+			}
+
+			corr := &models.Correction{
+				Msg: fmt.Sprintf("%s, porkbun ID: %s: ", m.String(), id),
+				F: func() error {
+					return c.modifyRecord(dc.Name, id, req)
+				},
+			}
+			corrections = append(corrections, corr)
 		}
-		corrections = append(corrections, corr)
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/powerdns/dns.go b/providers/powerdns/dns.go
index 8284b970..7d09cfc9 100644
--- a/providers/powerdns/dns.go
+++ b/providers/powerdns/dns.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/mittwald/go-powerdns/apis/zones"
 	"github.com/mittwald/go-powerdns/pdnshttp"
 )
@@ -48,7 +49,6 @@ func (dsp *powerdnsProvider) GetZoneRecords(domain string) (models.Records, erro
 
 // GetDomainCorrections returns a list of corrections to update a domain.
 func (dsp *powerdnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
-	var corrections []*models.Correction
 
 	// get current zone records
 	curRecords, err := dsp.GetZoneRecords(dc.Name)
@@ -62,65 +62,73 @@ func (dsp *powerdnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m
 	}
 	models.PostProcessRecords(curRecords)
 
-	// create record diff by group
-	keysToUpdate, err := (diff.New(dc)).ChangedGroups(curRecords)
-	if err != nil {
-		return nil, err
-	}
-	desiredRecords := dc.Records.GroupedByKey()
-
-	var cuCorrections []*models.Correction
-	var dCorrections []*models.Correction
-
-	// add create/update and delete corrections separately
-	for label, msgs := range keysToUpdate {
-		labelName := label.NameFQDN + "."
-		labelType := label.Type
-		msgJoined := strings.Join(msgs, "\n   ")
-
-		if _, ok := desiredRecords[label]; !ok {
-			// no record found so delete it
-			dCorrections = append(dCorrections, &models.Correction{
-				Msg: msgJoined,
-				F: func() error {
-					return dsp.client.Zones().RemoveRecordSetFromZone(context.Background(), dsp.ServerName, dc.Name, labelName, labelType)
-				},
-			})
-		} else {
-			// record found so create or update it
-			ttl := desiredRecords[label][0].TTL
-			var records []zones.Record
-			for _, recordContent := range desiredRecords[label] {
-				records = append(records, zones.Record{
-					Content: recordContent.GetTargetCombined(),
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
+
+		// create record diff by group
+		keysToUpdate, err := (diff.New(dc)).ChangedGroups(curRecords)
+		if err != nil {
+			return nil, err
+		}
+		desiredRecords := dc.Records.GroupedByKey()
+
+		var cuCorrections []*models.Correction
+		var dCorrections []*models.Correction
+
+		// add create/update and delete corrections separately
+		for label, msgs := range keysToUpdate {
+			labelName := label.NameFQDN + "."
+			labelType := label.Type
+			msgJoined := strings.Join(msgs, "\n   ")
+
+			if _, ok := desiredRecords[label]; !ok {
+				// no record found so delete it
+				dCorrections = append(dCorrections, &models.Correction{
+					Msg: msgJoined,
+					F: func() error {
+						return dsp.client.Zones().RemoveRecordSetFromZone(context.Background(), dsp.ServerName, dc.Name, labelName, labelType)
+					},
 				})
-			}
-			cuCorrections = append(cuCorrections, &models.Correction{
-				Msg: msgJoined,
-				F: func() error {
-					return dsp.client.Zones().AddRecordSetToZone(context.Background(), dsp.ServerName, dc.Name, zones.ResourceRecordSet{
-						Name:       labelName,
-						Type:       labelType,
-						TTL:        int(ttl),
-						Records:    records,
-						ChangeType: zones.ChangeTypeReplace,
+			} else {
+				// record found so create or update it
+				ttl := desiredRecords[label][0].TTL
+				var records []zones.Record
+				for _, recordContent := range desiredRecords[label] {
+					records = append(records, zones.Record{
+						Content: recordContent.GetTargetCombined(),
 					})
-				},
-			})
+				}
+				cuCorrections = append(cuCorrections, &models.Correction{
+					Msg: msgJoined,
+					F: func() error {
+						return dsp.client.Zones().AddRecordSetToZone(context.Background(), dsp.ServerName, dc.Name, zones.ResourceRecordSet{
+							Name:       labelName,
+							Type:       labelType,
+							TTL:        int(ttl),
+							Records:    records,
+							ChangeType: zones.ChangeTypeReplace,
+						})
+					},
+				})
+			}
 		}
-	}
 
-	// append corrections in the right order
-	// delete corrections must be run first to avoid correlations with existing RR
-	corrections = append(corrections, dCorrections...)
-	corrections = append(corrections, cuCorrections...)
+		// append corrections in the right order
+		// delete corrections must be run first to avoid correlations with existing RR
+		corrections = append(corrections, dCorrections...)
+		corrections = append(corrections, cuCorrections...)
 
-	// DNSSec corrections
-	dnssecCorrections, err := dsp.getDNSSECCorrections(dc)
-	if err != nil {
-		return nil, err
+		// DNSSec corrections
+		dnssecCorrections, err := dsp.getDNSSECCorrections(dc)
+		if err != nil {
+			return nil, err
+		}
+		corrections = append(corrections, dnssecCorrections...)
+
+		return corrections, nil
 	}
-	corrections = append(corrections, dnssecCorrections...)
+
+	// Insert Future diff2 version here.
 
 	return corrections, nil
 }
diff --git a/providers/route53/route53Provider.go b/providers/route53/route53Provider.go
index e4c4dbf5..1f03c74d 100644
--- a/providers/route53/route53Provider.go
+++ b/providers/route53/route53Provider.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -261,8 +262,6 @@ func (r *route53Provider) getZoneRecords(zone r53Types.HostedZone) (models.Recor
 func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
 	dc.Punycode()
 
-	var corrections = []*models.Correction{}
-
 	zone, err := r.getZone(dc)
 	if err != nil {
 		return nil, err
@@ -284,108 +283,134 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
 	models.PostProcessRecords(existingRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	// diff
-	differ := diff.New(dc, getAliasMap)
-	namesToUpdate, err := differ.ChangedGroups(existingRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	if len(namesToUpdate) == 0 {
-		return nil, nil
-	}
-
-	updates := map[models.RecordKey][]*models.RecordConfig{}
-
-	// for each name we need to update, collect relevant records from our desired domain state
-	for k := range namesToUpdate {
-		updates[k] = nil
-		for _, rc := range dc.Records {
-			if rc.Key() == k {
-				updates[k] = append(updates[k], rc)
-			}
+		// diff
+		differ := diff.New(dc, getAliasMap)
+		namesToUpdate, err := differ.ChangedGroups(existingRecords)
+		if err != nil {
+			return nil, err
 		}
-	}
 
-	// updateOrder is the order that the updates will happen.
-	// The order should be sorted by NameFQDN, then Type, with R53_ALIAS_*
-	// types sorted after all other types. R53_ALIAS_* needs to be last
-	// because they are order dependent (aliases must refer to labels
-	// that already exist).
-	var updateOrder []models.RecordKey
-	// Collect the keys
-	for k := range updates {
-		updateOrder = append(updateOrder, k)
-	}
-	// Sort themm
-	sort.Slice(updateOrder, func(i, j int) bool {
-		if updateOrder[i].Type == updateOrder[j].Type {
-			return updateOrder[i].NameFQDN < updateOrder[j].NameFQDN
+		if len(namesToUpdate) == 0 {
+			return nil, nil
 		}
 
-		if strings.HasPrefix(updateOrder[i].Type, "R53_ALIAS_") {
-			return false
-		}
-		if strings.HasPrefix(updateOrder[j].Type, "R53_ALIAS_") {
-			return true
+		updates := map[models.RecordKey][]*models.RecordConfig{}
+
+		// for each name we need to update, collect relevant records from our desired domain state
+		for k := range namesToUpdate {
+			updates[k] = nil
+			for _, rc := range dc.Records {
+				if rc.Key() == k {
+					updates[k] = append(updates[k], rc)
+				}
+			}
 		}
 
-		if updateOrder[i].NameFQDN == updateOrder[j].NameFQDN {
-			return updateOrder[i].Type < updateOrder[j].Type
+		// updateOrder is the order that the updates will happen.
+		// The order should be sorted by NameFQDN, then Type, with R53_ALIAS_*
+		// types sorted after all other types. R53_ALIAS_* needs to be last
+		// because they are order dependent (aliases must refer to labels
+		// that already exist).
+		var updateOrder []models.RecordKey
+		// Collect the keys
+		for k := range updates {
+			updateOrder = append(updateOrder, k)
 		}
-		return updateOrder[i].NameFQDN < updateOrder[j].NameFQDN
-	})
+		// Sort themm
+		sort.Slice(updateOrder, func(i, j int) bool {
+			if updateOrder[i].Type == updateOrder[j].Type {
+				return updateOrder[i].NameFQDN < updateOrder[j].NameFQDN
+			}
 
-	// we collect all changes into one of two categories now:
-	// pure deletions where we delete an entire record set,
-	// or changes where we upsert an entire record set.
-	dels := []r53Types.Change{}
-	delDesc := []string{}
-	changes := []r53Types.Change{}
-	changeDesc := []string{}
-
-	for _, k := range updateOrder {
-		recs := updates[k]
-		// If there are no records in our desired state for a key, this
-		// indicates we should delete all records at that key.
-		if len(recs) == 0 {
-			// To delete, we submit the original resource set we got from r53.
-			var (
-				rrset r53Types.ResourceRecordSet
-				found bool
-			)
-			// Find the original resource set:
-			for _, r := range r.originalRecords {
-				if unescape(r.Name) == k.NameFQDN && (string(r.Type) == k.Type || k.Type == "R53_ALIAS_"+string(r.Type)) {
-					rrset = r
-					found = true
-					break
-				}
+			if strings.HasPrefix(updateOrder[i].Type, "R53_ALIAS_") {
+				return false
 			}
-			if !found {
-				// This should not happen.
-				return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type)
+			if strings.HasPrefix(updateOrder[j].Type, "R53_ALIAS_") {
+				return true
 			}
-			// Assemble the change and add it to the list:
-			chg := r53Types.Change{
-				Action:            r53Types.ChangeActionDelete,
-				ResourceRecordSet: &rrset,
+
+			if updateOrder[i].NameFQDN == updateOrder[j].NameFQDN {
+				return updateOrder[i].Type < updateOrder[j].Type
 			}
-			dels = append(dels, chg)
-			delDesc = append(delDesc, strings.Join(namesToUpdate[k], "\n"))
-		} else {
-			// If it isn't a delete, it must be either a change or create. In
-			// either case, we build a new record set from the desired state and
-			// UPSERT it.
-
-			if strings.HasPrefix(k.Type, "R53_ALIAS_") {
-				// Each R53_ALIAS_* requires an individual change.
-				if len(recs) != 1 {
-					log.Fatal("Only one R53_ALIAS_ permitted on a label")
+			return updateOrder[i].NameFQDN < updateOrder[j].NameFQDN
+		})
+
+		// we collect all changes into one of two categories now:
+		// pure deletions where we delete an entire record set,
+		// or changes where we upsert an entire record set.
+		dels := []r53Types.Change{}
+		delDesc := []string{}
+		changes := []r53Types.Change{}
+		changeDesc := []string{}
+
+		for _, k := range updateOrder {
+			recs := updates[k]
+			// If there are no records in our desired state for a key, this
+			// indicates we should delete all records at that key.
+			if len(recs) == 0 {
+				// To delete, we submit the original resource set we got from r53.
+				var (
+					rrset r53Types.ResourceRecordSet
+					found bool
+				)
+				// Find the original resource set:
+				for _, r := range r.originalRecords {
+					if unescape(r.Name) == k.NameFQDN && (string(r.Type) == k.Type || k.Type == "R53_ALIAS_"+string(r.Type)) {
+						rrset = r
+						found = true
+						break
+					}
+				}
+				if !found {
+					// This should not happen.
+					return nil, fmt.Errorf("no record set found to delete. Name: '%s'. Type: '%s'", k.NameFQDN, k.Type)
+				}
+				// Assemble the change and add it to the list:
+				chg := r53Types.Change{
+					Action:            r53Types.ChangeActionDelete,
+					ResourceRecordSet: &rrset,
 				}
-				for _, r := range recs {
-					rrset := aliasToRRSet(zone, r)
-					rrset.Name = aws.String(k.NameFQDN)
+				dels = append(dels, chg)
+				delDesc = append(delDesc, strings.Join(namesToUpdate[k], "\n"))
+			} else {
+				// If it isn't a delete, it must be either a change or create. In
+				// either case, we build a new record set from the desired state and
+				// UPSERT it.
+
+				if strings.HasPrefix(k.Type, "R53_ALIAS_") {
+					// Each R53_ALIAS_* requires an individual change.
+					if len(recs) != 1 {
+						log.Fatal("Only one R53_ALIAS_ permitted on a label")
+					}
+					for _, r := range recs {
+						rrset := aliasToRRSet(zone, r)
+						rrset.Name = aws.String(k.NameFQDN)
+						// Assemble the change and add it to the list:
+						chg := r53Types.Change{
+							Action:            r53Types.ChangeActionUpsert,
+							ResourceRecordSet: rrset,
+						}
+						changes = append(changes, chg)
+						changeDesc = append(changeDesc, strings.Join(namesToUpdate[k], "\n"))
+					}
+				} else {
+					// All other keys combine their updates into one rrset:
+					rrset := &r53Types.ResourceRecordSet{
+						Name: aws.String(k.NameFQDN),
+						Type: r53Types.RRType(k.Type),
+					}
+					for _, r := range recs {
+						val := r.GetTargetCombined()
+						rr := r53Types.ResourceRecord{
+							Value: aws.String(val),
+						}
+						rrset.ResourceRecords = append(rrset.ResourceRecords, rr)
+						i := int64(r.TTL)
+						rrset.TTL = &i // TODO: make sure that ttls are consistent within a set
+					}
 					// Assemble the change and add it to the list:
 					chg := r53Types.Change{
 						Action:            r53Types.ChangeActionUpsert,
@@ -394,79 +419,61 @@ func (r *route53Provider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
 					changes = append(changes, chg)
 					changeDesc = append(changeDesc, strings.Join(namesToUpdate[k], "\n"))
 				}
-			} else {
-				// All other keys combine their updates into one rrset:
-				rrset := &r53Types.ResourceRecordSet{
-					Name: aws.String(k.NameFQDN),
-					Type: r53Types.RRType(k.Type),
-				}
-				for _, r := range recs {
-					val := r.GetTargetCombined()
-					rr := r53Types.ResourceRecord{
-						Value: aws.String(val),
-					}
-					rrset.ResourceRecords = append(rrset.ResourceRecords, rr)
-					i := int64(r.TTL)
-					rrset.TTL = &i // TODO: make sure that ttls are consistent within a set
-				}
-				// Assemble the change and add it to the list:
-				chg := r53Types.Change{
-					Action:            r53Types.ChangeActionUpsert,
-					ResourceRecordSet: rrset,
-				}
-				changes = append(changes, chg)
-				changeDesc = append(changeDesc, strings.Join(namesToUpdate[k], "\n"))
-			}
 
+			}
 		}
-	}
 
-	addCorrection := func(msg string, req *r53.ChangeResourceRecordSetsInput) {
-		corrections = append(corrections,
-			&models.Correction{
-				Msg: msg,
-				F: func() error {
-					var err error
-					req.HostedZoneId = zone.Id
-					withRetry(func() error {
-						_, err = r.client.ChangeResourceRecordSets(context.Background(), req)
+		addCorrection := func(msg string, req *r53.ChangeResourceRecordSetsInput) {
+			corrections = append(corrections,
+				&models.Correction{
+					Msg: msg,
+					F: func() error {
+						var err error
+						req.HostedZoneId = zone.Id
+						withRetry(func() error {
+							_, err = r.client.ChangeResourceRecordSets(context.Background(), req)
+							return err
+						})
 						return err
-					})
-					return err
-				},
-			})
-	}
+					},
+				})
+		}
 
-	batcher := newChangeBatcher(dels)
-	for batcher.Next() {
-		start, end := batcher.Batch()
-		batch := dels[start:end]
-		descBatchStr := "\n" + strings.Join(delDesc[start:end], "\n") + "\n"
-		req := &r53.ChangeResourceRecordSetsInput{
-			ChangeBatch: &r53Types.ChangeBatch{Changes: batch},
+		batcher := newChangeBatcher(dels)
+		for batcher.Next() {
+			start, end := batcher.Batch()
+			batch := dels[start:end]
+			descBatchStr := "\n" + strings.Join(delDesc[start:end], "\n") + "\n"
+			req := &r53.ChangeResourceRecordSetsInput{
+				ChangeBatch: &r53Types.ChangeBatch{Changes: batch},
+			}
+			addCorrection(descBatchStr, req)
+		}
+		if err := batcher.Err(); err != nil {
+			return nil, err
 		}
-		addCorrection(descBatchStr, req)
-	}
-	if err := batcher.Err(); err != nil {
-		return nil, err
-	}
 
-	batcher = newChangeBatcher(changes)
-	for batcher.Next() {
-		start, end := batcher.Batch()
-		batch := changes[start:end]
-		descBatchStr := "\n" + strings.Join(changeDesc[start:end], "\n") + "\n"
-		req := &r53.ChangeResourceRecordSetsInput{
-			ChangeBatch: &r53Types.ChangeBatch{Changes: batch},
+		batcher = newChangeBatcher(changes)
+		for batcher.Next() {
+			start, end := batcher.Batch()
+			batch := changes[start:end]
+			descBatchStr := "\n" + strings.Join(changeDesc[start:end], "\n") + "\n"
+			req := &r53.ChangeResourceRecordSetsInput{
+				ChangeBatch: &r53Types.ChangeBatch{Changes: batch},
+			}
+			addCorrection(descBatchStr, req)
 		}
-		addCorrection(descBatchStr, req)
-	}
-	if err := batcher.Err(); err != nil {
-		return nil, err
+		if err := batcher.Err(); err != nil {
+			return nil, err
+		}
+
+		return corrections, nil
+
 	}
 
-	return corrections, nil
+	// Insert Future diff2 version here.
 
+	return corrections, nil
 }
 
 func nativeToRecords(set r53Types.ResourceRecordSet, origin string) ([]*models.RecordConfig, error) {
diff --git a/providers/rwth/dns.go b/providers/rwth/dns.go
index 8c5edd2a..8112ef9b 100644
--- a/providers/rwth/dns.go
+++ b/providers/rwth/dns.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/txtutil"
 )
 
@@ -51,44 +52,50 @@ func (api *rwthProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*model
 	models.PostProcessRecords(existingRecords)
 	txtutil.SplitSingleLongTxt(dc.Records) // Autosplit long TXT records
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, d := range create {
-		des := d.Desired
-		corrections = append(corrections, &models.Correction{
-			Msg: d.String(),
-			F:   func() error { return api.createRecord(dc.Name, des) },
-		})
-	}
-	for _, d := range del {
-		existingRecord := d.Existing.Original.(RecordReply)
-		corrections = append(corrections, &models.Correction{
-			Msg: d.String(),
-			F:   func() error { return api.destroyRecord(existingRecord) },
-		})
-	}
-	for _, d := range modify {
-		rec := d.Desired
-		existingID := d.Existing.Original.(RecordReply).ID
-		corrections = append(corrections, &models.Correction{
-			Msg: d.String(),
-			F:   func() error { return api.updateRecord(existingID, *rec) },
-		})
-	}
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(existingRecords)
+		if err != nil {
+			return nil, err
+		}
+
+		for _, d := range create {
+			des := d.Desired
+			corrections = append(corrections, &models.Correction{
+				Msg: d.String(),
+				F:   func() error { return api.createRecord(dc.Name, des) },
+			})
+		}
+		for _, d := range del {
+			existingRecord := d.Existing.Original.(RecordReply)
+			corrections = append(corrections, &models.Correction{
+				Msg: d.String(),
+				F:   func() error { return api.destroyRecord(existingRecord) },
+			})
+		}
+		for _, d := range modify {
+			rec := d.Desired
+			existingID := d.Existing.Original.(RecordReply).ID
+			corrections = append(corrections, &models.Correction{
+				Msg: d.String(),
+				F:   func() error { return api.updateRecord(existingID, *rec) },
+			})
+		}
 
-	// And deploy if any corrections were applied
-	if len(corrections) > 0 {
-		corrections = append(corrections, &models.Correction{
-			Msg: fmt.Sprintf("Deploy zone %s", domain),
-			F:   func() error { return api.deployZone(domain) },
-		})
+		// And deploy if any corrections were applied
+		if len(corrections) > 0 {
+			corrections = append(corrections, &models.Correction{
+				Msg: fmt.Sprintf("Deploy zone %s", domain),
+				F:   func() error { return api.deployZone(domain) },
+			})
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
diff --git a/providers/softlayer/softlayerProvider.go b/providers/softlayer/softlayerProvider.go
index 18050d94..c968313e 100644
--- a/providers/softlayer/softlayerProvider.go
+++ b/providers/softlayer/softlayerProvider.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/printer"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"github.com/softlayer/softlayer-go/datatypes"
@@ -67,7 +68,6 @@ func (s *softlayerProvider) GetZoneRecords(domain string) (models.Records, error
 
 // GetDomainCorrections returns corrections to update a domain.
 func (s *softlayerProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
-	corrections := []*models.Correction{}
 
 	domain, err := s.getDomain(&dc.Name)
 
@@ -81,34 +81,42 @@ func (s *softlayerProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mo
 		return nil, err
 	}
 
-	_, create, delete, modify, err := diff.New(dc).IncrementalDiff(actual)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, del := range delete {
-		existing := del.Existing.Original.(datatypes.Dns_Domain_ResourceRecord)
-		corrections = append(corrections, &models.Correction{
-			Msg: del.String(),
-			F:   s.deleteRecordFunc(*existing.Id),
-		})
-	}
+		_, create, delete, modify, err := diff.New(dc).IncrementalDiff(actual)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, cre := range create {
-		corrections = append(corrections, &models.Correction{
-			Msg: cre.String(),
-			F:   s.createRecordFunc(cre.Desired, domain),
-		})
-	}
+		for _, del := range delete {
+			existing := del.Existing.Original.(datatypes.Dns_Domain_ResourceRecord)
+			corrections = append(corrections, &models.Correction{
+				Msg: del.String(),
+				F:   s.deleteRecordFunc(*existing.Id),
+			})
+		}
+
+		for _, cre := range create {
+			corrections = append(corrections, &models.Correction{
+				Msg: cre.String(),
+				F:   s.createRecordFunc(cre.Desired, domain),
+			})
+		}
 
-	for _, mod := range modify {
-		existing := mod.Existing.Original.(datatypes.Dns_Domain_ResourceRecord)
-		corrections = append(corrections, &models.Correction{
-			Msg: mod.String(),
-			F:   s.updateRecordFunc(&existing, mod.Desired),
-		})
+		for _, mod := range modify {
+			existing := mod.Existing.Original.(datatypes.Dns_Domain_ResourceRecord)
+			corrections = append(corrections, &models.Correction{
+				Msg: mod.String(),
+				F:   s.updateRecordFunc(&existing, mod.Desired),
+			})
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/transip/transipProvider.go b/providers/transip/transipProvider.go
index 2f0d0c30..726f2e39 100644
--- a/providers/transip/transipProvider.go
+++ b/providers/transip/transipProvider.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"github.com/transip/gotransip/v6"
 	"github.com/transip/gotransip/v6/domain"
@@ -79,7 +80,6 @@ func init() {
 }
 
 func (n *transipProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
-	var corrections []*models.Correction
 
 	curRecords, err := n.GetZoneRecords(dc.Name)
 	if err != nil {
@@ -94,69 +94,77 @@ func (n *transipProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
 
 	models.PostProcessRecords(curRecords)
 
-	differ := diff.New(dc)
-	_, create, del, modify, err := differ.IncrementalDiff(curRecords)
-	if err != nil {
-		return nil, err
-	}
+	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, del := range del {
-		entry, err := recordToNative(del.Existing)
+		differ := diff.New(dc)
+		_, create, del, modify, err := differ.IncrementalDiff(curRecords)
 		if err != nil {
 			return nil, err
 		}
 
-		corrections = append(corrections, &models.Correction{
-			Msg: del.String(),
-			F:   func() error { return n.domains.RemoveDNSEntry(dc.Name, entry) },
-		})
-	}
+		for _, del := range del {
+			entry, err := recordToNative(del.Existing)
+			if err != nil {
+				return nil, err
+			}
 
-	for _, cre := range create {
-		entry, err := recordToNative(cre.Desired)
-		if err != nil {
-			return nil, err
+			corrections = append(corrections, &models.Correction{
+				Msg: del.String(),
+				F:   func() error { return n.domains.RemoveDNSEntry(dc.Name, entry) },
+			})
 		}
 
-		corrections = append(corrections, &models.Correction{
-			Msg: cre.String(),
-			F:   func() error { return n.domains.AddDNSEntry(dc.Name, entry) },
-		})
-	}
-
-	for _, mod := range modify {
-		targetEntry, err := recordToNative(mod.Desired)
-		if err != nil {
-			return nil, err
-		}
+		for _, cre := range create {
+			entry, err := recordToNative(cre.Desired)
+			if err != nil {
+				return nil, err
+			}
 
-		// TransIP identifies records by (Label, TTL Type), we can only update it if only the contents
-		// has changed. Otherwise we delete the old record and create the new one
-		if canUpdateDNSEntry(mod.Desired, mod.Existing) {
 			corrections = append(corrections, &models.Correction{
-				Msg: mod.String(),
-				F:   func() error { return n.domains.UpdateDNSEntry(dc.Name, targetEntry) },
+				Msg: cre.String(),
+				F:   func() error { return n.domains.AddDNSEntry(dc.Name, entry) },
 			})
-		} else {
-			oldEntry, err := recordToNative(mod.Existing)
+		}
+
+		for _, mod := range modify {
+			targetEntry, err := recordToNative(mod.Desired)
 			if err != nil {
 				return nil, err
 			}
 
-			corrections = append(corrections,
-				&models.Correction{
-					Msg: mod.String() + "[1/2]",
-					F:   func() error { return n.domains.RemoveDNSEntry(dc.Name, oldEntry) },
-				},
-				&models.Correction{
-					Msg: mod.String() + "[2/2]",
-					F:   func() error { return n.domains.AddDNSEntry(dc.Name, targetEntry) },
-				},
-			)
+			// TransIP identifies records by (Label, TTL Type), we can only update it if only the contents
+			// has changed. Otherwise we delete the old record and create the new one
+			if canUpdateDNSEntry(mod.Desired, mod.Existing) {
+				corrections = append(corrections, &models.Correction{
+					Msg: mod.String(),
+					F:   func() error { return n.domains.UpdateDNSEntry(dc.Name, targetEntry) },
+				})
+			} else {
+				oldEntry, err := recordToNative(mod.Existing)
+				if err != nil {
+					return nil, err
+				}
+
+				corrections = append(corrections,
+					&models.Correction{
+						Msg: mod.String() + "[1/2]",
+						F:   func() error { return n.domains.RemoveDNSEntry(dc.Name, oldEntry) },
+					},
+					&models.Correction{
+						Msg: mod.String() + "[2/2]",
+						F:   func() error { return n.domains.AddDNSEntry(dc.Name, targetEntry) },
+					},
+				)
+			}
+
 		}
 
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 
diff --git a/providers/vultr/vultrProvider.go b/providers/vultr/vultrProvider.go
index a72e4c7f..93b8ff37 100644
--- a/providers/vultr/vultrProvider.go
+++ b/providers/vultr/vultrProvider.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/diff"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/providers"
 	"github.com/vultr/govultr/v2"
 )
@@ -117,45 +118,51 @@ func (api *vultrProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*mode
 
 	models.PostProcessRecords(curRecords)
 
-	differ := diff.New(dc)
-	_, create, delete, modify, err := differ.IncrementalDiff(curRecords)
-	if err != nil {
-		return nil, err
-	}
-
 	var corrections []*models.Correction
+	if !diff2.EnableDiff2 || true { // Remove "|| true" when diff2 version arrives
 
-	for _, mod := range delete {
-		id := mod.Existing.Original.(govultr.DomainRecord).ID
-		corrections = append(corrections, &models.Correction{
-			Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), id),
-			F: func() error {
-				return api.client.DomainRecord.Delete(context.Background(), dc.Name, id)
-			},
-		})
-	}
+		differ := diff.New(dc)
+		_, create, delete, modify, err := differ.IncrementalDiff(curRecords)
+		if err != nil {
+			return nil, err
+		}
 
-	for _, mod := range create {
-		r := toVultrRecord(dc, mod.Desired, "0")
-		corrections = append(corrections, &models.Correction{
-			Msg: mod.String(),
-			F: func() error {
-				_, err := api.client.DomainRecord.Create(context.Background(), dc.Name, &govultr.DomainRecordReq{Name: r.Name, Type: r.Type, Data: r.Data, TTL: r.TTL, Priority: &r.Priority})
-				return err
-			},
-		})
-	}
+		for _, mod := range delete {
+			id := mod.Existing.Original.(govultr.DomainRecord).ID
+			corrections = append(corrections, &models.Correction{
+				Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), id),
+				F: func() error {
+					return api.client.DomainRecord.Delete(context.Background(), dc.Name, id)
+				},
+			})
+		}
+
+		for _, mod := range create {
+			r := toVultrRecord(dc, mod.Desired, "0")
+			corrections = append(corrections, &models.Correction{
+				Msg: mod.String(),
+				F: func() error {
+					_, err := api.client.DomainRecord.Create(context.Background(), dc.Name, &govultr.DomainRecordReq{Name: r.Name, Type: r.Type, Data: r.Data, TTL: r.TTL, Priority: &r.Priority})
+					return err
+				},
+			})
+		}
 
-	for _, mod := range modify {
-		r := toVultrRecord(dc, mod.Desired, mod.Existing.Original.(govultr.DomainRecord).ID)
-		corrections = append(corrections, &models.Correction{
-			Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), r.ID),
-			F: func() error {
-				return api.client.DomainRecord.Update(context.Background(), dc.Name, r.ID, &govultr.DomainRecordReq{Name: r.Name, Type: r.Type, Data: r.Data, TTL: r.TTL, Priority: &r.Priority})
-			},
-		})
+		for _, mod := range modify {
+			r := toVultrRecord(dc, mod.Desired, mod.Existing.Original.(govultr.DomainRecord).ID)
+			corrections = append(corrections, &models.Correction{
+				Msg: fmt.Sprintf("%s; Vultr RecordID: %v", mod.String(), r.ID),
+				F: func() error {
+					return api.client.DomainRecord.Update(context.Background(), dc.Name, r.ID, &govultr.DomainRecordReq{Name: r.Name, Type: r.Type, Data: r.Data, TTL: r.TTL, Priority: &r.Priority})
+				},
+			})
+		}
+
+		return corrections, nil
 	}
 
+	// Insert Future diff2 version here.
+
 	return corrections, nil
 }
 


Test patch:
diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go
index 8998529f..2f56ac0f 100644
--- a/integrationTest/integration_test.go
+++ b/integrationTest/integration_test.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/StackExchange/dnscontrol/v3/models"
 	"github.com/StackExchange/dnscontrol/v3/pkg/credsfile"
+	"github.com/StackExchange/dnscontrol/v3/pkg/diff2"
 	"github.com/StackExchange/dnscontrol/v3/pkg/nameservers"
 	"github.com/StackExchange/dnscontrol/v3/pkg/normalize"
 	"github.com/StackExchange/dnscontrol/v3/providers"
@@ -29,6 +30,8 @@ var enableCFWorkers = flag.Bool("cfworkers", true, "Set false to disable CF work
 
 func init() {
 	testing.Init()
+
+	flag.BoolVar(&diff2.EnableDiff2, "diff2", false, "enable diff2")
 	flag.Parse()
 }