Skip to content

Commit c726afe

Browse files
committed
sstable: add blob value liveness writer random test
Add a randomized test of blob value liveness writer.
1 parent c0d37a5 commit c726afe

File tree

2 files changed

+76
-11
lines changed

2 files changed

+76
-11
lines changed

sstable/blob_reference_index.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,10 @@ func (s *blobRefValueLivenessState) finishCurrentBlock() {
4646
}
4747

4848
// blobRefValueLivenessWriter helps maintain the liveness of values in blob value
49-
// blocks for a sstable's blob references. It maintains:
50-
// - bufs: serialized value liveness encodings that will be written to the
51-
// sstable.
52-
// - refState: a slice of blobRefValueLivenessState. This tracks the
53-
// in-progress value liveness for each blob value block for our sstable's
54-
// blob references. The index of the slice corresponds to the blob.ReferenceID.
49+
// blocks for a sstable's blob references. It maintains a refState, a slice of
50+
// blobRefValueLivenessState. This tracks the in-progress value liveness for
51+
// each blob value block for our sstable's blob references. The index of the
52+
// slice corresponds to the blob.ReferenceID.
5553
type blobRefValueLivenessWriter struct {
5654
refState []blobRefValueLivenessState
5755
}
@@ -70,14 +68,11 @@ func (w *blobRefValueLivenessWriter) numReferences() int {
7068

7169
// addLiveValue adds a live value to the state maintained by refID. If the
7270
// current blockID for this in-progress state is different from the provided
73-
// blockID, a new state is created and the old one is preserved to the buffer
74-
// at w.bufs[refID].
71+
// blockID, a new state is created.
7572
//
7673
// addLiveValue adds a new state for the provided refID if one does
7774
// not already exist. It assumes that any new blob.ReferenceIDs are visited in
7875
// monotonically increasing order.
79-
//
80-
// INVARIANT: len(w.refState) == len(w.bufs).
8176
func (w *blobRefValueLivenessWriter) addLiveValue(
8277
refID blob.ReferenceID, blockID blob.BlockID, valueID blob.BlockValueID, valueSize uint64,
8378
) error {
@@ -137,7 +132,8 @@ type BlobRefLivenessEncoding struct {
137132

138133
// DecodeBlobRefLivenessEncoding decodes a sequence of blob reference liveness
139134
// encodings from the provided buffer. Each encoding has the format:
140-
// <block ID> <values size> <n bytes of bitmap> [<bitmap>]
135+
//
136+
// <block ID> <values size> <len of bitmap> [<bitmap>]
141137
func DecodeBlobRefLivenessEncoding(buf []byte) []BlobRefLivenessEncoding {
142138
var encodings []BlobRefLivenessEncoding
143139
for len(buf) > 0 {

sstable/blob_reference_index_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
package sstable
66

77
import (
8+
"iter"
89
"maps"
10+
"math/rand/v2"
911
"testing"
12+
"time"
1013

14+
"github.com/cockroachdb/pebble/internal/testutils"
1115
"github.com/cockroachdb/pebble/sstable/blob"
1216
"github.com/stretchr/testify/require"
1317
)
@@ -77,3 +81,68 @@ func TestBlobRefValueLivenessWriter(t *testing.T) {
7781
require.Equal(t, uint8(0x7), blocks[0].Bitmap[0])
7882
})
7983
}
84+
85+
func TestBlobRefLivenessEncoding_Randomized(t *testing.T) {
86+
const valueSize = uint64(10)
87+
prng := rand.New(rand.NewPCG(uint64(time.Now().UnixNano()), 0))
88+
w := blobRefValueLivenessWriter{}
89+
90+
collectSlice := func(i iter.Seq2[blob.ReferenceID, []byte]) [][]byte {
91+
var s [][]byte
92+
for _, enc := range i {
93+
s = append(s, enc)
94+
}
95+
return s
96+
}
97+
98+
for range 20 {
99+
w.init()
100+
numRefs := testutils.RandIntInRange(prng, 1, 10)
101+
for refID := range numRefs {
102+
numBlocks := testutils.RandIntInRange(prng, 1, 6)
103+
currentBlockID := blob.BlockID(testutils.RandIntInRange(prng, 0, 4))
104+
105+
for range numBlocks {
106+
// Generate blockIDs that are increasing -- with occasional
107+
// duplicates. Allow a 70% chance to repeat the previous block
108+
// ID.
109+
if prng.Float64() < 0.3 {
110+
currentBlockID += blob.BlockID(testutils.RandIntInRange(prng, 1, 4))
111+
}
112+
numValues := testutils.RandIntInRange(prng, 10, 101)
113+
currentValueID := blob.BlockValueID(testutils.RandIntInRange(prng, 0, 4))
114+
for range numValues {
115+
require.NoError(t, w.addLiveValue(
116+
blob.ReferenceID(refID),
117+
currentBlockID,
118+
currentValueID,
119+
valueSize,
120+
))
121+
currentValueID += blob.BlockValueID(testutils.RandIntInRange(prng, 1, 4))
122+
}
123+
}
124+
}
125+
encoded := collectSlice(w.finish())
126+
127+
// Test the encoding/decoding roundtrip to ensure idempotence.
128+
// Reinitialize the writer before reconstructing values.
129+
w.init()
130+
for refID, blockEnc := range encoded {
131+
for _, block := range DecodeBlobRefLivenessEncoding(blockEnc) {
132+
// Reconstruct the live values from the bitmap and add them to
133+
// the writer.
134+
for valueID := range IterSetBitsInRunLengthBitmap(block.Bitmap) {
135+
require.NoError(t, w.addLiveValue(
136+
blob.ReferenceID(refID),
137+
block.BlockID,
138+
blob.BlockValueID(valueID),
139+
valueSize,
140+
))
141+
}
142+
}
143+
}
144+
145+
reencoded := collectSlice(w.finish())
146+
require.Equal(t, encoded, reencoded)
147+
}
148+
}

0 commit comments

Comments
 (0)