Skip to content

Add support for SplitBlob and SpliceBlob methods - #345

Open
meroton-benjamin wants to merge 1 commit into
buildbarn:mainfrom
meroton:cdc-support-step-1
Open

Add support for SplitBlob and SpliceBlob methods#345
meroton-benjamin wants to merge 1 commit into
buildbarn:mainfrom
meroton:cdc-support-step-1

Conversation

@meroton-benjamin

Copy link
Copy Markdown
Contributor

This commit adds support for the SplitBlob and SpliceBlob methods from the Remote Execution v2 (REv2) api. SplitBlob and SpliceBlob can be used to facilitate uploads and downloads of large files but a naïve implementation like this has some major drawbacks as well.

The blobs must exist in both their chunked and non chunked form, which may significantly increase storage requirements for large blobs. The protocol gives no guarantee that a large blob stored in the CAS exists in its chunked form which forces you to perform a fairly heavy Split call that loads the entire large blob in order to decomposition it into its chunks.

This implementation mostly exists as a stepping stone for a different implementation where Buildbarn internally manages all blobs as chunked blobs.

Comment thread pkg/blobstore/grpcclients/cls_blob_access.go Outdated
func (contentAddressableStorageServer) SpliceBlob(ctx context.Context, in *remoteexecution.SpliceBlobRequest) (*remoteexecution.SpliceBlobResponse, error) {
return nil, status.Error(codes.Unimplemented, "This service does not support splicing blobs")
func (s *contentAddressableStorageServer) SpliceBlob(ctx context.Context, in *remoteexecution.SpliceBlobRequest) (*remoteexecution.SpliceBlobResponse, error) {
if s.chunkListStorage == nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing this, why don't we require that ChunkListStorage is non-nil, and that an ErrorBlobAccess is passed in?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could but then the ContentAddressableStorageServer would be unable to behave differently when the ChunkListStorage is not defined. If we want the FindMissingBlobs implementation from below then we need to know when we need to verify chunk list storage and when we do not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe good to at least add a // TODO: Require that s.chunkListStorage is non-null once we require chunking. ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread pkg/blobstore/grpcclients/cls_blob_access.go Outdated
Comment thread pkg/blobstore/grpcservers/content_addressable_storage_server.go
Comment thread pkg/capabilities/chunking_provider.go Outdated

// fakeBlobAccess provides a thread-safe, in-memory BlobAccess for
// testing.
type fakeBlobAccess struct {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just use gomock for consistency with most of the other tests in Buildbarn?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh we sort of could but it becomes noisy, for some of the tests a straight up EXPECT().Return() construct flows well but for the more complicated tests such as the ones where we want to verify chunking and that files have been properly touched it becomes very noisy.

For those tests we could use .EXPECT().DoAndReturn().AnyTimes() to implement pretty much the same thing as in fakeBlobAccess already does but that would basically be the same thing as fakeBlobAccess just with lambdas instead of functions.

If you prefer that anyway I can modify the code such that it uses gomock for consistency but at least in my mind fakeBlobAccess was easier to understand.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any input on if we want to turn fakeBlobAccess into a lambda construct with gomock for consistency or if it's fine as is?

Comment thread cmd/bb_storage/main.go
@meroton-benjamin
meroton-benjamin force-pushed the cdc-support-step-1 branch 7 times, most recently from c20835c to e8f71eb Compare June 3, 2026 11:42
@moroten
moroten force-pushed the cdc-support-step-1 branch from 343d61a to 9c637a9 Compare June 5, 2026 06:21
Comment thread cmd/bb_storage/main.go Outdated
grpcClientFactory,
)
if err != nil {
return util.StatusWrap(err, "Failed to create Chunk Map")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed to create Chunk List Storage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

func (contentAddressableStorageServer) SpliceBlob(ctx context.Context, in *remoteexecution.SpliceBlobRequest) (*remoteexecution.SpliceBlobResponse, error) {
return nil, status.Error(codes.Unimplemented, "This service does not support splicing blobs")
func (s *contentAddressableStorageServer) SpliceBlob(ctx context.Context, in *remoteexecution.SpliceBlobRequest) (*remoteexecution.SpliceBlobResponse, error) {
if s.chunkListStorage == nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe good to at least add a // TODO: Require that s.chunkListStorage is non-null once we require chunking. ?

bigBlobDigests.Add(digest)
}
}
_, _ = s.chunkListStorage.FindMissing(ctx, bigBlobDigests.Build())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure we don't need any error/results handling here...?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We perform the call towards the chunk list storage in order to prevent the chunk list storage itself from being dropped from the cache if they exist. As we've yet to make the chunking mandatory semantically the blob is not missing even if it's chunk list is missing.

With regards to errors, I'm not sure how we would treat errors at this point.

switch backend := configuration.Backend.(type) {
case *pb.BlobAccessConfiguration_ChunkListValidating:
if bac.contentAddressableStorage == nil {
return BlobAccessInfo{}, "", status.Error(codes.InvalidArgument, "Action Cache completeness checking can only be enabled if a Content Addressable Storage is configured")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Action Cache completeness checking/.../ ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with "Chunk list validation can only be enabled if a Content Addressable Storage is configured"

return nil, status.Error(codes.Unimplemented, "This backend only supports upstream servers with rep max cdc support.")
}
if params.MinChunkSizeBytes < 64 {
return nil, status.Errorf(codes.Internal, "MinChunkSizeBytes was %d but a minimum of 64 is required.", params.MinChunkSizeBytes)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"RepMaxCDC minimum chunk size was %d bytes, but a minimum of 64 bytes is required" ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

Comment thread pkg/blobstore/chunklistvalidating/chunk_list_validating_blob_access.go Outdated
return nil, util.StatusWrap(err, "Unable to GetCapabilities to determine chunking parameters")
}

params := capabilities.GetCacheCapabilities().GetRepMaxCdcParams()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/GetCacheCapabilities()/CacheCapabilities/

In the Buildbarn tree I only try to use .Get*() in cases where the parent may be nil. That way it's sort of self-documenting what can be nil or not. In this case the top-level message is always non-nil.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

}
maxMinChunkSize := (ba.maximumMessageSizeBytes + 1) / 2
if params.MinChunkSizeBytes > uint64(maxMinChunkSize) {
return nil, status.Errorf(codes.Internal, "MinChunkSizeBytes was %d but a maximum of %d is supported with the configured maximum message size.", params.MinChunkSizeBytes, maxMinChunkSize)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure to remove trailing dots from the error messages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

This commit adds support for the SplitBlob and SpliceBlob methods from
the Remote Execution v2 (REv2) api. SplitBlob and SpliceBlob can be used
to facilitate uploads and downloads of large files but a naïve
implementation like this has some major drawbacks as well.

The blobs must exist in both their chunked and non chunked form,
which may significantly increase storage requirements for large blobs.
The protocol gives no guarantee that a large blob stored in the CAS
exists in its chunked form which forces you to perform a fairly heavy
Split call that loads the entire large blob in order to decomposition it
into its chunks.

This implementation mostly exists as a stepping stone for a different
implementation where Buildbarn internally manages all blobs as chunked
blobs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants