Files
lix/lix/libstore/binary-cache-store.hh
T
Raito Bezarius 95f15cf94f libstore/binary-cache: default to zstd for compression
libarchive's xz offers single threaded xz compression which is very slow
and provides ~10-20Mbps compression speed in addition to maxing a core.

In exchange, it achieves optimal compression ratios among all our
compression methods.

Nonetheless, xz prevent the saturation of 1Gbps+ connections and slow
down significantly decompression for end users. As these connections and
faster hardware is becoming prevalent for cache servers and clients, we
offer to default to zstd.

Lix is a "compress once, decompress many times" application. To avoid
incurring a high penalty to end users very sensitive to compress ratio
(very slow Internet connections), we dampen the consequences of
switching to zstd by increasing the default zstd level to 12.

On one example, xz will compress a 4.4GB file to 632MB, zstd on 12 will
compress it to 775MB, that is a ~18 % increase over the optimal xz
compression. zstd took 18 seconds to produce this file.

Increasing to level 14 leads to a 773MB file while taking 37s.
Increasing to level 16 leads to 735MB file while taking 66s.

Finally, xz took 77s, so a 50 % reduction in time taken to compress in
exchange of an increase of 18 % of the compressed size.

This change will reduce issues encountered in #945 but is probably not
the root cause.

References:

- https://discourse.nixos.org/t/switch-cache-nixos-org-to-zstd-to-fix-slow-nixos-updates-nix-downloads/23961

Change-Id: I7beda2bf2c1fed146dcb797b8f85dc290c486ab2
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-10-30 15:10:10 +01:00

197 lines
5.9 KiB
C++

#pragma once
///@file
#include "lix/libstore/crypto.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libstore/log-store.hh"
#include "lix/libutil/archive.hh"
#include "lix/libutil/async-io.hh"
#include "lix/libutil/pool.hh"
#include <atomic>
namespace nix {
struct NarInfo;
struct BinaryCacheStoreConfig : virtual StoreConfig
{
using StoreConfig::StoreConfig;
const Setting<std::string> compression{
this,
"zstd",
"compression",
"NAR compression method (`xz`, `bzip2`, `gzip`, `zstd`, or `none`)."
};
const Setting<bool> writeNARListing{this, false, "write-nar-listing",
"Whether to write a JSON file that lists the files in each NAR."};
const Setting<bool> writeDebugInfo{this, false, "index-debug-info",
R"(
Whether to index DWARF debug info files by build ID. This allows [`dwarffs`](https://github.com/edolstra/dwarffs) to
fetch debug info on demand
)"};
const Setting<Path> secretKeyFile{this, "", "secret-key",
"Path to the secret key used to sign the binary cache."};
const Setting<Path> localNarCache{this, "", "local-nar-cache",
"Path to a local cache of NARs fetched from this binary cache, used by commands such as `nix store cat`."};
const Setting<bool> parallelCompression{this, false, "parallel-compression",
"Enable multi-threaded compression of NARs. This is currently only available for `xz` and `zstd`."};
const Setting<int> compressionLevel{
this,
-1,
"compression-level",
R"(
The *preset level* to be used when compressing NARs.
The meaning and accepted values depend on the compression method selected.
`-1` specifies that the default compression level should be used.
Note: when using zstd `-1` will select level 12 to approximately match xz compression
ratios at default settings rather than the zstd library default of 3.
)"
};
};
/**
* @note subclasses must implement at least one of the two
* virtual getFile() methods.
*/
class BinaryCacheStore : public virtual Store,
public virtual LogStore
{
private:
std::unique_ptr<SecretKey> secretKey;
protected:
// The prefix under which realisation infos will be stored
const std::string realisationsPrefix = "realisations";
BinaryCacheStore(const BinaryCacheStoreConfig & config);
public:
BinaryCacheStoreConfig & config() override = 0;
const BinaryCacheStoreConfig & config() const override = 0;
virtual kj::Promise<Result<bool>>
fileExists(const std::string & path, const Activity * context = nullptr) = 0;
virtual kj::Promise<Result<void>> upsertFile(
const std::string & path,
std::shared_ptr<std::basic_iostream<char>> istream,
const std::string & mimeType,
const Activity * context
) = 0;
kj::Promise<Result<void>> upsertFile(
const std::string & path,
// FIXME: use std::string_view
std::string && data,
const std::string & mimeType,
const Activity * context = nullptr
);
/**
* Dump the contents of the specified file to a sink.
*/
virtual kj::Promise<Result<box_ptr<AsyncInputStream>>>
getFile(const std::string & path, const Activity * context = nullptr) = 0;
virtual kj::Promise<Result<std::optional<std::string>>>
getFileContents(const std::string & path, const Activity * context = nullptr);
public:
virtual kj::Promise<Result<void>> init() override;
private:
std::string narMagic;
std::string narInfoFileFor(const StorePath & storePath);
kj::Promise<Result<void>>
writeNarInfo(ref<NarInfo> narInfo, const Activity * context = nullptr);
kj::Promise<Result<ref<const ValidPathInfo>>> addToStoreCommon(
AsyncInputStream & narSource,
RepairFlag repair,
CheckSigsFlag checkSigs,
const Activity * context,
std::function<ValidPathInfo(HashResult)> mkInfo
);
public:
kj::Promise<Result<bool>>
isValidPathUncached(const StorePath & path, const Activity * context = nullptr) override;
kj::Promise<Result<std::shared_ptr<const ValidPathInfo>>>
queryPathInfoUncached(const StorePath & path, const Activity * context = nullptr) override;
kj::Promise<Result<std::optional<StorePath>>>
queryPathFromHashPart(const std::string & hashPart) override;
kj::Promise<Result<void>> addToStore(
const ValidPathInfo & info,
AsyncInputStream & narSource,
RepairFlag repair,
CheckSigsFlag checkSigs,
const Activity * context
) override;
kj::Promise<Result<StorePath>> addToStoreFromDump(
AsyncInputStream & dump,
std::string_view name,
FileIngestionMethod method,
HashType hashAlgo,
RepairFlag repair,
const StorePathSet & references
) override;
kj::Promise<Result<StorePath>> addToStoreRecursive(
std::string_view name,
const PreparedDump & source,
HashType hashAlgo,
RepairFlag repair) override;
kj::Promise<Result<StorePath>> addToStoreFlat(
std::string_view name,
const Path & srcPath,
HashType hashAlgo,
RepairFlag repair) override;
kj::Promise<Result<StorePath>> addTextToStore(
std::string_view name,
std::string_view s,
const StorePathSet & references,
RepairFlag repair) override;
kj::Promise<Result<box_ptr<AsyncInputStream>>>
narFromPath(const StorePath & path, const Activity * context) override;
ref<FSAccessor> getFSAccessor() override;
kj::Promise<Result<void>>
addSignatures(const StorePath & storePath, const StringSet & sigs) override;
kj::Promise<Result<std::optional<std::string>>> getBuildLogExact(const StorePath & path) override;
kj::Promise<Result<void>> addBuildLog(const StorePath & drvPath, std::string_view log) override;
};
MakeError(NoSuchBinaryCacheFile, Error);
}