Skip to content

Commit a2f3fc2

Browse files
authored
Filestore cache cleaner performance tweaks (#1282)
1 parent 8477d9f commit a2f3fc2

8 files changed

Lines changed: 180 additions & 11 deletions

File tree

iac/provider-gcp/main.tf

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,11 @@ module "nomad" {
246246
launch_darkly_api_key_secret_name = module.init.launch_darkly_api_key_secret_version.secret
247247

248248
# Filestore
249-
shared_chunk_cache_path = module.cluster.shared_chunk_cache_path
250-
filestore_cache_max_disk_usage_target = var.filestore_max_disk_usage_target
249+
shared_chunk_cache_path = module.cluster.shared_chunk_cache_path
250+
filestore_cache_cleanup_disk_usage_target = var.filestore_cache_cleanup_disk_usage_target
251+
filestore_cache_cleanup_dry_run = var.filestore_cache_cleanup_dry_run
252+
filestore_cache_cleanup_deletions_per_loop = var.filestore_cache_cleanup_deletions_per_loop
253+
filestore_cache_cleanup_files_per_loop = var.filestore_cache_cleanup_files_per_loop
251254
}
252255

253256
module "redis" {

iac/provider-gcp/nomad/clean-nfs-cache.tf

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ resource "nomad_job" "clean_nfs_cache" {
2020
environment = var.environment
2121
clean_nfs_cache_checksum = data.external.filestore_cleanup_checksum.result.hex
2222
nfs_cache_mount_path = var.shared_chunk_cache_path
23-
max_disk_usage_target = var.filestore_cache_max_disk_usage_target
23+
max_disk_usage_target = var.filestore_cache_cleanup_disk_usage_target
24+
dry_run = var.filestore_cache_cleanup_dry_run
25+
deletions_per_loop = var.filestore_cache_cleanup_deletions_per_loop
26+
files_per_loop = var.filestore_cache_cleanup_files_per_loop
2427
})
2528
}

iac/provider-gcp/nomad/jobs/clean-nfs-cache.hcl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ job "filestore-cleanup" {
2222
config {
2323
command = "local/clean-nfs-cache"
2424
args = [
25-
"--dry-run=false",
25+
"--dry-run=${dry_run}",
2626
"--disk-usage-target-percent=${max_disk_usage_target}",
27+
"--files-per-loop=${files_per_loop}",
28+
"--deletions-per-loop=${deletions_per_loop}",
2729
"${nfs_cache_mount_path}",
2830
]
2931
}

iac/provider-gcp/nomad/variables.tf

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,29 @@ variable "shared_chunk_cache_path" {
336336
default = ""
337337
}
338338

339-
variable "filestore_cache_max_disk_usage_target" {
339+
variable "filestore_cache_cleanup_disk_usage_target" {
340340
type = number
341-
description = "The maximum disk usage target for the Filestore cache in percent"
341+
description = "The disk usage target for the Filestore cache in percent"
342+
validation {
343+
condition = var.filestore_cache_cleanup_disk_usage_target >= 0 && var.filestore_cache_cleanup_disk_usage_target < 100
344+
error_message = "Must be between 0 and 100"
345+
}
346+
}
347+
348+
variable "filestore_cache_cleanup_dry_run" {
349+
type = bool
350+
}
351+
352+
variable "filestore_cache_cleanup_deletions_per_loop" {
353+
type = number
354+
validation {
355+
condition = var.filestore_cache_cleanup_deletions_per_loop > 0
356+
error_message = "Must be greater than 0"
357+
}
358+
}
359+
360+
variable "filestore_cache_cleanup_files_per_loop" {
361+
type = number
342362
}
343363

344364
variable "dockerhub_remote_repository_url" {

iac/provider-gcp/variables.tf

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,12 +430,27 @@ variable "filestore_cache_capacity_gb" {
430430
default = 0
431431
}
432432

433-
variable "filestore_max_disk_usage_target" {
433+
variable "filestore_cache_cleanup_disk_usage_target" {
434434
type = number
435435
description = "The max disk usage target of the Filestore"
436436
default = 90
437437
}
438438

439+
variable "filestore_cache_cleanup_dry_run" {
440+
type = bool
441+
default = false
442+
}
443+
444+
variable "filestore_cache_cleanup_files_per_loop" {
445+
type = number
446+
default = 10000
447+
}
448+
449+
variable "filestore_cache_cleanup_deletions_per_loop" {
450+
type = number
451+
default = 900
452+
}
453+
439454
variable "min_cpu_platform" {
440455
type = string
441456
default = "Intel Skylake"

packages/orchestrator/cmd/clean-nfs-cache/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,10 @@ func parseArgs() (string, opts, error) {
300300
flags := flag.NewFlagSet("clean-nfs-cache", flag.ExitOnError)
301301

302302
var opts opts
303-
flags.Float64Var(&opts.targetDiskUsagePercent, "disk-usage-target-percent", 10, "disk usage target as a % (0-100)")
303+
flags.Float64Var(&opts.targetDiskUsagePercent, "disk-usage-target-percent", 90, "disk usage target as a % (0-100)")
304304
flags.BoolVar(&opts.dryRun, "dry-run", true, "dry run")
305-
flags.IntVar(&opts.filesPerLoop, "files-per-iteration", 10000, "number of files to gather metadata for per loop")
306-
flags.Int64Var(&opts.filesToDeletePerLoop, "max-files-per-iteration", 100, "maximum number of files to delete per loop")
305+
flags.IntVar(&opts.filesPerLoop, "files-per-loop", 10000, "number of files to gather metadata for per loop")
306+
flags.Int64Var(&opts.filesToDeletePerLoop, "deletions-per-loop", 100, "maximum number of files to delete per loop")
307307

308308
args := os.Args[1:] // skip the command name
309309
if err := flags.Parse(args); err != nil {

packages/orchestrator/cmd/clean-nfs-cache/pkg/cache.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"math/rand"
66
"os"
77
"path/filepath"
8+
"slices"
89
)
910

1011
type ListingCache struct {
@@ -26,7 +27,31 @@ func NewListingCache(root string) *ListingCache {
2627

2728
func (c *ListingCache) Decache(path string) {
2829
dirName := filepath.Dir(path)
29-
delete(c.cache, dirName)
30+
items, ok := c.cache[dirName]
31+
if !ok {
32+
return
33+
}
34+
35+
index := slices.IndexFunc(items, func(e cacheEntry) bool {
36+
return e.path == path
37+
})
38+
items = removeByIndex(items, index)
39+
c.cache[dirName] = items
40+
}
41+
42+
func removeByIndex[E any](items []E, index int) []E {
43+
if index < 0 || index >= len(items) {
44+
return items
45+
}
46+
47+
switch index {
48+
case 0:
49+
return items[1:]
50+
case len(items) - 1:
51+
return items[:len(items)-1]
52+
default:
53+
return append(items[:index], items[index+1:]...)
54+
}
3055
}
3156

3257
func (c *ListingCache) GetRandomFile() (string, error) {

packages/orchestrator/cmd/clean-nfs-cache/pkg/cache_test.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,104 @@ func TestGetRandomFile(t *testing.T) {
7575
assert.Empty(t, f)
7676
})
7777
}
78+
79+
func TestListingCache_Decache(t *testing.T) {
80+
t.Parallel()
81+
82+
testCases := map[string]struct {
83+
path string
84+
cached []string
85+
expected []string
86+
}{
87+
"not in cache": {
88+
path: "c",
89+
cached: []string{"a", "b"},
90+
expected: []string{"a", "b"},
91+
},
92+
"in cache": {
93+
path: "c",
94+
cached: []string{"a", "b", "c"},
95+
expected: []string{"a", "b"},
96+
},
97+
}
98+
99+
for name, testCase := range testCases {
100+
t.Run(name, func(t *testing.T) {
101+
t.Parallel()
102+
103+
basePath := "/a/b"
104+
105+
fullPath := filepath.Join(basePath, testCase.path)
106+
items := buildItemsFromPaths(basePath, testCase.cached)
107+
108+
lc := ListingCache{
109+
root: basePath,
110+
cache: map[string][]cacheEntry{
111+
basePath: items,
112+
},
113+
}
114+
115+
lc.Decache(fullPath)
116+
117+
expected := buildItemsFromPaths(basePath, testCase.expected)
118+
assert.Equal(t, expected, lc.cache[basePath])
119+
})
120+
}
121+
}
122+
123+
func buildItemsFromPaths(basePath string, input []string) []cacheEntry {
124+
var items []cacheEntry
125+
for _, path := range input {
126+
items = append(items, cacheEntry{
127+
path: filepath.Join(basePath, path),
128+
isDir: false,
129+
})
130+
}
131+
return items
132+
}
133+
134+
func TestRemoveByIndex(t *testing.T) {
135+
t.Parallel()
136+
137+
testCases := map[string]struct {
138+
input []string
139+
index int
140+
expected []string
141+
}{
142+
"empty": {},
143+
"only": {
144+
input: []string{"one"},
145+
index: 0,
146+
expected: []string{},
147+
},
148+
"first": {
149+
input: []string{"one", "two", "three"},
150+
index: 0,
151+
expected: []string{"two", "three"},
152+
},
153+
"middle": {
154+
input: []string{"one", "two", "three"},
155+
index: 1,
156+
expected: []string{"one", "three"},
157+
},
158+
"last": {
159+
input: []string{"one", "two", "three"},
160+
index: 2,
161+
expected: []string{"one", "two"},
162+
},
163+
"missing": {
164+
input: []string{"one", "two", "three"},
165+
index: -1,
166+
expected: []string{"one", "two", "three"},
167+
},
168+
}
169+
170+
for name, testCase := range testCases {
171+
t.Run(name, func(t *testing.T) {
172+
t.Parallel()
173+
174+
actual := removeByIndex(testCase.input, testCase.index)
175+
assert.Equal(t, testCase.expected, actual)
176+
})
177+
}
178+
}

0 commit comments

Comments
 (0)