Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
eb612d7
use the proposed compressed-blobs REAPI
mostynb Nov 16, 2020
712e06e
[disk] store CAS blobs in compressed form
mostynb Nov 16, 2020
e9fbf6e
[disk] try to preserve atimes when migrating CAS blobs
mostynb Dec 31, 2020
f522e31
[bytestream] add support for compressed-blobs reads and writes
mostynb Dec 20, 2020
e1d3c4d
[http] allow zstd-encoded CAS GETs
mostynb Dec 7, 2020
ba069f8
[http] accept zstd-encoded PUTs
mostynb Dec 8, 2020
5eac267
[http] document how to upload/download zstd-compressed data
mostynb Dec 31, 2020
997695a
[zstd] limit concurrency to 1 for single-use {en,de}coders
mostynb Dec 20, 2020
fe70a24
[zstd] use decoder and encoder pools
mostynb Dec 22, 2020
0accc93
[bytestream] be more careful about calling Close on the result of Get…
mostynb Dec 25, 2020
6cc58ac
[disk] make the CAS storage mode (zstd/uncompressed) configurable
mostynb Dec 31, 2020
2176041
[disk] fan out when migrating old data files
mostynb Dec 31, 2020
038e067
[disk] use raw uncompressed CAS blob files
mostynb Jan 2, 2021
f8eb86c
[disk] validate cache file hash values when loading
mostynb Jan 2, 2021
512ad74
remove --destructive_compression_prototype flag
mostynb Jan 2, 2021
d5ad6e7
doc: mention compressed-blobs in the gRPC section of the readme file
mostynb Jan 3, 2021
e873612
[disk] fix space accounting
mostynb Jan 10, 2021
eaffa82
[httpproxy] don't read remote file headers for CAS.v2 Contains size v…
mostynb Jan 10, 2021
67a2ad0
[disk] make compressed cas blob files valid .zst
mostynb Jan 14, 2021
9154319
[disk] encode logical CAS blob sizes in the filename
mostynb Jan 14, 2021
3832c9c
[disk] fix --storage_mode uncompressed uploads
mostynb Jan 15, 2021
f907889
[proxy] add cmdline flags for http and gcs backend proxies
mostynb Jan 16, 2021
0cdfdeb
[proxy] switch between v1 and v2 CAS blobs depending on the storage mode
mostynb Jan 31, 2021
163eb40
[s3proxy] don't read remote file headers for CAS.v2 Contains size value
mostynb Feb 7, 2021
9af561e
[http] don't interact with files in tests, use disk.Cache public API
mostynb Feb 9, 2021
0bdc1b4
[disk] rework the disk storage to avoid fs operations while holding t…
mostynb Feb 11, 2021
63ba6fc
[casblob] sanity check file size
mostynb Feb 19, 2021
b12351e
[casblob] add note that we never store empty files
mostynb Feb 19, 2021
605d444
[casblob] add missing Close on error conditions
mostynb Feb 19, 2021
c24f861
[casblob] make the GetUncompressedReadCloser and GetZstdReadCloser co…
mostynb Feb 19, 2021
ed9901a
[casblob] GetLegacyZstdReadCloser should pass errors to readers
mostynb Feb 20, 2021
311fb8e
[casblob] read and write chunk offset table with one binary.Read/Writ…
mostynb Feb 20, 2021
ff4c853
[casblob] proxy backends should return the logical size for Get
mostynb Feb 20, 2021
564d20e
[casblob] test a basic assumption about the sizeof int
mostynb Feb 20, 2021
48e8649
[disk] accept ascii characters in random string of blob filenames
mostynb Feb 26, 2021
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
97 changes: 63 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ commodity hardware and AWS servers. Outgoing bandwidth can exceed 15 Gbit/s on t
## HTTP/1.1 REST API

Cache entries are set and retrieved by key, and there are two types of keys that can be used:
1. Content addressed storage (CAS), where the key is the lowercase SHA256 hash of the stored value.
1. Content addressed storage (CAS), where the key is the lowercase SHA256 hash of the entry.
The REST API for these entries is: `/cas/<key>` or with an optional but ignored instance name:
`/<instance>/cas/<key>`.
2. Action cache, where the key is an arbitrary 64 character lowercase hexadecimal string.
Expand All @@ -24,6 +24,14 @@ Cache entries are set and retrieved by key, and there are two types of keys that
Values are stored via HTTP PUT requests, and retrieved via GET requests.
HEAD requests can be used to confirm whether a key exists or not.

If GET requests specify `zstd` in the `Accept-Encoding` header, then
zstandard-encoded data may be returned.

