Skip to content

Commit 258531b

Browse files
committed
blob: add FileFormatV2 which has a properties block
We extend the blob file format by adding a new V2 footer (and associated magic signature) which encodes a properties block handle (and a metaindex block handle just in case we need one in the future). The properties block contains a key/value for compression statistics.
1 parent 4be89e7 commit 258531b

21 files changed

+633
-339
lines changed

blob_rewrite.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ func (d *DB) runBlobFileRewriteLocked(
291291
env,
292292
objMeta.DiskFileNum,
293293
writable,
294-
d.opts.MakeBlobWriterOptions(6),
294+
d.opts.MakeBlobWriterOptions(6, d.BlobFileFormat()),
295295
c.referencingTables,
296296
c.input,
297297
)

data_test.go

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,12 +1163,18 @@ func runDBDefineCmdReuseFS(td *datadriven.TestData, opts *Options) (*DB, error)
11631163
return nil, err
11641164
}
11651165

1166-
if len(ve.NewTables) > 0 {
1167-
// Collect any blob files created.
1168-
fileStats, err := valueSeparator.bv.WriteFiles(func(fileNum base.DiskFileNum) (objstorage.Writable, error) {
1166+
if len(ve.NewTables) == 0 {
1167+
return d, nil
1168+
}
1169+
1170+
// Collect any blob files created.
1171+
if !valueSeparator.bv.IsEmpty() {
1172+
newBlobObject := func(fileNum base.DiskFileNum) (objstorage.Writable, error) {
11691173
writable, _, err := d.objProvider.Create(context.Background(), base.FileTypeBlob, fileNum, objstorage.CreateOptions{})
11701174
return writable, err
1171-
}, d.opts.MakeBlobWriterOptions(0))
1175+
}
1176+
blobWriterOpts := d.opts.MakeBlobWriterOptions(0, d.BlobFileFormat())
1177+
fileStats, err := valueSeparator.bv.WriteFiles(newBlobObject, blobWriterOpts)
11721178
if err != nil {
11731179
return nil, err
11741180
}
@@ -1184,22 +1190,22 @@ func runDBDefineCmdReuseFS(td *datadriven.TestData, opts *Options) (*DB, error)
11841190
Physical: m,
11851191
})
11861192
}
1193+
}
11871194

1188-
jobID := d.newJobIDLocked()
1189-
_, err = d.mu.versions.UpdateVersionLocked(func() (versionUpdate, error) {
1190-
return versionUpdate{
1191-
VE: ve,
1192-
JobID: jobID,
1193-
Metrics: newFileMetrics(ve.NewTables),
1194-
InProgressCompactionsFn: func() []compactionInfo { return nil },
1195-
}, nil
1196-
})
1197-
if err != nil {
1198-
return nil, err
1199-
}
1200-
d.updateReadStateLocked(nil)
1201-
d.updateTableStatsLocked(ve.NewTables)
1195+
jobID := d.newJobIDLocked()
1196+
_, err = d.mu.versions.UpdateVersionLocked(func() (versionUpdate, error) {
1197+
return versionUpdate{
1198+
VE: ve,
1199+
JobID: jobID,
1200+
Metrics: newFileMetrics(ve.NewTables),
1201+
InProgressCompactionsFn: func() []compactionInfo { return nil },
1202+
}, nil
1203+
})
1204+
if err != nil {
1205+
return nil, err
12021206
}
1207+
d.updateReadStateLocked(nil)
1208+
d.updateTableStatsLocked(ve.NewTables)
12031209

12041210
return d, nil
12051211
}

format_major_version.go

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/cockroachdb/pebble/internal/manifest"
1313
"github.com/cockroachdb/pebble/objstorage/remote"
1414
"github.com/cockroachdb/pebble/sstable"
15+
"github.com/cockroachdb/pebble/sstable/blob"
1516
"github.com/cockroachdb/pebble/vfs"
1617
"github.com/cockroachdb/pebble/vfs/atomicfs"
1718
)
@@ -234,11 +235,16 @@ const (
234235
// participate in every compaction.
235236
FormatValueSeparation
236237

237-
// -- Add new versions here --
238238
// FormatExciseBoundsRecord is a format major version that adds support for
239239
// persisting excise bounds records in the manifest (VersionEdit).
240240
FormatExciseBoundsRecord
241241

242+
// FormatV2BlobFiles is a format major version that adds support for V2 blob
243+
// file format (which adds compression statistics).
244+
FormatV2BlobFiles
245+
246+
// -- Add new versions here --
247+
242248
// FormatNewest is the most recent format major version.
243249
FormatNewest FormatMajorVersion = iota - 1
244250

@@ -260,44 +266,55 @@ const FormatMinSupported = FormatFlushableIngest
260266
// objects (see CreateOnShared option).
261267
const FormatMinForSharedObjects = FormatVirtualSSTables
262268

263-
// IsSupported returns true if the version is supported by the current Pebble
264-
// version.
265-
func (v FormatMajorVersion) IsSupported() bool {
266-
return v == FormatDefault && v >= FormatMinSupported && v <= internalFormatNewest
269+
// resolveDefault asserts that the given version is supported, and returns the
270+
// given version, replacing FormatDefault with FormatMinSupported.
271+
func (v FormatMajorVersion) resolveDefault() FormatMajorVersion {
272+
if v == FormatDefault {
273+
return FormatMinSupported
274+
}
275+
if v < FormatMinSupported || v > internalFormatNewest {
276+
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
277+
}
278+
return v
267279
}
268280

269281
// MaxTableFormat returns the maximum sstable.TableFormat that can be used at
270282
// this FormatMajorVersion.
271283
func (v FormatMajorVersion) MaxTableFormat() sstable.TableFormat {
272-
switch v {
273-
case FormatDefault, FormatFlushableIngest, FormatPrePebblev1MarkedCompacted:
274-
return sstable.TableFormatPebblev3
275-
case FormatDeleteSizedAndObsolete, FormatVirtualSSTables, FormatSyntheticPrefixSuffix,
276-
FormatFlushableIngestExcises:
277-
return sstable.TableFormatPebblev4
278-
case FormatColumnarBlocks, FormatWALSyncChunks:
279-
return sstable.TableFormatPebblev5
280-
case FormatTableFormatV6, formatDeprecatedExperimentalValueSeparation:
281-
return sstable.TableFormatPebblev6
282-
case formatFooterAttributes, FormatValueSeparation, FormatExciseBoundsRecord:
284+
v = v.resolveDefault()
285+
switch {
286+
case v >= formatFooterAttributes:
283287
return sstable.TableFormatPebblev7
288+
case v >= FormatTableFormatV6:
289+
return sstable.TableFormatPebblev6
290+
case v >= FormatColumnarBlocks:
291+
return sstable.TableFormatPebblev5
292+
case v >= FormatDeleteSizedAndObsolete:
293+
return sstable.TableFormatPebblev4
284294
default:
285-
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
295+
return sstable.TableFormatPebblev3
286296
}
287297
}
288298

289299
// MinTableFormat returns the minimum sstable.TableFormat that can be used at
290300
// this FormatMajorVersion.
291301
func (v FormatMajorVersion) MinTableFormat() sstable.TableFormat {
292-
switch v {
293-
case FormatDefault, FormatFlushableIngest, FormatPrePebblev1MarkedCompacted,
294-
FormatDeleteSizedAndObsolete, FormatVirtualSSTables, FormatSyntheticPrefixSuffix,
295-
FormatFlushableIngestExcises, FormatColumnarBlocks, FormatWALSyncChunks,
296-
FormatExciseBoundsRecord, FormatTableFormatV6, formatDeprecatedExperimentalValueSeparation, formatFooterAttributes,
297-
FormatValueSeparation:
298-
return sstable.TableFormatPebblev1
302+
_ = v.resolveDefault()
303+
return sstable.TableFormatPebblev1
304+
}
305+
306+
// MaxBlobFileFormat returns the maximum blob.FileFormat that can be used at
307+
// this FormatMajorVersion. It can only be used on versions that support value
308+
// separation.
309+
func (v FormatMajorVersion) MaxBlobFileFormat() blob.FileFormat {
310+
v = v.resolveDefault()
311+
switch {
312+
case v >= FormatV2BlobFiles:
313+
return blob.FileFormatV2
314+
case v >= FormatValueSeparation:
315+
return blob.FileFormatV1
299316
default:
300-
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
317+
panic(fmt.Sprintf("pebble: format major version %s does not support blob files", v))
301318
}
302319
}
303320

@@ -355,6 +372,9 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
355372
FormatExciseBoundsRecord: func(d *DB) error {
356373
return d.finalizeFormatVersUpgrade(FormatExciseBoundsRecord)
357374
},
375+
FormatV2BlobFiles: func(d *DB) error {
376+
return d.finalizeFormatVersUpgrade(FormatV2BlobFiles)
377+
},
358378
}
359379

360380
const formatVersionMarkerName = `format-version`
@@ -426,6 +446,12 @@ func (d *DB) TableFormat() sstable.TableFormat {
426446
return f
427447
}
428448

449+
// BlobFileFormat returns the blob.FileFormat that the database is currently
450+
// using when writing blob files.
451+
func (d *DB) BlobFileFormat() blob.FileFormat {
452+
return d.FormatMajorVersion().MaxBlobFileFormat()
453+
}
454+
429455
// shouldCreateShared returns true if the database should use shared objects
430456
// when creating new objects on the given level.
431457
func (d *DB) shouldCreateShared(targetLevel int) bool {

format_major_version_test.go

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111

1212
"github.com/cockroachdb/pebble/sstable"
13+
"github.com/cockroachdb/pebble/sstable/blob"
1314
"github.com/cockroachdb/pebble/vfs"
1415
"github.com/cockroachdb/pebble/vfs/atomicfs"
1516
"github.com/stretchr/testify/require"
@@ -32,12 +33,13 @@ func TestFormatMajorVersionStableValues(t *testing.T) {
3233
require.Equal(t, formatDeprecatedExperimentalValueSeparation, FormatMajorVersion(22))
3334
require.Equal(t, formatFooterAttributes, FormatMajorVersion(23))
3435
require.Equal(t, FormatValueSeparation, FormatMajorVersion(24))
35-
36-
// When we add a new version, we should add a check for the new version in
37-
// addition to updating these expected values.
38-
require.Equal(t, FormatNewest, FormatMajorVersion(25))
39-
require.Equal(t, internalFormatNewest, FormatMajorVersion(25))
4036
require.Equal(t, FormatExciseBoundsRecord, FormatMajorVersion(25))
37+
require.Equal(t, FormatV2BlobFiles, FormatMajorVersion(26))
38+
39+
// When we add a new version, we should add a check for the new version above
40+
// in addition to updating the expected values below.
41+
require.Equal(t, FormatNewest, FormatMajorVersion(26))
42+
require.Equal(t, internalFormatNewest, FormatMajorVersion(26))
4143
}
4244

4345
func TestFormatMajorVersion_MigrationDefined(t *testing.T) {
@@ -55,36 +57,18 @@ func TestRatchetFormat(t *testing.T) {
5557
d, err := Open("", opts)
5658
require.NoError(t, err)
5759
require.NoError(t, d.Set([]byte("foo"), []byte("bar"), Sync))
58-
require.Equal(t, FormatFlushableIngest, d.FormatMajorVersion())
59-
require.NoError(t, d.RatchetFormatMajorVersion(FormatPrePebblev1MarkedCompacted))
60-
require.Equal(t, FormatPrePebblev1MarkedCompacted, d.FormatMajorVersion())
61-
require.NoError(t, d.RatchetFormatMajorVersion(FormatDeleteSizedAndObsolete))
62-
require.Equal(t, FormatDeleteSizedAndObsolete, d.FormatMajorVersion())
63-
require.NoError(t, d.RatchetFormatMajorVersion(FormatVirtualSSTables))
64-
require.Equal(t, FormatVirtualSSTables, d.FormatMajorVersion())
65-
require.NoError(t, d.RatchetFormatMajorVersion(FormatSyntheticPrefixSuffix))
66-
require.Equal(t, FormatSyntheticPrefixSuffix, d.FormatMajorVersion())
67-
require.NoError(t, d.RatchetFormatMajorVersion(FormatFlushableIngestExcises))
68-
require.Equal(t, FormatFlushableIngestExcises, d.FormatMajorVersion())
69-
require.NoError(t, d.RatchetFormatMajorVersion(FormatColumnarBlocks))
70-
require.Equal(t, FormatColumnarBlocks, d.FormatMajorVersion())
71-
require.NoError(t, d.RatchetFormatMajorVersion(FormatWALSyncChunks))
72-
require.Equal(t, FormatWALSyncChunks, d.FormatMajorVersion())
73-
require.NoError(t, d.RatchetFormatMajorVersion(FormatTableFormatV6))
74-
require.Equal(t, FormatTableFormatV6, d.FormatMajorVersion())
75-
require.NoError(t, d.RatchetFormatMajorVersion(formatDeprecatedExperimentalValueSeparation))
76-
require.Equal(t, formatDeprecatedExperimentalValueSeparation, d.FormatMajorVersion())
77-
require.NoError(t, d.RatchetFormatMajorVersion(formatFooterAttributes))
78-
require.Equal(t, formatFooterAttributes, d.FormatMajorVersion())
79-
require.NoError(t, d.RatchetFormatMajorVersion(FormatValueSeparation))
80-
require.Equal(t, FormatValueSeparation, d.FormatMajorVersion())
81-
require.NoError(t, d.RatchetFormatMajorVersion(FormatExciseBoundsRecord))
82-
require.Equal(t, FormatExciseBoundsRecord, d.FormatMajorVersion())
60+
61+
// Ratchet through all supported versions.
62+
require.Equal(t, FormatMinSupported, d.FormatMajorVersion())
63+
for v := FormatMinSupported + 1; v <= internalFormatNewest; v++ {
64+
require.NoError(t, d.RatchetFormatMajorVersion(v))
65+
require.Equal(t, v, d.FormatMajorVersion())
66+
}
8367

8468
require.NoError(t, d.Close())
8569

8670
// If we Open the database again, leaving the default format, the
87-
// database should Open using the persisted FormatNewest.
71+
// database should Open using the persisted internalFormatNewest.
8872
opts = &Options{FS: fs, Logger: testLogger{t}}
8973
opts.WithFSDefaults()
9074
d, err = Open("", opts)
@@ -243,6 +227,7 @@ func TestFormatMajorVersions_TableFormat(t *testing.T) {
243227
formatFooterAttributes: {sstable.TableFormatPebblev1, sstable.TableFormatPebblev7},
244228
FormatValueSeparation: {sstable.TableFormatPebblev1, sstable.TableFormatPebblev7},
245229
FormatExciseBoundsRecord: {sstable.TableFormatPebblev1, sstable.TableFormatPebblev7},
230+
FormatV2BlobFiles: {sstable.TableFormatPebblev1, sstable.TableFormatPebblev7},
246231
}
247232

248233
// Valid versions.
@@ -258,6 +243,29 @@ func TestFormatMajorVersions_TableFormat(t *testing.T) {
258243
require.Panics(t, func() { _ = fmv.MinTableFormat() })
259244
}
260245

246+
func TestFormatMajorVersions_BlobFileFormat(t *testing.T) {
247+
// NB: This test is intended to validate the mapping between every
248+
// FormatMajorVersion and blob.FileFormat exhaustively. This serves as a
249+
// sanity check that new versions have a corresponding mapping. The test
250+
// fixture is intentionally verbose.
251+
252+
m := map[FormatMajorVersion]blob.FileFormat{
253+
FormatValueSeparation: blob.FileFormatV1,
254+
FormatExciseBoundsRecord: blob.FileFormatV1,
255+
FormatV2BlobFiles: blob.FileFormatV2,
256+
}
257+
258+
// Valid versions.
259+
for fmv := FormatValueSeparation; fmv <= internalFormatNewest; fmv++ {
260+
got := fmv.MaxBlobFileFormat()
261+
require.Equalf(t, m[fmv], got, "got %s; want %s", got, m[fmv])
262+
}
263+
264+
// Invalid versions.
265+
require.Panics(t, func() { _ = (internalFormatNewest + 1).MaxBlobFileFormat() })
266+
require.Panics(t, func() { _ = (FormatValueSeparation - 1).MaxBlobFileFormat() })
267+
}
268+
261269
// TestFormatMajorVersions_ColumnarBlocks ensures that
262270
// Experimental.EnableColumnarBlocks is respected on recent format major
263271
// versions.

internal/blobtest/handles.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ func (bv *Values) ParseInlineHandle(
212212
return h, remaining, nil
213213
}
214214

215+
// IsEmpty returns true if the Values has no tracked handles.
216+
func (bv *Values) IsEmpty() bool {
217+
return len(bv.trackedHandles) == 0
218+
}
219+
215220
// WriteFiles writes all the blob files referenced by Values, using
216221
// newBlobObject to construct new objects.
217222
//

metrics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ func (m *Metrics) StringForTests() string {
10611061
// We recalculate the file cache size using the 64-bit sizes, and we ignore
10621062
// the genericcache metadata size which is harder to adjust.
10631063
const sstableReaderSize64bit = 280
1064-
const blobFileReaderSize64bit = 96
1064+
const blobFileReaderSize64bit = 112
10651065
mCopy.FileCache.Size = mCopy.FileCache.TableCount*sstableReaderSize64bit + mCopy.FileCache.BlobFileCount*blobFileReaderSize64bit
10661066
if math.MaxInt == math.MaxInt64 {
10671067
// Verify the 64-bit sizes, so they are kept updated.

open_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ func TestNewDBFilenames(t *testing.T) {
502502
"LOCK",
503503
"MANIFEST-000001",
504504
"OPTIONS-000003",
505-
"marker.format-version.000012.025",
505+
"marker.format-version.000013.026",
506506
"marker.manifest.000001.MANIFEST-000001",
507507
},
508508
}

options.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2543,9 +2543,10 @@ func (o *Options) MakeWriterOptions(level int, format sstable.TableFormat) sstab
25432543

25442544
// MakeBlobWriterOptions constructs blob.FileWriterOptions from the corresponding
25452545
// options in the receiver.
2546-
func (o *Options) MakeBlobWriterOptions(level int) blob.FileWriterOptions {
2546+
func (o *Options) MakeBlobWriterOptions(level int, format blob.FileFormat) blob.FileWriterOptions {
25472547
lo := o.Levels[level]
25482548
return blob.FileWriterOptions{
2549+
Format: format,
25492550
Compression: lo.Compression(),
25502551
ChecksumType: block.ChecksumTypeCRC32c,
25512552
FlushGovernor: block.MakeFlushGovernor(

0 commit comments

Comments
 (0)