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
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ use_repo(
"com_github_buildbarn_go_sha256tree",
"com_github_fxtlabs_primes",
"com_github_go_jose_go_jose_v3",
"com_github_gocql_gocql",
"com_github_google_uuid",
"com_github_gorilla_mux",
"com_github_grpc_ecosystem_go_grpc_middleware",
Expand Down
27 changes: 18 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.33.14
github.com/bazelbuild/buildtools v0.0.0-20250204160707-ad48c76ab9b5
github.com/bazelbuild/remote-apis v0.0.0-20250211041012-7f922028fcfa
github.com/bazelbuild/rules_go v0.53.0
github.com/buildbarn/go-sha256tree v0.0.0-20250310211320-0f70f20e855b
github.com/fxtlabs/primes v0.0.0-20150821004651-dad82d10a449
github.com/go-jose/go-jose/v3 v3.0.3
Expand Down Expand Up @@ -54,14 +55,20 @@ require (
mvdan.cc/gofumpt v0.7.0
)

require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
)

require (
cel.dev/expr v0.19.0 // indirect
cloud.google.com/go v0.116.0 // indirect
cloud.google.com/go v0.118.0 // indirect
cloud.google.com/go/auth v0.14.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.2.2 // indirect
cloud.google.com/go/monitoring v1.21.2 // indirect
cloud.google.com/go/iam v1.3.1 // indirect
cloud.google.com/go/monitoring v1.22.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
Expand All @@ -88,31 +95,33 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/gocql/gocql v1.7.0
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.10.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d // indirect
golang.org/x/tools v0.30.0 // indirect
google.golang.org/genproto v0.0.0-20250115164207-1a7da9e5054f // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
Expand Down
97 changes: 48 additions & 49 deletions go.sum

Large diffs are not rendered by default.

38 changes: 38 additions & 0 deletions pkg/blobstore/cassandra/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "cassandra",
srcs = [
"cassandra_blob_access.go",
"format.go",
],
importpath = "github.com/buildbarn/bb-storage/pkg/blobstore/cassandra",
visibility = ["//visibility:public"],
deps = [
"//pkg/blobstore",
"//pkg/blobstore/buffer",
"//pkg/blobstore/slicing",
"//pkg/capabilities",
"//pkg/digest",
"//pkg/util",
"@bazel_remote_apis//build/bazel/remote/execution/v2:remote_execution_go_proto",
"@com_github_gocql_gocql//:gocql",
"@com_github_prometheus_client_golang//prometheus",
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//status",
"@org_golang_x_sync//errgroup",
],
)

go_test(
name = "cassandra_test",
srcs = [
"cassandra_blob_access_test.go",
"format_test.go",
],
embed = [":cassandra"],
deps = [
"@com_github_gocql_gocql//:gocql",
"@com_github_stretchr_testify//require",
],
)
104 changes: 104 additions & 0 deletions pkg/blobstore/cassandra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Cassandra

## Required setup

`bb-storage` should not be configured to use the admin user by default in
production. Instead, you should be using a regular user which cannot create
tables or keyspaces.

The tables that are required can be set up using command similar to the
below in a `cqlsh` session:

```sql
CREATE KEYSPACE IF NOT EXISTS buildbarn_storage WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor': 3 };

USE buildbarn_storage;

CREATE TABLE IF NOT EXISTS prod_metadata (
digest_function TEXT,
digest_hash BLOB,
digest_size_bytes BIGINT,
digest_instance_name TEXT,
blob_id ASCII,
last_access TIMESTAMP,
segment_count INT,
segment_size INT,
PRIMARY KEY ((digest_function, digest_hash, digest_size_bytes, digest_instance_name)))
WITH gc_grace_seconds = 86400;

CREATE TABLE IF NOT EXISTS prod_content (
blob_id ASCII,
segment INT,
content BLOB,
PRIMARY KEY ((blob_id, segment)))
WITH gc_grace_seconds = 86400;

CREATE TABLE IF NOT EXISTS prod_orphaned_content (
blob_id ASCII,
digest_instance_name TEXT,
digest_function TEXT,
digest_hash BLOB,
digest_size_bytes BIGINT,
segment_count INT,
timestamp DATE,
PRIMARY KEY (blob_id, digest_instance_name))
WITH WITH gc_grace_seconds = 86400;
```

Note that you will need a table prefix (`prod` in this case), which allow
multiple different environments to run in the same keyspace.

The `prod_orphaned_content` table is used to track items that may have been
orphaned. Essentially, as blobs begin to be streamed into cassandra, a note
is taken of the blob and the estimated number of segments it will require.
Users are expected to have their own processes in place for using this data
to "reap" orphan content from the table.

The `prod_orphaned_content` table will contain many Cassandra tombstones
(most rows are deleted soon after they are inserted). For that reason, you
should reduce the `gc_grace_seconds` to 1 day. This reduces the minimum
amount of time tombstones will be kept around. This can cause some rows to
resurrect if a Cassandra node is down for more than a day, but it is not a
problem at the application level: they will just need to be reaped again.

In the long term, this might still create many tombstones, causing non-critical
errors when reaping old orphan rows. Consider setting a scheduled compaction
(e.g. every 2h) to help ensure that tombstones are regularly reviewed for
garbage collection.

The `prod_metadata` and `prod_content` tables also contain many tombstones
so the `gc_grace_seconds` attribute is also set to 1 day (86400s).

### Configuring `bb-storage`

An example snippet of jsonnet config for setting up Cassandra is:

```jsonnet
{
cassandra: {
hosts: ["cassandra.mycorp.com:1234"],
context: "example/bazel",
keyspace: "buildbarn_storage",
tablePrefix: "prod",
segmentSize: 524288,
username: "cassandra_user",
password: "hunter2",
}
}
```

The segment size is the size in bytes before a new segment should be created.
It is a balancing act between being able to store as many items as possible
in a single segment and the maximum row size allowed in Cassandra (1MB). In
this example, 512kb was chosen, which should allow the majority of writes
seen by Buildbarn to be written in a single row.

### Using the nearest DC

To minimise latency, you can set a `preferredDC` in the `cassandra`
configuration. This works on the assumption that physical proximity will
lead to lower latencies, so the preferred DC is the one that is closest to the
particular cluster the Buildbarn is installed in. To find the nearest data
centre, talk to someone familiar with your network topology, and select from
the datacenters listed in your `NetworkTopologyStrategy` (if you're using
one) when creating the Cassandra keyspace.
Loading