unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
backwards-compatibilityrefactoring

Stop generating v2 snapshot files, nor loading v2 snapshot files

Project: etcd-io/etcd | Language: Go | Concepts: backwards-compatibility, refactoring | Difficulty: mid
Link: https://github.com/etcd-io/etcd/pull/22263

The problem

etcd previously supported generating and loading v2 snapshot files for backward compatibility with older cluster versions. With v2 features deprecated and v2 store support removed, continuing to generate or load these snapshot files is unnecessary and adds maintenance burden.

Why it's non-trivial

A naive removal could break downgrade/upgrade scenarios or tests that still expect v2 snapshot files. The codebase has many places where v2 snapshot logic is intertwined with v3 logic, including test utilities, snapshotting, and recovery. Careful changes are needed to ensure no accidental breakage and that only v2-specific logic is removed.

The approach

This PR removes all code related to generating or loading v2 snapshot files, both in production code and tests. It ensures only v3 snapshot mechanisms remain.

1. Remove v2 snapshot file creation and loading in server logic:

-	d := GetMembershipInfoInV2Format(lg, s.cluster)
...
-	// For backward compatibility, generate v2 snapshot from v3 state.
-	snap, err := s.r.raftStorage.CreateSnapshot(ep.appliedi, ep.confState, d)
+	snap, err := s.r.raftStorage.CreateSnapshot(ep.appliedi, ep.confState, nil)

Removes the call to generate v2 membership info for the snapshot, passing nil instead. This means no v2 data is included in the snapshot.

2. Remove v2 snapshot file saving from storage:

-	// save the snapshot file before writing the snapshot to the wal.
-	// This makes it possible for the snapshot file to become orphaned, but prevents
-	// a WAL snapshot entry from having no corresponding snapshot file.
-	err := st.s.SaveSnap(snap)
-	if err != nil {
-		return err
-	}
-	// gofail: var raftBeforeWALSaveSnaphot struct{}

Deletes the call to actually write a v2 snapshot file to disk, so only the WAL snapshot entry is saved.

3. Remove v2 snapshot loading and verification from tests:

-		ss := snap.New(epc.Cfg.Logger, datadir.ToSnapDir(epc.Procs[i].Config().DataDirPath))
-		_, err := ss.Load()
+		walSnaps, err := wal.ValidSnapshotEntries(epc.Cfg.Logger, datadir.ToWALDir(epc.Procs[i].Config().DataDirPath))
 		require.NoError(t, err)
+		require.GreaterOrEqual(t, len(walSnaps), 1)

Test code now only checks for valid WAL snapshot entries, not v2 snapshot files.

4. Remove v2 snapshot test helpers and code:

-// TestV2DeprecationSnapshotMatches ensures that etcd v3.7 still commits v2 store
-// changes to the snapshot for backwards compatibility.
-func TestV2DeprecationSnapshotMatches(t *testing.T) {
-	...
-}

Deletes entire test functions that checked for v2 snapshot file compatibility.

5. Remove v2 snapshot generation in etcdutl and server bootstrapping:

-	raftSnap := raftpb.Snapshot{
-		Data: etcdserver.GetMembershipInfoInV2Format(s.lg, s.cl),
-		Metadata: &raftpb.SnapshotMetadata{
-			Index:     new(commit),
-			Term:      new(term),
-			ConfState: confState,
-		},
-	}
-	sn := snap.New(s.lg, s.snapDir)
-	if err := sn.SaveSnap(&raftSnap); err != nil {
-		return nil, err
-	}

Removes code that would generate and save a v2-format snapshot during WAL and snapshot creation.

From the review thread

serathius: What kind of tests we have to ensure downgrade works? IIRC v3.8 will not generate snapshots, v3.7 generates but not need them, but v3.6 will still need them.

Do we care about users going down from v3.8 to v3.6 via etcdutl? Do we have any safeguards around this?

ahrtr: We should be good as long as the existing e2e test cases (including downgrade test cases) are green.

We only support two major/minor releases, when v3.8 is out, v3.6 should be almost end of support. In practice, we don't expect users downgrade from v3.8 to v3.6. ... For the etcdutl case, we can document that it only support v3.8 and v3.7, or document that we only support upgrade or downgrade one version at a time. If users want to continue to downgrade (not recommended, or not even supported if v3.6 if EOL) when they are already on v3.7, then they should use the the same version of etcdutl (v3.7)

Takeaways

  • Remove deprecated compatibility logic only after verifying test coverage for all supported upgrade/downgrade paths.
  • When deprecating a data format, update both production and test code to eliminate all dependencies on the old format.
  • Document or enforce version compatibility boundaries when removing backward-compatibility code, especially in tools used for migration or recovery.