Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/mock/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ gomock(
"AEAD",
"IntTreeDirectoryVisitor",
"ReadCloser",
"RecordArray",
"RecordKeyHasher",
"ResponseWriter",
"RoundTripper",
"ValueComparator",
"Writer",
],
library = "//internal/mock/aliases",
Expand Down Expand Up @@ -428,6 +431,7 @@ go_library(
"//pkg/digest",
"//pkg/filesystem",
"//pkg/filesystem/path",
"//pkg/lossymap",
"//pkg/program",
"//pkg/proto/blobstore/local",
"//pkg/proto/configuration/grpc",
Expand Down
5 changes: 4 additions & 1 deletion internal/mock/aliases/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ go_library(
srcs = ["aliases.go"],
importpath = "github.com/buildbarn/bb-storage/internal/mock/aliases",
visibility = ["//:__subpackages__"],
deps = ["//pkg/blobstore"],
deps = [
"//pkg/blobstore",
"//pkg/lossymap",
],
)
10 changes: 10 additions & 0 deletions internal/mock/aliases/aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"

"github.com/buildbarn/bb-storage/pkg/blobstore"
"github.com/buildbarn/bb-storage/pkg/lossymap"
)

// This file contains aliases for some of the interfaces provided by the
Expand All @@ -24,11 +25,20 @@ type IntTreeDirectoryVisitor = blobstore.TreeDirectoryVisitor[int]
// ReadCloser is an alias of io.ReadCloser.
type ReadCloser = io.ReadCloser

// RecordArray is declared for unit testing the hash map.
type RecordArray = lossymap.RecordArray[int, int, int]

// RecordKeyHasher is declared for unit testing the hash map.
type RecordKeyHasher = lossymap.RecordKeyHasher[int]

// ResponseWriter is an alias of http.ResponseWriter.
type ResponseWriter = http.ResponseWriter

// RoundTripper is an alias of http.RoundTripper.
type RoundTripper = http.RoundTripper

// ValueComparator is declared for unit testing the hash map.
type ValueComparator = lossymap.ValueComparator[int]

