-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
etcdserver: compact the raft log up to the minimum snapshot index of …
…all ongoing snapshots Signed-off-by: Clement <[email protected]>
- Loading branch information
1 parent
641eb41
commit 93d0484
Showing
4 changed files
with
223 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package etcdserver | ||
|
||
import ( | ||
"cmp" | ||
"container/heap" | ||
"errors" | ||
"sync" | ||
) | ||
|
||
// SnapshotTracker keeps track of all ongoing snapshot creation. To safeguard ongoing snapshot creation, | ||
// only compact the raft log up to the minimum snapshot index in the track. | ||
type SnapshotTracker struct { | ||
h minHeap[uint64] | ||
mu sync.Mutex | ||
} | ||
|
||
// MinSnapi returns the minimum snapshot index in the track or an error if the tracker is empty. | ||
func (st *SnapshotTracker) MinSnapi() (uint64, error) { | ||
st.mu.Lock() | ||
defer st.mu.Unlock() | ||
if st.h.Len() == 0 { | ||
return 0, errors.New("SnapshotTracker is empty") | ||
} | ||
return st.h[0], nil | ||
} | ||
|
||
// Track adds a snapi to the tracker. Make sure to call UnTrack once the snapshot has been created. | ||
func (st *SnapshotTracker) Track(snapi uint64) { | ||
st.mu.Lock() | ||
defer st.mu.Unlock() | ||
heap.Push(&st.h, snapi) | ||
} | ||
|
||
// UnTrack removes 'snapi' from the tracker. No action taken if 'snapi' is not found. | ||
func (st *SnapshotTracker) UnTrack(snapi uint64) { | ||
st.mu.Lock() | ||
defer st.mu.Unlock() | ||
|
||
for i := 0; i < len((*st).h); i++ { | ||
if (*st).h[i] == snapi { | ||
heap.Remove(&st.h, i) | ||
return | ||
} | ||
} | ||
} | ||
|
||
// minHeap implements the heap.Interface for E. | ||
type minHeap[E interface { | ||
cmp.Ordered | ||
}] []E | ||
|
||
func (h minHeap[_]) Len() int { | ||
return len(h) | ||
} | ||
|
||
func (h minHeap[_]) Less(i, j int) bool { | ||
return h[i] < h[j] | ||
} | ||
|
||
func (h minHeap[_]) Swap(i, j int) { | ||
h[i], h[j] = h[j], h[i] | ||
} | ||
|
||
func (h *minHeap[E]) Push(x any) { | ||
*h = append(*h, x.(E)) | ||
} | ||
|
||
func (h *minHeap[E]) Pop() any { | ||
old := *h | ||
n := len(old) | ||
x := old[n-1] | ||
*h = old[0 : n-1] | ||
return x | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
package etcdserver | ||
|
||
import ( | ||
"container/heap" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
func TestSnapTracker_MinSnapi(t *testing.T) { | ||
st := SnapshotTracker{} | ||
|
||
_, err := st.MinSnapi() | ||
assert.NotNil(t, err, "SnapshotTracker should be empty initially") | ||
|
||
st.Track(10) | ||
minSnapi, err := st.MinSnapi() | ||
assert.Nil(t, err) | ||
assert.Equal(t, uint64(10), minSnapi, "MinSnapi should return the only tracked snapshot index") | ||
|
||
st.Track(5) | ||
minSnapi, err = st.MinSnapi() | ||
assert.Nil(t, err) | ||
assert.Equal(t, uint64(5), minSnapi, "MinSnapi should return the minimum tracked snapshot index") | ||
|
||
st.UnTrack(5) | ||
minSnapi, err = st.MinSnapi() | ||
assert.Nil(t, err) | ||
assert.Equal(t, uint64(10), minSnapi, "MinSnapi should return the remaining tracked snapshot index") | ||
} | ||
|
||
func TestSnapTracker_Track(t *testing.T) { | ||
st := SnapshotTracker{} | ||
st.Track(20) | ||
st.Track(10) | ||
st.Track(15) | ||
|
||
assert.Equal(t, 3, st.h.Len(), "SnapshotTracker should have 3 snapshots tracked") | ||
|
||
minSnapi, err := st.MinSnapi() | ||
assert.Nil(t, err) | ||
assert.Equal(t, uint64(10), minSnapi, "MinSnapi should return the minimum tracked snapshot index") | ||
} | ||
|
||
func TestSnapTracker_UnTrack(t *testing.T) { | ||
st := SnapshotTracker{} | ||
st.Track(20) | ||
st.Track(30) | ||
st.Track(40) | ||
// track another snapshot with the same index | ||
st.Track(20) | ||
|
||
st.UnTrack(30) | ||
assert.Equal(t, 3, st.h.Len()) | ||
|
||
minSnapi, err := st.MinSnapi() | ||
assert.Nil(t, err) | ||
assert.Equal(t, uint64(20), minSnapi) | ||
|
||
st.UnTrack(20) | ||
assert.Equal(t, 2, st.h.Len()) | ||
|
||
minSnapi, err = st.MinSnapi() | ||
assert.Nil(t, err) | ||
assert.Equal(t, uint64(20), minSnapi) | ||
|
||
st.UnTrack(20) | ||
minSnapi, err = st.MinSnapi() | ||
assert.Equal(t, uint64(40), minSnapi) | ||
|
||
st.UnTrack(40) | ||
_, err = st.MinSnapi() | ||
assert.NotNil(t, err) | ||
} | ||
|
||
func newMinHeap(elements ...uint64) minHeap[uint64] { | ||
h := minHeap[uint64](elements) | ||
heap.Init(&h) | ||
return h | ||
} | ||
|
||
func TestMinHeapLen(t *testing.T) { | ||
h := newMinHeap(3, 2, 1) | ||
assert.Equal(t, 3, h.Len()) | ||
} | ||
|
||
func TestMinHeapLess(t *testing.T) { | ||
h := newMinHeap(3, 2, 1) | ||
assert.True(t, h.Less(0, 1)) | ||
} | ||
|
||
func TestMinHeapSwap(t *testing.T) { | ||
h := newMinHeap(3, 2, 1) | ||
h.Swap(0, 1) | ||
assert.Equal(t, uint64(2), h[0]) | ||
assert.Equal(t, uint64(1), h[1]) | ||
assert.Equal(t, uint64(3), h[2]) | ||
} | ||
|
||
func TestMinHeapPushPop(t *testing.T) { | ||
h := newMinHeap(3, 2) | ||
heap.Push(&h, uint64(1)) | ||
assert.Equal(t, 3, h.Len()) | ||
|
||
got := heap.Pop(&h).(uint64) | ||
assert.Equal(t, uint64(1), got) | ||
} | ||
|
||
func TestMinHeapEmpty(t *testing.T) { | ||
h := minHeap[uint64]{} | ||
assert.Equal(t, 0, h.Len()) | ||
} | ||
|
||
func TestMinHeapSingleElement(t *testing.T) { | ||
h := newMinHeap(uint64(1)) | ||
assert.Equal(t, 1, h.Len()) | ||
|
||
heap.Push(&h, uint64(2)) | ||
assert.Equal(t, 2, h.Len()) | ||
|
||
got := heap.Pop(&h) | ||
assert.Equal(t, uint64(1), got) | ||
} |