@@ -15,12 +15,20 @@ if [ -z "${NIX_CACHE_S3_STORE_URL:-}" ]; then
1515 exit 1
1616fi
1717
18+query_jobs="${NIX_CACHE_QUERY_JOBS:-16}"
19+if [[ ! "$query_jobs" =~ ^[1-9][0-9]{0,2}$ ]] || (( query_jobs > 128 )); then
20+ echo "NIX_CACHE_QUERY_JOBS must be an integer between 1 and 128." >&2
21+ exit 1
22+fi
23+
1824key_file="$RUNNER_TEMP/nix-cache-private-key"
1925paths_file="$RUNNER_TEMP/nix-built-paths"
26+closure_file="$RUNNER_TEMP/nix-built-closure"
2027
2128cleanup() {
2229 rm -f "$key_file"
2330 rm -f "$paths_file"
31+ rm -f "$closure_file"
2432}
2533trap cleanup EXIT
2634
@@ -54,6 +62,38 @@ append_store_param parallel-compression true
5462path_count="$(wc -l < "$paths_file" | tr -d ' ')"
5563echo "Uploading $path_count Nix paths to $NIX_CACHE_S3_STORE_URL"
5664started_at="$SECONDS"
57−xargs -r nix copy --to "$upload_url" < "$paths_file"
58−echo "Uploaded $path_count Nix paths to $NIX_CACHE_S3_STORE_URL in $((SECONDS - started_at))s"
5965
66+if (( query_jobs > 1 )); then
67+ stage_started_at="$SECONDS"
68+ echo "Resolving the dependency closure of $path_count recorded paths..."
69+ nix path-info --recursive --stdin < "$paths_file" > "$closure_file"
70+ closure_count="$(wc -l < "$closure_file" | tr -d ' ')"
71+ echo "Resolved $closure_count paths including dependencies in $((SECONDS - stage_started_at))s"
72+
73+ # Nix's S3 metadata query pool is limited to the number of CPU cores.
74+ # Separate processes share its on-disk narinfo cache, including missing
75+ # paths. Prefetch disjoint batches so the recursive copy can reuse these
76+ # checks without uploading overlapping closures in parallel.
77+ batch_size="$(((closure_count + query_jobs - 1) / query_jobs))"
78+ if (( batch_size > 32 )); then batch_size=32; fi
79+ if (( batch_size < 1 )); then batch_size=1; fi
80+ stage_started_at="$SECONDS"
81+ echo "Checking S3 cache metadata for $closure_count paths with up to $query_jobs parallel queries..."
82+ xargs -r -P "$query_jobs" -n "$batch_size" bash -c '
83+ set -euo pipefail
84+ upload_url="$1"
85+ shift
86+ # --print-invalid permits absent paths. Unlike nix path-info, this
87+ # only checks the destination, without querying other substituters.
88+ # The S3 validity check fetches and caches the narinfo metadata.
89+ nix-store --store "$upload_url" --check-validity --print-invalid "$@" > /dev/null
90+ echo "Checked cache metadata for $# paths in ${SECONDS}s"
91+ ' bash "$upload_url" < "$closure_file"
92+ echo "Checked S3 cache metadata in $((SECONDS - stage_started_at))s"
93+fi
94+
95+stage_started_at="$SECONDS"
96+echo "Copying missing paths and dependencies to S3..."
97+nix copy --to "$upload_url" --stdin < "$paths_file"
98+echo "Copied missing paths in $((SECONDS - stage_started_at))s"
99+echo "Uploaded $path_count Nix paths to $NIX_CACHE_S3_STORE_URL in $((SECONDS - started_at))s"