To upload zstandard compressed data, PUT requests must set
`Content-Encoding: zstd` and include a custom `X-Digest-SizeBytes` header
with the size of the uncompressed entry. The key must also refer to
the uncompressed entry.

If the `--enable_ac_key_instance_mangling` flag is specified and the instance
name is not empty, then action cache keys are hashed along with the instance
name to produce the action cache lookup key. Since the URL path is processed
Expand Down Expand Up @@ -93,6 +101,19 @@ which can be enabled with the `--experimental_remote_asset_api` flag.
To use this with Bazel, specify
[--experimental_remote_downloader=grpc://replace-with-your.host:port](https://docs.bazel.build/versions/master/command-line-reference.html#flag--experimental_remote_downloader).

### Byte Stream compressed-blobs

This version of bazel-remote supports the
[Byte Stream compressed-blobs proposal](https://github.com/bazelbuild/remote-apis/pull/168),
which provides a way for clients to upload and download CAS blobs compressed
with zstandard, in order to improve network efficiency.

Uploaded CAS blobs are stored in a zstandard compressed format by default,
which can increase the effective cache size and reduce load on the server
if clients also download blobs in zstandard compressed form. If you would
rather store CAS blobs in uncompressed form, add `--storage_mode uncompressed`
to your configuration.

## Usage

If a YAML configuration file is specified by the `--config_file` command line
Expand All @@ -115,39 +136,44 @@ DESCRIPTION:
A remote build cache for Bazel.

GLOBAL OPTIONS:
--config_file value Path to a YAML configuration file. If this flag is specified then all other flags are ignored. [$BAZEL_REMOTE_CONFIG_FILE]
--dir value Directory path where to store the cache contents. This flag is required. [$BAZEL_REMOTE_DIR]
--max_size value The maximum size of the remote cache in GiB. This flag is required. (default: -1) [$BAZEL_REMOTE_MAX_SIZE]
--host value Address to listen on. Listens on all network interfaces by default. [$BAZEL_REMOTE_HOST]
--port value The port the HTTP server listens on. (default: 8080) [$BAZEL_REMOTE_PORT]
--grpc_port value The port the gRPC server listens on. Set to 0 to disable. (default: 9092) [$BAZEL_REMOTE_GRPC_PORT]
--profile_host value A host address to listen on for profiling, if enabled by a valid --profile_port setting. (default: "127.0.0.1") [$BAZEL_REMOTE_PROFILE_HOST]
--profile_port value If a positive integer, serve /debug/pprof/* URLs from http://profile_host:profile_port. (default: 0, ie profiling disabled) [$BAZEL_REMOTE_PROFILE_PORT]
--http_read_timeout value The HTTP read timeout for a client request in seconds (does not apply to the proxy backends or the profiling endpoint) (default: 0s, ie disabled) [$BAZEL_REMOTE_HTTP_READ_TIMEOUT]
--http_write_timeout value The HTTP write timeout for a server response in seconds (does not apply to the proxy backends or the profiling endpoint) (default: 0s, ie disabled) [$BAZEL_REMOTE_HTTP_WRITE_TIMEOUT]
--htpasswd_file value Path to a .htpasswd file. This flag is optional. Please read https://httpd.apache.org/docs/2.4/programs/htpasswd.html. [$BAZEL_REMOTE_HTPASSWD_FILE]
--tls_enabled This flag has been deprecated. Specify tls_cert_file and tls_key_file instead. (default: false) [$BAZEL_REMOTE_TLS_ENABLED]
--tls_ca_file value Optional. Enables mTLS (authenticating client certificates), should be the certificate authority that signed the client certificates. [$BAZEL_REMOTE_TLS_CA_FILE]
--tls_cert_file value Path to a pem encoded certificate file. [$BAZEL_REMOTE_TLS_CERT_FILE]
--tls_key_file value Path to a pem encoded key file. [$BAZEL_REMOTE_TLS_KEY_FILE]
--idle_timeout value The maximum period of having received no request after which the server will shut itself down. (default: 0s, ie disabled) [$BAZEL_REMOTE_IDLE_TIMEOUT]
--max_queued_uploads value When using proxy backends, sets the maximum number of objects in queue for upload. If the queue is full, uploads will be skipped until the queue has space again. (default: 1000000) [$BAZEL_REMOTE_MAX_QUEUED_UPLOADS]
--num_uploaders value When using proxy backends, sets the number of Goroutines to process parallel uploads to backend. (default: 100) [$BAZEL_REMOTE_NUM_UPLOADERS]
--s3.endpoint value The S3/minio endpoint to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_ENDPOINT]
--s3.bucket value The S3/minio bucket to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_BUCKET]
--s3.prefix value The S3/minio object prefix to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_PREFIX]
--s3.access_key_id value The S3/minio access key to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_ACCESS_KEY_ID]
--s3.secret_access_key value The S3/minio secret access key to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_SECRET_ACCESS_KEY]
--s3.disable_ssl Whether to disable TLS/SSL when using the S3 proxy backend. (default: false, ie enable TLS/SSL) [$BAZEL_REMOTE_S3_DISABLE_SSL]
--s3.iam_role_endpoint value Endpoint for using IAM security credentials. By default it will look for credentials in the standard locations for the AWS platform. [$BAZEL_REMOTE_S3_IAM_ROLE_ENDPOINT]
--s3.region value The AWS region. Required when not specifying S3/minio access keys. [$BAZEL_REMOTE_S3_REGION]
--s3.key_version value Set to 1 for the legacy flat key format, or 2 for the newer format that reduces the impact of S3 rate limits. (default: 1) [$BAZEL_REMOTE_S3_KEY_VERSION]
--disable_http_ac_validation Whether to disable ActionResult validation for HTTP requests. (default: false, ie enable validation) [$BAZEL_REMOTE_DISABLE_HTTP_AC_VALIDATION]
--disable_grpc_ac_deps_check Whether to disable ActionResult dependency checks for gRPC GetActionResult requests. (default: false, ie enable ActionCache dependency checks) [$BAZEL_REMOTE_DISABLE_GRPS_AC_DEPS_CHECK]
--enable_ac_key_instance_mangling Whether to enable mangling ActionCache keys with non-empty instance names. (default: false, ie disable mangling) [$BAZEL_REMOTE_ENABLE_AC_KEY_INSTANCE_MANGLING]
--enable_endpoint_metrics Whether to enable metrics for each HTTP/gRPC endpoint. (default: false, ie disable metrics) [$BAZEL_REMOTE_ENABLE_ENDPOINT_METRICS]
--experimental_remote_asset_api Whether to enable the experimental remote asset API implementation. (default: false, ie disable remote asset API) [$BAZEL_REMOTE_EXPERIMENTAL_REMOTE_ASSET_API]
--help, -h show help (default: false)
--config_file value Path to a YAML configuration file. If this flag is specified then all other flags are ignored. [$BAZEL_REMOTE_CONFIG_FILE]
--dir value Directory path where to store the cache contents. This flag is required. [$BAZEL_REMOTE_DIR]
--max_size value The maximum size of the remote cache in GiB. This flag is required. (default: -1) [$BAZEL_REMOTE_MAX_SIZE]
--storage_mode value Which format to store CAS blobs in. Must be one of "zstd" or "uncompressed". (default: "zstd") [$BAZEL_REMOTE_STORAGE_MODE]
--host value Address to listen on. Listens on all network interfaces by default. [$BAZEL_REMOTE_HOST]
--port value The port the HTTP server listens on. (default: 8080) [$BAZEL_REMOTE_PORT]
--grpc_port value The port the gRPC server listens on. Set to 0 to disable. (default: 9092) [$BAZEL_REMOTE_GRPC_PORT]
--profile_host value A host address to listen on for profiling, if enabled by a valid --profile_port setting. (default: "127.0.0.1") [$BAZEL_REMOTE_PROFILE_HOST]
--profile_port value If a positive integer, serve /debug/pprof/* URLs from http://profile_host:profile_port. (default: 0, ie profiling disabled) [$BAZEL_REMOTE_PROFILE_PORT]
--http_read_timeout value The HTTP read timeout for a client request in seconds (does not apply to the proxy backends or the profiling endpoint) (default: 0s, ie disabled) [$BAZEL_REMOTE_HTTP_READ_TIMEOUT]
--http_write_timeout value The HTTP write timeout for a server response in seconds (does not apply to the proxy backends or the profiling endpoint) (default: 0s, ie disabled) [$BAZEL_REMOTE_HTTP_WRITE_TIMEOUT]
--htpasswd_file value Path to a .htpasswd file. This flag is optional. Please read https://httpd.apache.org/docs/2.4/programs/htpasswd.html. [$BAZEL_REMOTE_HTPASSWD_FILE]
--tls_enabled This flag has been deprecated. Specify tls_cert_file and tls_key_file instead. (default: false) [$BAZEL_REMOTE_TLS_ENABLED]
--tls_ca_file value Optional. Enables mTLS (authenticating client certificates), should be the certificate authority that signed the client certificates. [$BAZEL_REMOTE_TLS_CA_FILE]
--tls_cert_file value Path to a pem encoded certificate file. [$BAZEL_REMOTE_TLS_CERT_FILE]
--tls_key_file value Path to a pem encoded key file. [$BAZEL_REMOTE_TLS_KEY_FILE]
--idle_timeout value The maximum period of having received no request after which the server will shut itself down. (default: 0s, ie disabled) [$BAZEL_REMOTE_IDLE_TIMEOUT]
--max_queued_uploads value When using proxy backends, sets the maximum number of objects in queue for upload. If the queue is full, uploads will be skipped until the queue has space again. (default: 1000000) [$BAZEL_REMOTE_MAX_QUEUED_UPLOADS]
--num_uploaders value When using proxy backends, sets the number of Goroutines to process parallel uploads to backend. (default: 100) [$BAZEL_REMOTE_NUM_UPLOADERS]
--http_proxy.url value The base URL to use for a http proxy backend. [$BAZEL_REMOTE_HTTP_PROXY_URL]
--gcs_proxy.bucket value The bucket to use for the Google Cloud Storage proxy backend. [$BAZEL_REMOTE_GCS_BUCKET]
--gcs_proxy.use_default_credentials Whether or not to use authentication for the Google Cloud Storage proxy backend. (default: false) [$BAZEL_REMOTE_GCS_USE_DEFAULT_CREDENTIALS]
--gcs_proxy.json_credentials_file value Path to a JSON file that contains Google credentials for the Google Cloud Storage proxy backend. [$BAZEL_REMOTE_GCS_JSON_CREDENTIALS_FILE]
--s3.endpoint value The S3/minio endpoint to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_ENDPOINT]
--s3.bucket value The S3/minio bucket to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_BUCKET]
--s3.prefix value The S3/minio object prefix to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_PREFIX]
--s3.access_key_id value The S3/minio access key to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_ACCESS_KEY_ID]
--s3.secret_access_key value The S3/minio secret access key to use when using S3 proxy backend. [$BAZEL_REMOTE_S3_SECRET_ACCESS_KEY]
--s3.disable_ssl Whether to disable TLS/SSL when using the S3 proxy backend. (default: false, ie enable TLS/SSL) [$BAZEL_REMOTE_S3_DISABLE_SSL]
--s3.iam_role_endpoint value Endpoint for using IAM security credentials. By default it will look for credentials in the standard locations for the AWS platform. [$BAZEL_REMOTE_S3_IAM_ROLE_ENDPOINT]
--s3.region value The AWS region. Required when not specifying S3/minio access keys. [$BAZEL_REMOTE_S3_REGION]
--s3.key_version value Set to 1 for the legacy flat key format, or 2 for the newer format that reduces the impact of S3 rate limits. (default: 1) [$BAZEL_REMOTE_S3_KEY_VERSION]
--disable_http_ac_validation Whether to disable ActionResult validation for HTTP requests. (default: false, ie enable validation) [$BAZEL_REMOTE_DISABLE_HTTP_AC_VALIDATION]
--disable_grpc_ac_deps_check Whether to disable ActionResult dependency checks for gRPC GetActionResult requests. (default: false, ie enable ActionCache dependency checks) [$BAZEL_REMOTE_DISABLE_GRPS_AC_DEPS_CHECK]
--enable_ac_key_instance_mangling Whether to enable mangling ActionCache keys with non-empty instance names. (default: false, ie disable mangling) [$BAZEL_REMOTE_ENABLE_AC_KEY_INSTANCE_MANGLING]
--enable_endpoint_metrics Whether to enable metrics for each HTTP/gRPC endpoint. (default: false, ie disable metrics) [$BAZEL_REMOTE_ENABLE_ENDPOINT_METRICS]
--experimental_remote_asset_api Whether to enable the experimental remote asset API implementation. (default: false, ie disable remote asset API) [$BAZEL_REMOTE_EXPERIMENTAL_REMOTE_ASSET_API]
--help, -h show help (default: false)
```

### Example configuration file
Expand All @@ -157,6 +183,9 @@ GLOBAL OPTIONS:
dir: path/to/cache-dir
max_size: 100

# The form to store CAS blobs in ("zstd" or "uncompressed"):
#storage_mode: zstd

host: localhost
# The port to use for HTTP/HTTPS:
#port: 8080
Expand Down
15 changes: 11 additions & 4 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -969,15 +969,15 @@ go_repository(
go_repository(
name = "com_github_klauspost_compress",
importpath = "github.com/klauspost/compress",
sum = "h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=",
version = "v1.11.3",
sum = "h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=",
version = "v1.11.4",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a note: I found out that https://github.com/luben/zstd-jni is much less CPU intensive during unrelated testing - esp. for decompression. This may not matter for now though, possibly more data points would be needed if want to switch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

For bazel? Sounds interesting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Oh, that makes more sense :) I prefer to avoid cgo for now, but it's possible that we switch in the future if it looks worthwhile.

)

go_repository(
name = "com_github_mostynb_go_grpc_compression",
importpath = "github.com/mostynb/go-grpc-compression",
sum = "h1:emrImHjSPW/H4aarNxuiPG1uN1WgIlVdR3M0LH5HH5E=",
version = "v1.1.2",
sum = "h1:wlscKqxoQsZQxUi6uTER+Gh9MbcUFUa4Cw5A69pj/c8=",
version = "v1.1.4",
)

go_repository(
Expand Down Expand Up @@ -1070,3 +1070,10 @@ go_repository(
sum = "h1:y7Vn4YH/rfUHOCwNhvkAcA0gMQvFdKzSE8Ri3qtcFlc=",
version = "v1.10.1",
)

go_repository(
name = "com_github_mostynb_zstdpool_syncpool",
importpath = "github.com/mostynb/zstdpool-syncpool",
sum = "h1:tSmtZSiSAfffEgy9ziKvOZOOlccUwF2Ar78+CeOH+8A=",
version = "v0.0.2",
)
36 changes: 25 additions & 11 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"crypto/sha256"
"encoding/hex"
"io"
"path/filepath"
)

// EntryKind describes the kind of cache entry
Expand Down Expand Up @@ -33,6 +32,16 @@ func (e EntryKind) String() string {
return "raw"
}

func (e EntryKind) DirName() string {
if e == AC {
return "ac.v2"
}
if e == CAS {
return "cas.v2"
}
return "raw.v2"
}

// Logger is designed to be satisfied by log.Logger.
type Logger interface {
Printf(format string, v ...interface{})
Expand All @@ -53,14 +62,20 @@ func (e *Error) Error() string {
// Proxy is the interface that (optional) proxy backends must implement.
// Implementations are expected to be safe for concurrent use.
type Proxy interface {
// Put should make a reasonable effort to proxy this data to the backend.
// This is allowed to fail silently (eg when under heavy load).
Put(kind EntryKind, hash string, size int64, rdr io.ReadCloser)

// Get should return the cache item identified by `hash`, or an error
// if something went wrong. If the item was not found, the io.ReadCloser
// will be nil.
Get(kind EntryKind, hash string) (io.ReadCloser, int64, error)
// Put makes a reasonable effort to upload the cache item identified by
// `hash` with logical size `size`, whose data is readable from `rc` to
// the proxy backend. The data available in `rc` is in the same format
// as used by the disk.Cache instance.
//
// This is allowed to fail silently (for example when under heavy load).
Put(kind EntryKind, hash string, size int64, rc io.ReadCloser)

// Get returns an io.ReadCloser from which the cache item identified by
// `hash` can be read, its logical size, and an error if something went
// wrong. The data available from `rc` is in the same format as used by
// the disk.Cache instance.
Get(kind EntryKind, hash string) (rc io.ReadCloser, size int64, err error)

// Contains returns whether or not the cache item exists on the
// remote end, and the size if it exists (and -1 if the size is
Expand All @@ -87,7 +102,6 @@ func TransformActionCacheKey(key, instance string, logger Logger) string {
return newKey
}

// Key returns the proper cache key for an entry kind and hash.
func Key(kind EntryKind, hash string) string {
return filepath.Join(kind.String(), hash[:2], hash)
func LookupKey(kind EntryKind, hash string) string {
return kind.String() + "/" + hash
}
2 changes: 2 additions & 0 deletions cache/disk/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//cache:go_default_library",
"//cache/disk/casblob:go_default_library",
"//genproto/build/bazel/remote/execution/v2:go_default_library",
"//utils/tempfile:go_default_library",
"@com_github_djherbis_atime//:go_default_library",
Expand All @@ -28,6 +29,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//cache:go_default_library",
"//cache/disk/casblob:go_default_library",
"//cache/httpproxy:go_default_library",
"//genproto/build/bazel/remote/execution/v2:go_default_library",
"//utils:go_default_library",
Expand Down
19 changes: 19 additions & 0 deletions cache/disk/casblob/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
srcs = ["casblob.go"],
importpath = "github.com/buchgr/bazel-remote/cache/disk/casblob",
visibility = ["//visibility:public"],
deps = [
"//utils/zstdpool:go_default_library",
"@com_github_klauspost_compress//zstd:go_default_library",
"@com_github_mostynb_zstdpool_syncpool//:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = ["casblob_test.go"],
embed = [":go_default_library"],
)
Loading