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
22 changes: 22 additions & 0 deletions doc/manual/rl-next/gc-age-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
synopsis: "Store GCs can now be restricted with a minimum age"
prs: [14725]
issues: [7572]
---

Nix store GCs invoked using either `nix-collect-garbage` or `nix store gc` may
now be restricted to only deleting paths older than a certain minimum age, i.e.
whose most recent usage is more than *n* days in the past. "Usage" is
intentionally defined ambiguously, however in general all operations which
produce/require the presence of a given store path count as "usage".


Example usage:

```bash
# Delete store paths older than 7 days
nix store gc --older-than 7d

# Alternatively:
nix-collect-garbage --path-older-than 7d
```
14 changes: 12 additions & 2 deletions doc/manual/source/command-ref/nix-collect-garbage.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# Synopsis

`nix-collect-garbage` [`--delete-old`] [`-d`] [`--delete-older-than` *period*] [`--max-freed` *bytes*] [`--dry-run`]
`nix-collect-garbage` [`--delete-old`] [`-d`] [`--delete-older-than` *period*] [`--delete-paths-older-than` *period*] [`--max-freed` *bytes*] [`--dry-run`]

# Description

Expand Down Expand Up @@ -62,7 +62,17 @@ These options are for deleting old [profiles] prior to deleting unreachable [sto
This is the equivalent of invoking [`nix-env --delete-generations <period>`](@docroot@/command-ref/nix-env/delete-generations.md#generations-time) on each found profile.
See the documentation of that command for additional information about the *period* argument.

- <span id="opt-max-freed">[`--max-freed`](#opt-max-freed)</span> *bytes*
- <span id="opt-paths-delete-older-than">[`--delete-paths-older-than`](#opt-delete-paths-older-than)</span> *period*

Only delete store paths older than the specified amount, i.e. restrict the GC
to only deleting store paths whose most recent usage is more than *N* days in
the past. *period* is a value such as `30d`, which would mean 30 days.

"Usage" is intentionally defined ambiguously, however in general all
operations which produce/require the presence of a given store path count as
"usage".

- <span id="opt-max-freed">[`--max-freed`](#opt-max-freed)</span> *bytes*

<!-- duplication from https://github.com/NixOS/nix/blob/442a2623e48357ff72c77bb11cf2cf06d94d2f90/doc/manual/source/command-ref/nix-store/gc.md?plain=1#L39-L44 -->

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ $defs:
# impure
- deriver
- registrationTime
- lastUsageTime
- ultimate
- signatures
properties:
Expand Down Expand Up @@ -147,6 +148,15 @@ $defs:

> This is an "impure" field that may not be included in certain contexts.

lastUsageTime:
type: ["integer", "null"]
title: Last Usage Time
description: |
If known, when this derivation was last used (Unix timestamp).
Otherwise `null`.

> This is an "impure" field that may not be included in certain contexts.

ultimate:
type: boolean
title: Ultimate
Expand Down Expand Up @@ -195,6 +205,7 @@ $defs:
# impure
- deriver
- registrationTime
- lastUsageTime
- ultimate
- signatures
# nar
Expand All @@ -211,6 +222,7 @@ $defs:
ca: { $ref: "#/$defs/base/properties/ca" }
deriver: { $ref: "#/$defs/impure/properties/deriver" }
registrationTime: { $ref: "#/$defs/impure/properties/registrationTime" }
lastUsageTime: { $ref: "#/$defs/impure/properties/lastUsageTime" }
ultimate: { $ref: "#/$defs/impure/properties/ultimate" }
signatures: { $ref: "#/$defs/impure/properties/signatures" }
closureSize: { $ref: "#/$defs/impure/properties/closureSize" }
Expand Down
2 changes: 2 additions & 0 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,8 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value ** args, V
auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode)
state.store->ensurePath(path2);
else
state.store->bumpLastUsageTime(path2);
context.insert(NixStringContextElem::Opaque{.path = path2});
v.mkString(path.abs(), context, state.mem);
}
Expand Down
2 changes: 2 additions & 0 deletions src/libexpr/primops/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value ** arg
auto namePath = state.store->parseStorePath(name);
if (!settings.readOnlyMode)
state.store->ensurePath(namePath);
else
state.store->bumpLastUsageTime(namePath);
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");

if (auto attr = i.value->attrs()->get(sPath)) {
Expand Down
1 change: 1 addition & 0 deletions src/libstore-tests/data/dummy-store/one-flat-file.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"narSize": 120,
"references": [],
"registrationTime": null,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand Down
1 change: 1 addition & 0 deletions src/libstore-tests/data/nar-info/json-1/impure.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"/nix/store/n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
],
"registrationTime": 23423,
"lastUsageTime": 34534,
"signatures": [
"asdf",
"qwer"
Expand Down
1 change: 1 addition & 0 deletions src/libstore-tests/data/nar-info/json-2/impure.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
],
"registrationTime": 23423,
"lastUsageTime": 34534,
"signatures": [
"asdf",
"qwer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"narSize": 0,
"references": [],
"registrationTime": null,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 1
Expand Down
1 change: 1 addition & 0 deletions src/libstore-tests/data/path-info/json-1/impure.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"/nix/store/n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
],
"registrationTime": 23423,
"lastUsageTime": 34534,
"signatures": [
"asdf",
"qwer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"narSize": 0,
"references": [],
"registrationTime": null,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand Down
1 change: 1 addition & 0 deletions src/libstore-tests/data/path-info/json-2/impure.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
],
"registrationTime": 23423,
"lastUsageTime": 34534,
"signatures": [
"asdf",
"qwer"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"narSize": 34878,
"references": [],
"registrationTime": null,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand All @@ -27,6 +28,7 @@
"g1w7hyyyy1w7hy3qg1w7hy3qgqqqqy3q-foo.drv"
],
"registrationTime": null,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"g1w7hyyyy1w7hy3qg1w7hy3qgqqqqy3q-foo.drv"
],
"registrationTime": null,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand All @@ -37,6 +38,7 @@
"n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
],
"registrationTime": null,
"lastUsageTime": null,
"signatures": [
"fake-sig-1",
"fake-sig-2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"narSize": 34878,
"references": [],
"registrationTime": 23423,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand All @@ -27,6 +28,7 @@
"g1w7hyyyy1w7hy3qg1w7hy3qgqqqqy3q-foo.drv"
],
"registrationTime": 23423,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"path": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
"references": [],
"registrationTime": 23423,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand All @@ -30,6 +31,7 @@
"g1w7hyyyy1w7hy3qg1w7hy3qgqqqqy3q-foo"
],
"registrationTime": 23423,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"path": "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar",
"references": [],
"registrationTime": 23423,
"lastUsageTime": null,
"signatures": [],
"ultimate": true,
"version": 2
Expand All @@ -30,6 +31,7 @@
"g1w7hyyyy1w7hy3qg1w7hy3qgqqqqy3q-foo"
],
"registrationTime": 23423,
"lastUsageTime": null,
"signatures": [
"fake-sig-1",
"fake-sig-2"
Expand Down Expand Up @@ -59,6 +61,7 @@
"n5wkd9frr45pa74if5gpz9j7mifg27fh-foo"
],
"registrationTime": 23423,
"lastUsageTime": null,
"signatures": [],
"ultimate": false,
"version": 2
Expand Down
1 change: 1 addition & 0 deletions src/libstore-tests/nar-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ static NarInfo makeNarInfo(const Store & store, bool includeImpureInfo)
"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
};
info.registrationTime = 23423;
info.lastUsageTime = 34534;
info.ultimate = true;
info.sigs = {"asdf", "qwer"};

