Provide random access to cached NARs

E.g.

  $ time nix cat-store --store https://cache.nixos.org?local-nar-cache=/tmp/nars \
    /nix/store/b0w2hafndl09h64fhb86kw6bmhbmnpm1-blender-2.79/share/icons/hicolor/scalable/apps/blender.svg > /dev/null
  real    0m4.139s

  $ time nix cat-store --store https://cache.nixos.org?local-nar-cache=/tmp/nars \
    /nix/store/b0w2hafndl09h64fhb86kw6bmhbmnpm1-blender-2.79/share/icons/hicolor/scalable/apps/blender.svg > /dev/null
  real    0m0.024s

(Before, the second call took ~0.220s.)

This will use a NAR listing in
/tmp/nars/b0w2hafndl09h64fhb86kw6bmhbmnpm1.ls containing all metadata,
including the offsets of regular files inside the NAR. Thus, we don't
need to read the entire NAR. (We do read the entire listing, but
that's generally pretty small. We could use a SQLite DB by borrowing
some more code from nixos-channel-scripts/file-cache.hh.)

This is primarily useful when Hydra is serving files from an S3 binary
cache, in particular when you have giant NARs. E.g. we had some 12 GiB
NARs, so accessing individuals files was pretty slow.
This commit is contained in:
Eelco Dolstra 2017-12-07 00:50:46 +01:00
parent 338f29dbd4
commit 2df9cbeb47
5 changed files with 203 additions and 109 deletions

View file

@ -1,5 +1,10 @@
#include "remote-fs-accessor.hh"
#include "nar-accessor.hh"
#include "json.hh"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
namespace nix {
@ -11,20 +16,30 @@ RemoteFSAccessor::RemoteFSAccessor(ref<Store> store, const Path & cacheDir)
createDirs(cacheDir);
}
Path RemoteFSAccessor::makeCacheFile(const Path & storePath)
Path RemoteFSAccessor::makeCacheFile(const Path & storePath, const std::string & ext)
{
assert(cacheDir != "");
return fmt("%s/%s.nar", cacheDir, storePathToHash(storePath));
return fmt("%s/%s.%s", cacheDir, storePathToHash(storePath), ext);
}
void RemoteFSAccessor::addToCache(const Path & storePath, const std::string & nar)
void RemoteFSAccessor::addToCache(const Path & storePath, const std::string & nar,
ref<FSAccessor> narAccessor)
{
try {
if (cacheDir == "") return;
/* FIXME: do this asynchronously. */
writeFile(makeCacheFile(storePath), nar);
} catch (...) {
ignoreException();
nars.emplace(storePath, narAccessor);
if (cacheDir != "") {
try {
std::ostringstream str;
JSONPlaceholder jsonRoot(str);
listNar(jsonRoot, narAccessor, "", true);
writeFile(makeCacheFile(storePath, "ls"), str.str());
/* FIXME: do this asynchronously. */
writeFile(makeCacheFile(storePath, "nar"), nar);
} catch (...) {
ignoreException();
}
}
}
@ -42,20 +57,49 @@ std::pair<ref<FSAccessor>, Path> RemoteFSAccessor::fetch(const Path & path_)
if (i != nars.end()) return {i->second, restPath};
StringSink sink;
std::string listing;
Path cacheFile;
try {
if (cacheDir != "")
*sink.s = nix::readFile(makeCacheFile(storePath));
} catch (SysError &) { }
if (cacheDir != "" && pathExists(cacheFile = makeCacheFile(storePath, "nar"))) {
if (sink.s->empty()) {
store->narFromPath(storePath, sink);
addToCache(storePath, *sink.s);
try {
listing = nix::readFile(makeCacheFile(storePath, "ls"));
auto narAccessor = makeLazyNarAccessor(listing,
[cacheFile](uint64_t offset, uint64_t length) {
AutoCloseFD fd = open(cacheFile.c_str(), O_RDONLY | O_CLOEXEC);
if (!fd)
throw SysError("opening NAR cache file '%s'", cacheFile);
if (lseek(fd.get(), offset, SEEK_SET) != (off_t) offset)
throw SysError("seeking in '%s'", cacheFile);
std::string buf(length, 0);
readFull(fd.get(), (unsigned char *) buf.data(), length);
return buf;
});
nars.emplace(storePath, narAccessor);
return {narAccessor, restPath};
} catch (SysError &) { }
try {
*sink.s = nix::readFile(cacheFile);
auto narAccessor = makeNarAccessor(sink.s);
nars.emplace(storePath, narAccessor);
return {narAccessor, restPath};
} catch (SysError &) { }
}
auto accessor = makeNarAccessor(sink.s);
nars.emplace(storePath, accessor);
return {accessor, restPath};
store->narFromPath(storePath, sink);
auto narAccessor = makeNarAccessor(sink.s);
addToCache(storePath, *sink.s, narAccessor);
return {narAccessor, restPath};
}
FSAccessor::Stat RemoteFSAccessor::stat(const Path & path)