Skip to content

Commit 2d1ed3d

Browse files
committed
Add lease tracking to bonanza_storage_shard
Our disk based storage backend (which is still a stub) currently needs to deal with storing objects and tracking leases. However in our case it's sufficient to just have a decorator that does the lease tracking for us. Solve this by adding a new FlatStore interface, which is essentially a simplified version of Store that uses a new FlatReference type. FlatReference is similar to an REv2 digest, only storing a hash and size. We then add an adapter on top of FlatStore that does the lease tracking. The next step will be to add a proper FlatStore implementation that writes objects to disk. This can be a lot simpler than LocalBlobAccess, because we don't need to support FindMissingBlobs() and only need to store objects up to 2 MB in size.
1 parent 08198fc commit 2d1ed3d

24 files changed

Lines changed: 852 additions & 93 deletions

cmd/bonanza_storage_shard/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@ go_library(
77
importpath = "github.com/buildbarn/bonanza/cmd/bonanza_storage_shard",
88
visibility = ["//visibility:private"],
99
deps = [
10+
"//pkg/ds/lossymap",
1011
"//pkg/proto/configuration/bonanza_storage_shard",
1112
"//pkg/proto/storage/object",
1213
"//pkg/proto/storage/tag",
1314
"//pkg/storage/object",
15+
"//pkg/storage/object/flatbacked",
1416
"//pkg/storage/object/leasemarshaling",
1517
"//pkg/storage/object/local",
1618
"//pkg/storage/object/namespacemapping",
1719
"//pkg/storage/tag",
1820
"//pkg/storage/tag/leasemarshaling",
1921
"//pkg/storage/tag/local",
22+
"@com_github_buildbarn_bb_storage//pkg/clock",
2023
"@com_github_buildbarn_bb_storage//pkg/global",
2124
"@com_github_buildbarn_bb_storage//pkg/grpc",
2225
"@com_github_buildbarn_bb_storage//pkg/program",

cmd/bonanza_storage_shard/main.go

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ package main
22

33
import (
44
"context"
5+
"math/rand/v2"
56
"os"
67

8+
"github.com/buildbarn/bb-storage/pkg/clock"
79
"github.com/buildbarn/bb-storage/pkg/global"
810
bb_grpc "github.com/buildbarn/bb-storage/pkg/grpc"
911
"github.com/buildbarn/bb-storage/pkg/program"
1012
"github.com/buildbarn/bb-storage/pkg/util"
13+
"github.com/buildbarn/bonanza/pkg/ds/lossymap"
1114
"github.com/buildbarn/bonanza/pkg/proto/configuration/bonanza_storage_shard"
1215
object_pb "github.com/buildbarn/bonanza/pkg/proto/storage/object"
1316
tag_pb "github.com/buildbarn/bonanza/pkg/proto/storage/tag"
1417
"github.com/buildbarn/bonanza/pkg/storage/object"
18+
object_flatbacked "github.com/buildbarn/bonanza/pkg/storage/object/flatbacked"
1519
object_leasemarshaling "github.com/buildbarn/bonanza/pkg/storage/object/leasemarshaling"
1620
object_local "github.com/buildbarn/bonanza/pkg/storage/object/local"
1721
object_namespacemapping "github.com/buildbarn/bonanza/pkg/storage/object/namespacemapping"
@@ -38,9 +42,56 @@ func main() {
3842
return util.StatusWrap(err, "Failed to apply global configuration options")
3943
}
4044

41-
objectStore := object_local.NewStore()
45+
// Construct a flat storage backend for objects. Put an
46+
// in-memory store for leases in front of it, so that
47+
// UploadDag() can reliably enforce that clients upload
48+
// complete DAGs.
49+
leaseCompletenessDuration := configuration.LeasesMapLeaseCompletenessDuration
50+
if err := leaseCompletenessDuration.CheckValid(); err != nil {
51+
return util.StatusWrap(err, "Invalid leases map lease completeness duration")
52+
}
53+
leaseComparator := func(a, b *object_flatbacked.Lease) int {
54+
if *a < *b {
55+
return -1
56+
}
57+
if *a > *b {
58+
return 1
59+
}
60+
return 0
61+
}
62+
hashInitialization := rand.Uint64()
63+
objectStore := object_flatbacked.NewStore(
64+
object_local.NewStore(),
65+
clock.SystemClock,
66+
leaseCompletenessDuration.AsDuration(),
67+
lossymap.NewHashMap(
68+
lossymap.NewSimpleRecordArray[object.LocalReference](
69+
int(configuration.LeasesMapRecordsCount),
70+
leaseComparator,
71+
),
72+
/* recordKeyHasher = */ func(k *lossymap.RecordKey[object.LocalReference]) uint64 {
73+
h := hashInitialization
74+
for _, c := range k.Key.GetRawReference() {
75+
h ^= uint64(c)
76+
h *= 1099511628211
77+
}
78+
attempt := k.Attempt
79+
for i := 0; i < 4; i++ {
80+
h ^= uint64(attempt & 0xff)
81+
h *= 1099511628211
82+
attempt >>= 8
83+
}
84+
return h
85+
},
86+
configuration.LeasesMapRecordsCount,
87+
leaseComparator,
88+
configuration.LeasesMapMaximumGetAttempts,
89+
int(configuration.LeasesMapMaximumPutAttempts),
90+
"LeasesMap",
91+
),
92+
)
4293
tagStore := tag_local.NewStore()
43-
leaseMarshaler := object_local.LeaseMarshaler
94+
leaseMarshaler := object_flatbacked.LeaseMarshaler
4495

4596
if err := bb_grpc.NewServersFromConfigurationAndServe(
4697
configuration.GrpcServers,

deployments/demo/bonanza_storage_shard.jsonnet

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,9 @@ local shard = std.extVar('SHARD');
77
listenPaths: ['%s/bonanza_storage_shard_%s%s.sock' % [statePath, replica, shard]],
88
authenticationPolicy: { allow: {} },
99
}],
10+
11+
leasesMapRecordsCount: 1e6,
12+
leasesMapLeaseCompletenessDuration: '120s',
13+
leasesMapMaximumGetAttempts: 16,
14+
leasesMapMaximumPutAttempts: 64,
1015
}

pkg/proto/configuration/bonanza_storage_shard/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ proto_library(
99
deps = [
1010
"@com_github_buildbarn_bb_storage//pkg/proto/configuration/global:global_proto",
1111
"@com_github_buildbarn_bb_storage//pkg/proto/configuration/grpc:grpc_proto",
12+
"@protobuf//:duration_proto",
1213
],
1314
)
1415

pkg/proto/configuration/bonanza_storage_shard/bonanza_storage_shard.pb.go

Lines changed: 51 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/proto/configuration/bonanza_storage_shard/bonanza_storage_shard.proto

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ syntax = "proto3";
22

33
package bonanza.configuration.bonanza_storage_shard;
44

5+
import "google/protobuf/duration.proto";
56
import "pkg/proto/configuration/global/global.proto";
67
import "pkg/proto/configuration/grpc/grpc.proto";
78

@@ -13,4 +14,74 @@ message ApplicationConfiguration {
1314

1415
// gRPC servers to spawn to listen for requests from clients.
1516
repeated buildbarn.configuration.grpc.ServerConfiguration grpc_servers = 2;
17+
18+
// The size of the leases map.
19+
//
20+
// The leases map is an in-memory data structure that stores leases of
21+
// all children of objects stored in the current shard. These leases
22+
// track the completeness of graphs of objects, which is necessary for
23+
// UploadDag() operations against the storage frontend to run
24+
// efficiently.
25+
//
26+
// Each record of the leases map is less than 64 bytes in size, and
27+
// records are stored contiguously. This means that it is generally
28+
// safe to set this option to millions or more.
29+
//
30+
// The leases map needs to be large enough to not lose any records
31+
// that haven't expired yet. The following Prometheus queries may be
32+
// used to determine insertions into the leases map caused other
33+
// records to be displaced prematurely:
34+
//
35+
// bonanza_lossymap_hash_map_put_iterations_count{name="LeasesMap",outcome="TooManyAttempts"}
36+
// bonanza_lossymap_hash_map_put_too_many_iterations_total{name="LeasesMap"}
37+
uint64 leases_map_records_count = 3;
38+
39+
// The maximum age a lease may have for a parent object using the
40+
// lease to be considered complete.
41+
//
42+
// If a parent object references one or more children for which the
43+
// lease's age exceeds twice this value, the storage frontend is
44+
// instructed to renew the leases whose age exceeds this value.
45+
//
46+
// More concretely speaking, consider the case in which this option is
47+
// set to the recommended value of 2 minutes. In this case a parent
48+
// object is considered to be complete if the leases of all of its
49+
// children are less than 4 minutes old. If one or more leases are
50+
// more than 4 minutes old (or missing), the storage frontend has to
51+
// renew all the leases that are at least 2 minutes old (or
52+
// missing) to make the parent object complete again.
53+
//
54+
// What this means in practice is that for UploadDag() to work
55+
// reliably (i.e., not reporting graphs as being present while in
56+
// reality they are not), a storage replica may only be restarted if
57+
// the other replica is healthy for at least
58+
// 'height*leases_map_lease_completeness_duration' time, where
59+
// 'height' refers to the height of the root node of the DAG that is
60+
// being uploaded.
61+
//
62+
// Recommended value: 120s
63+
google.protobuf.Duration leases_map_lease_completeness_duration = 4;
64+
65+
// The number of records a Get() call on the leases map may attempt
66+
// to access.
67+
//
68+
// The lower the utilization rate of the leases map, the lower this
69+
// value may be set. For example, if the size of the leases map is set
70+
// in such a way that it is only utilized by 10% (factor 0.1), setting
71+
// this field to 16 means there is only a 0.1^16 chance that inserting
72+
// a record prematurely displaces another valid lease.
73+
//
74+
// Recommended value: 16
75+
uint32 leases_map_maximum_get_attempts = 5;
76+
77+
// The number of mutations that a Put() on the leases map may perform.
78+
//
79+
// Because the leases map uses a scheme similar to Robin Hood hashing,
80+
// insertions may cause other records to be displaced. Those records
81+
// may then cause even more records to be displaced. Because of that,
82+
// it is recommended to set this field to a small multiple of the
83+
// maximum Get() attempts.
84+
//
85+
// Recommended value: 64
86+
int64 leases_map_maximum_put_attempts = 6;
1687
}

pkg/storage/object/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ go_library(
88
"contents.go",
99
"downloader.go",
1010
"downloader_server.go",
11+
"flat_reference.go",
1112
"global_reference.go",
1213
"instance_name.go",
1314
"limit.go",

pkg/storage/object/basic_reference.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type BasicReference interface {
1515
GetReferenceFormat() ReferenceFormat
1616
GetRawReference() []byte
1717

18-
// Conversion to a LocalReference with the same value.
18+
// Conversion to other reference types with the same value.
1919
GetLocalReference() LocalReference
20+
Flatten() FlatReference
2021
}

pkg/storage/object/contents.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,15 @@ func (c *Contents) cloneWithReference(r LocalReference) *Contents {
107107
}
108108
}
109109

110-
// Flatten the reference of the object, so that its height and degree
111-
// are both zero.
110+
// FlattenContents flattens the reference of the object, so that its
111+
// height and degree are both zero.
112112
//
113113
// This is used by the read caching backend, where we want to cache
114114
// individual objects as opposed to graphs. When writing objects to the
115115
// local cache, we set the height and degree to zero, so that there is
116116
// no need to track any leases.
117-
func (c *Contents) Flatten() *Contents {
118-
return c.cloneWithReference(c.LocalReference.Flatten())
117+
func (c *Contents) FlattenContents() *Contents {
118+
return c.cloneWithReference(c.LocalReference.Flatten().GetLocalReference())
119119
}
120120

121121
func (c *Contents) validateOutgoingReferences() error {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package object
2+
3+
import (
4+
"encoding/binary"
5+
"encoding/hex"
6+
)
7+
8+
// FlatReference is a simplified version of LocalReference that assumes
9+
// that the height and degree of an object is zero. This is sufficient
10+
// for the read caching backend, which is oblivious of references
11+
// between objects.
12+
type FlatReference struct {
13+
rawReference [35]byte
14+
}
15+
16+
// MustNewSHA256V1LocalReference creates a flat reference that uses
17+
// reference format SHA256_V1. This function can be used as part of
18+
// tests.
19+
func MustNewSHA256V1FlatReference(hash string, sizeBytes uint32) (flatReference FlatReference) {
20+
var rawReference [36]byte
21+
if n, err := hex.Decode(rawReference[:], []byte(hash)); err != nil {
22+
panic(err)
23+
} else if n != 32 {
24+
panic("Wrong hash length")
25+
}
26+
binary.LittleEndian.PutUint32(rawReference[32:], sizeBytes)
27+
copy(flatReference.rawReference[:], rawReference[:])
28+
return
29+
}
30+
31+
func (r FlatReference) GetLocalReference() (localReference LocalReference) {
32+
copy(localReference.rawReference[:], r.rawReference[:])
33+
return
34+
}

0 commit comments

Comments
 (0)