Expand Down
1 change: 1 addition & 0 deletions src/libstore-tests/path-info.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ static ValidPathInfo makeFullKeyed(const Store & store, bool includeImpureInfo)
"g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar.drv",
};
info.registrationTime = 23423;
info.lastUsageTime = 34534;
info.ultimate = true;
info.sigs = {"asdf", "qwer"};
}
Expand Down
1 change: 1 addition & 0 deletions src/libstore/binary-cache-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ void BinaryCacheStore::addToStore(
{
if (!repair && isValidPath(info.path)) {
// FIXME: copyNAR -> null sink
bumpLastUsageTime(info.path);
narSource.drain();
return;
}
Expand Down
4 changes: 3 additions & 1 deletion src/libstore/build/derivation-building-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,10 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution(bool storeDerivation)
}

for (auto & i : drv->inputSrcs) {
if (worker.store.isValidPath(i))
if (worker.store.isValidPath(i)) {
worker.store.bumpLastUsageTime(i);
continue;
}
if (!settings.useSubstitutes)
throw Error(
"dependency '%s' of '%s' does not exist, and substitution is disabled",
Expand Down
1 change: 1 addition & 0 deletions src/libstore/build/derivation-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Goal::Co DerivationGoal::haveDerivation(bool storeDerivation)

/* If they are all valid, then we're done. */
if (checkResult && checkResult->second == PathStatus::Valid && buildMode == bmNormal) {
worker.store.bumpLastUsageTime(checkResult->first.outPath);
co_return doneSuccess(BuildResult::Success::AlreadyValid, checkResult->first);
}

Expand Down
4 changes: 3 additions & 1 deletion src/libstore/build/derivation-trampoline-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,10 @@ Goal::Co DerivationTrampolineGoal::init()
*/
auto drv = [&] {
for (auto * drvStore : {&worker.evalStore, &worker.store})
if (drvStore->isValidPath(drvPath))
if (drvStore->isValidPath(drvPath)) {
drvStore->bumpLastUsageTime(drvPath);
return drvStore->readDerivation(drvPath);
}
assert(false);
}();

Expand Down
4 changes: 3 additions & 1 deletion src/libstore/build/entry-points.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
void Store::ensurePath(const StorePath & path)
{
/* If the path is already valid, we're done. */
if (isValidPath(path))
if (isValidPath(path)) {
bumpLastUsageTime(path);
return;
}

Worker worker(*this, *this);
GoalPtr goal = worker.makePathSubstitutionGoal(path);
Expand Down
1 change: 1 addition & 0 deletions src/libstore/build/substitution-goal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Goal::Co PathSubstitutionGoal::init()

/* If the path already exists we're done. */
if (!repair && worker.store.isValidPath(storePath)) {
worker.store.bumpLastUsageTime(storePath);
co_return doneSuccess(BuildResult::Success::AlreadyValid);
}

Expand Down
4 changes: 3 additions & 1 deletion src/libstore/derivations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,10 @@ StorePath Store::writeDerivation(const Derivation & drv, RepairFlag repair)
{
auto [suffix, contents, references, path] = infoForDerivation(*this, drv);

if (isValidPath(path) && !repair)
if (isValidPath(path) && !repair) {
bumpLastUsageTime(path);
return path;
}

StringSource s{contents};
auto path2 = addToStoreFromDump(
Expand Down
6 changes: 6 additions & 0 deletions src/libstore/gc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,12 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
if (options.action == GCOptions::gcDeleteSpecific && !options.pathsToDelete.count(*path))
return;

/* If this path is too young, bail out */
if (options.olderThan.has_value() && options.olderThan.value() < queryPathInfo(*path)->lastUsageTime) {
debug("not deleting '%s' because it's too young", printStorePath(*path));
return markAlive();
}

{
auto hashPart = path->hashPart();
auto shared(_shared.lock());
Expand Down
5 changes: 5 additions & 0 deletions src/libstore/include/nix/store/gc-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ struct GCOptions
* Stop after at least `maxFreed` bytes have been freed.
*/
uint64_t maxFreed{std::numeric_limits<uint64_t>::max()};

/**
* Only delete paths older than a certain point in time.
*/
std::optional<time_t> olderThan;
};

struct GCResults
Expand Down
5 changes: 5 additions & 0 deletions src/libstore/include/nix/store/local-overlay-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ private:
*/
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;

/**
* Bump the last usage time in both the lower and upper DBs.
*/
void bumpLastUsageTime(const StorePath & path) override;

/**
* First copy up any lower store realisation with the same key, so we
* merge rather than mask it.
Expand Down
Loading