// Writer is an alias of io.Writer.
type Writer = io.Writer
1 change: 1 addition & 0 deletions pkg/blobstore/configuration/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ go_library(
"//pkg/filesystem/path",
"//pkg/grpc",
"//pkg/http/client",
"//pkg/lossymap",
"//pkg/program",
"//pkg/proto/configuration/blobstore",
"//pkg/proto/configuration/digest",
Expand Down
2 changes: 1 addition & 1 deletion pkg/blobstore/configuration/blob_access_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type BlobAccessCreator interface {
// NewHierarchicalInstanceNamesLocalBlobAccess() creates a
// BlobAccess suitable for storing data on the local system that
// uses hierarchical instance names.
NewHierarchicalInstanceNamesLocalBlobAccess(keyLocationMap local.KeyLocationMap, locationBlobMap local.LocationBlobMap, globalLock *sync.RWMutex) (blobstore.BlobAccess, error)
NewHierarchicalInstanceNamesLocalBlobAccess(keyLocationMap local.KeyLocationMap, blockReferenceResolver local.BlockReferenceResolver, locationBlobMap local.LocationBlobMap, globalLock *sync.RWMutex) (blobstore.BlobAccess, error)
// NewCustomBlobAccess() can be used as a fallback to create
// BlobAccess instances that only apply to this storage type.
// For example, CompletenessCheckingBlobAccess is only
Expand Down
4 changes: 2 additions & 2 deletions pkg/blobstore/configuration/cas_blob_access_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ func (casBlobAccessCreator) NewBlockListGrowthPolicy(currentBlocks, newBlocks in
return local.NewImmutableBlockListGrowthPolicy(currentBlocks, newBlocks), nil
}

func (casBlobAccessCreator) NewHierarchicalInstanceNamesLocalBlobAccess(keyLocationMap local.KeyLocationMap, locationBlobMap local.LocationBlobMap, globalLock *sync.RWMutex) (blobstore.BlobAccess, error) {
return local.NewHierarchicalCASBlobAccess(keyLocationMap, locationBlobMap, globalLock, casCapabilitiesProvider), nil
func (casBlobAccessCreator) NewHierarchicalInstanceNamesLocalBlobAccess(keyLocationMap local.KeyLocationMap, blockReferenceResolver local.BlockReferenceResolver, locationBlobMap local.LocationBlobMap, globalLock *sync.RWMutex) (blobstore.BlobAccess, error) {
return local.NewHierarchicalCASBlobAccess(keyLocationMap, blockReferenceResolver, locationBlobMap, globalLock, casCapabilitiesProvider), nil
}

func (bac *casBlobAccessCreator) NewCustomBlobAccess(terminationGroup program.Group, configuration *pb.BlobAccessConfiguration, nestedCreator NestedBlobAccessCreator) (BlobAccessInfo, string, error) {
Expand Down
55 changes: 40 additions & 15 deletions pkg/blobstore/configuration/new_blob_access.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/buildbarn/bb-storage/pkg/filesystem"
"github.com/buildbarn/bb-storage/pkg/filesystem/path"
"github.com/buildbarn/bb-storage/pkg/grpc"
"github.com/buildbarn/bb-storage/pkg/lossymap"
"github.com/buildbarn/bb-storage/pkg/program"
pb "github.com/buildbarn/bb-storage/pkg/proto/configuration/blobstore"
digest_pb "github.com/buildbarn/bb-storage/pkg/proto/configuration/digest"
Expand Down Expand Up @@ -307,15 +308,12 @@ func (nc *simpleNestedBlobAccessCreator) newNestedBlobAccessBare(configuration *
)

// Create the backing store for the key-location map.
var locationRecordArraySize int
var locationRecordArraySize uint64
var locationRecordArray local.LocationRecordArray
switch keyLocationMapBackend := backend.Local.KeyLocationMapBackend.(type) {
case *pb.LocalBlobAccessConfiguration_KeyLocationMapInMemory_:
locationRecordArraySize = int(keyLocationMapBackend.KeyLocationMapInMemory.Entries)
locationRecordArray = local.NewInMemoryLocationRecordArray(
locationRecordArraySize,
locationBlobMap,
)
locationRecordArraySize = keyLocationMapBackend.KeyLocationMapInMemory.Entries
locationRecordArray = local.NewInMemoryLocationRecordArray(locationRecordArraySize)
case *pb.LocalBlobAccessConfiguration_KeyLocationMapOnBlockDevice:
blockDevice, sectorSizeBytes, sectorCount, err := blockdevice.NewBlockDeviceFromConfiguration(
keyLocationMapBackend.KeyLocationMapOnBlockDevice,
Expand All @@ -324,11 +322,8 @@ func (nc *simpleNestedBlobAccessCreator) newNestedBlobAccessBare(configuration *
if err != nil {
return BlobAccessInfo{}, "", util.StatusWrap(err, "Failed to open key-location map block device")
}
locationRecordArraySize = int((int64(sectorSizeBytes) * sectorCount) / local.BlockDeviceBackedLocationRecordSize)
locationRecordArray = local.NewBlockDeviceBackedLocationRecordArray(
blockDevice,
locationBlobMap,
)
locationRecordArraySize = uint64((int64(sectorSizeBytes) * sectorCount) / local.BlockDeviceBackedLocationRecordSize)
locationRecordArray = local.NewBlockDeviceBackedLocationRecordArray(blockDevice)
default:
return BlobAccessInfo{}, "", status.Errorf(codes.InvalidArgument, "Key-location map backend not specified")
}
Expand All @@ -338,15 +333,43 @@ func (nc *simpleNestedBlobAccessCreator) newNestedBlobAccessBare(configuration *
// to store entries in the location record array, ensure
// that the size that is used is prime. This causes the
// best dispersion of hash table entries.
for locationRecordArraySize > 3 && !primes.IsPrime(locationRecordArraySize) {
for locationRecordArraySize > 3 && !primes.IsPrime(int(locationRecordArraySize)) {
locationRecordArraySize--
}

keyLocationMap := local.NewHashingKeyLocationMap(
keyLocationMap := lossymap.NewHashMap(
locationRecordArray,
func(k *lossymap.RecordKey[local.Key]) uint64 {
h := keyLocationMapHashInitialization
for _, c := range k.Key {
h ^= uint64(c)
h *= 1099511628211
}
attempt := k.Attempt
for i := 0; i < 4; i++ {
h ^= uint64(attempt & 0xff)
h *= 1099511628211
attempt >>= 8
}
return h
},
locationRecordArraySize,
keyLocationMapHashInitialization,
backend.Local.KeyLocationMapMaximumGetAttempts,
func(a, b *local.Location) int {
if a.BlockIndex < b.BlockIndex {
return -1
}
if a.BlockIndex > b.BlockIndex {
return 1
}
if a.OffsetBytes < b.OffsetBytes {
return -1
}
if a.OffsetBytes > b.OffsetBytes {
return 1
}
return 0
},
uint8(backend.Local.KeyLocationMapMaximumGetAttempts),
int(backend.Local.KeyLocationMapMaximumPutAttempts),
storageTypeName,
)
Expand All @@ -356,6 +379,7 @@ func (nc *simpleNestedBlobAccessCreator) newNestedBlobAccessBare(configuration *
localBlobAccess, err = creator.NewHierarchicalInstanceNamesLocalBlobAccess(
keyLocationMap,
locationBlobMap,
locationBlobMap,
&globalLock,
)
if err != nil {
Expand All @@ -365,6 +389,7 @@ func (nc *simpleNestedBlobAccessCreator) newNestedBlobAccessBare(configuration *
localBlobAccess = local.NewFlatBlobAccess(
keyLocationMap,
locationBlobMap,
locationBlobMap,
digestKeyFormat,
&globalLock,
storageTypeName,
Expand Down
2 changes: 1 addition & 1 deletion pkg/blobstore/configuration/proto_blob_access_creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (protoBlobAccessCreator) NewBlockListGrowthPolicy(currentBlocks, newBlocks
return local.NewMutableBlockListGrowthPolicy(currentBlocks), nil
}

func (protoBlobAccessCreator) NewHierarchicalInstanceNamesLocalBlobAccess(keyLocationMap local.KeyLocationMap, locationBlobMap local.LocationBlobMap, globalLock *sync.RWMutex) (blobstore.BlobAccess, error) {
func (protoBlobAccessCreator) NewHierarchicalInstanceNamesLocalBlobAccess(keyLocationMap local.KeyLocationMap, blockReferenceResolver local.BlockReferenceResolver, locationBlobMap local.LocationBlobMap, globalLock *sync.RWMutex) (blobstore.BlobAccess, error) {
return nil, status.Error(codes.InvalidArgument, "The hierarchical instance names option can only be used for the Content Addressable Storage")
}

Expand Down
7 changes: 2 additions & 5 deletions pkg/blobstore/local/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@ go_library(
"block_reference.go",
"directory_backed_persistent_state_store.go",
"flat_blob_access.go",
"hashing_key_location_map.go",
"hierarchical_cas_blob_access.go",
"in_memory_block_allocator.go",
"in_memory_location_record_array.go",
"key.go",
"key_location_map.go",
"location.go",
"location_blob_map.go",
"location_record_array.go",
"location_record_key.go",
"old_current_new_location_blob_map.go",
"periodic_syncer.go",
"persistent_block_list.go",
Expand All @@ -40,6 +37,7 @@ go_library(
"//pkg/digest",
"//pkg/filesystem",
"//pkg/filesystem/path",
"//pkg/lossymap",
"//pkg/proto/blobstore/local",
"//pkg/random",
"//pkg/util",
Expand All @@ -57,11 +55,9 @@ go_test(
"block_device_backed_location_record_array_test.go",
"directory_backed_persistent_state_store_test.go",
"flat_blob_access_test.go",
"hashing_key_location_map_test.go",
"hierarchical_cas_blob_access_test.go",
"in_memory_block_allocator_test.go",
"in_memory_location_record_array_test.go",
"location_record_key_test.go",
"old_current_new_location_blob_map_test.go",
"periodic_syncer_test.go",
"persistent_block_list_test.go",
Expand All @@ -76,6 +72,7 @@ go_test(
"//pkg/digest",
"//pkg/filesystem",
"//pkg/filesystem/path",
"//pkg/lossymap",
"//pkg/proto/blobstore/local",
"//pkg/testutil",
"@bazel_remote_apis//build/bazel/remote/execution/v2:remote_execution_go_proto",
Expand Down
36 changes: 20 additions & 16 deletions pkg/blobstore/local/block_device_backed_location_record_array.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/binary"

"github.com/buildbarn/bb-storage/pkg/blockdevice"
"github.com/buildbarn/bb-storage/pkg/lossymap"
)

const (
Expand All @@ -20,22 +21,25 @@ const (
// - Blob length 8 bytes
// - Record checksum 8 bytes
// Total: 66 bytes
//
// TODO: The hash table probing attempt field can be reduced to
// just a single byte, as lossymap only permits up to 255
// attempts. We should change the order of fields, so that
// records may become smaller.
BlockDeviceBackedLocationRecordSize = 4 + 2 + sha256.Size + 4 + 8 + 8 + 8
)

type blockDeviceBackedLocationRecordArray struct {
device blockdevice.BlockDevice
resolver BlockReferenceResolver
device blockdevice.BlockDevice
}

// NewBlockDeviceBackedLocationRecordArray creates a persistent
// LocationRecordArray. It works by using a block device as an
// array-like structure, writing serialized LocationRecords next to each
// other.
func NewBlockDeviceBackedLocationRecordArray(device blockdevice.BlockDevice, resolver BlockReferenceResolver) LocationRecordArray {
func NewBlockDeviceBackedLocationRecordArray(device blockdevice.BlockDevice) LocationRecordArray {
return &blockDeviceBackedLocationRecordArray{
device: device,
resolver: resolver,
device: device,
}
}

Expand All @@ -50,36 +54,36 @@ func computeChecksumForRecord(record *[BlockDeviceBackedLocationRecordSize]byte,
return h
}

func (lra *blockDeviceBackedLocationRecordArray) Get(index int) (LocationRecord, error) {
func (lra *blockDeviceBackedLocationRecordArray) Get(index uint64, resolver BlockReferenceResolver) (LocationRecord, error) {
var record [BlockDeviceBackedLocationRecordSize]byte
if _, err := lra.device.ReadAt(record[:], int64(index)*BlockDeviceBackedLocationRecordSize); err != nil {
return LocationRecord{}, err
}

// Reobtain the index of the block in the BlockList. This may
// fail if the entry refers to a block that is no longer there.
blockIndex, hashSeed, found := lra.resolver.BlockReferenceToBlockIndex(BlockReference{
blockIndex, hashSeed, found := resolver.BlockReferenceToBlockIndex(BlockReference{
EpochID: binary.LittleEndian.Uint32(record[:]),
BlocksFromLast: binary.LittleEndian.Uint16(record[4:]),
})
if !found {
return LocationRecord{}, ErrLocationRecordInvalid
return LocationRecord{}, lossymap.ErrRecordInvalidOrExpired
}

// Discard entries for which the checksum of the record doesn't
// match up with what's expected. Such records may have either
// been corrupted or correspond to blobs that weren't flushed
// before shutdown.
if computeChecksumForRecord(&record, hashSeed) != binary.LittleEndian.Uint64(record[4+2+sha256.Size+4+8+8:]) {
return LocationRecord{}, ErrLocationRecordInvalid
return LocationRecord{}, lossymap.ErrRecordInvalidOrExpired
}

// Deserialize the read record into a LocationRecord.
l := LocationRecord{
RecordKey: LocationRecordKey{
Attempt: binary.LittleEndian.Uint32(record[4+2+sha256.Size:]),
Attempt: record[4+2+sha256.Size],
},
Location: Location{
Value: Location{
BlockIndex: blockIndex,
OffsetBytes: int64(binary.LittleEndian.Uint64(record[4+2+sha256.Size+4:])),
SizeBytes: int64(binary.LittleEndian.Uint64(record[4+2+sha256.Size+4+8:])),
Expand All @@ -89,17 +93,17 @@ func (lra *blockDeviceBackedLocationRecordArray) Get(index int) (LocationRecord,
return l, nil
}

func (lra *blockDeviceBackedLocationRecordArray) Put(index int, locationRecord LocationRecord) error {
blockReference, hashSeed := lra.resolver.BlockIndexToBlockReference(locationRecord.Location.BlockIndex)
func (lra *blockDeviceBackedLocationRecordArray) Put(index uint64, locationRecord LocationRecord, resolver BlockReferenceResolver) error {
blockReference, hashSeed := resolver.BlockIndexToBlockReference(locationRecord.Value.BlockIndex)

// Serialize the LocationRecord ready to be written to disk.
var record [BlockDeviceBackedLocationRecordSize]byte
binary.LittleEndian.PutUint32(record[:], blockReference.EpochID)
binary.LittleEndian.PutUint16(record[4:], blockReference.BlocksFromLast)
copy(record[4+2:], locationRecord.RecordKey.Key[:])
binary.LittleEndian.PutUint32(record[4+2+sha256.Size:], locationRecord.RecordKey.Attempt)
binary.LittleEndian.PutUint64(record[4+2+sha256.Size+4:], uint64(locationRecord.Location.OffsetBytes))
binary.LittleEndian.PutUint64(record[4+2+sha256.Size+4+8:], uint64(locationRecord.Location.SizeBytes))
record[4+2+sha256.Size] = locationRecord.RecordKey.Attempt
binary.LittleEndian.PutUint64(record[4+2+sha256.Size+4:], uint64(locationRecord.Value.OffsetBytes))
binary.LittleEndian.PutUint64(record[4+2+sha256.Size+4+8:], uint64(locationRecord.Value.SizeBytes))
binary.LittleEndian.PutUint64(record[4+2+sha256.Size+4+8+8:], computeChecksumForRecord(&record, hashSeed))

_, err := lra.device.WriteAt(record[:], int64(index)*BlockDeviceBackedLocationRecordSize)
Expand Down
Loading