Skip to content

feat: Prefix keys with strategy name#129

Open
nssherpa wants to merge 3 commits intomainfrom
nsherpa/s3-strategy-prefix
Open

feat: Prefix keys with strategy name#129
nssherpa wants to merge 3 commits intomainfrom
nsherpa/s3-strategy-prefix

Conversation

@nssherpa
Copy link
Collaborator

@nssherpa nssherpa commented Feb 19, 2026

Testing

Per strategy name directory in the cache folder

Screenshot 2026-02-19 at 8 25 43 PM Screenshot 2026-02-19 at 8 01 44 PM Screenshot 2026-02-19 at 8 28 04 PM
Every 1.0s: curl -s http://localhost:8080/api/v1/stats | jq                                                                                                             BLKL5XW75Q41X.local: Thu Feb 19 20:28:34 2026
                                                                                                                                                                                                        in 0.058s (0)
{
  "objects": 5,
  "size": 3055418,
  "capacity": 5242880
}

@nssherpa nssherpa force-pushed the nsherpa/s3-strategy-prefix branch from d02b0ff to 2fd1efb Compare February 19, 2026 23:38
@nssherpa nssherpa force-pushed the nsherpa/s3-strategy-prefix branch 4 times, most recently from 8cc22b8 to 3935ba2 Compare February 20, 2026 02:27
@nssherpa nssherpa force-pushed the nsherpa/s3-strategy-prefix branch 3 times, most recently from 63232fe to c7ba70b Compare February 20, 2026 04:08
@nssherpa nssherpa force-pushed the nsherpa/s3-strategy-prefix branch from c7ba70b to 9c72769 Compare February 20, 2026 04:23
@nssherpa nssherpa marked this pull request as ready for review February 20, 2026 04:28
@nssherpa nssherpa requested a review from a team as a code owner February 20, 2026 04:28
@nssherpa nssherpa requested review from alecthomas and removed request for a team February 20, 2026 04:28
c := *d
c.namespace = namespace
return &c
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lmk if this is how you were thinking to have the same underlying cache and inject the namespace from the registry.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep this seems like the cleanest way!

Put PutCmd `cmd:"" help:"Upload object to cache." group:"Operations:"`
Delete DeleteCmd `cmd:"" help:"Remove object from cache." group:"Operations:"`
URL string `help:"Remote cache server URL." default:"http://127.0.0.1:8080"`
Namespace string `help:"Namespace for organizing cache objects." default:""`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this should be a positional arg in each subcommand instead? Or if not, it should have a short flag so it's easy to use.

Subcommand:

cachew get <namespace> <key>

Flag:

cachew -n <namespace> get <key>

I feel like 99% of the time you're going to need a namespace, so the former is probably preferable.

Stat StatCmd `cmd:"" help:"Show metadata for cached object." group:"Operations:"`
Put PutCmd `cmd:"" help:"Upload object to cache." group:"Operations:"`
Delete DeleteCmd `cmd:"" help:"Remove object from cache." group:"Operations:"`
ListNamespaces ListNamespacesCmd `cmd:"" help:"List available namespaces in cache." group:"Operations:"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename this to Namespaces.

Suggested change
ListNamespaces ListNamespacesCmd `cmd:"" help:"List available namespaces in cache." group:"Operations:"`
Namespaces ListNamespacesCmd `cmd:"" help:"List available namespaces in cache." group:"Operations:"`

Comment on lines +416 to +417
if err := d.db.deleteAll(sizeEvictedEntries); err != nil {
return errors.Errorf("failed to delete size-evicted metadata: %w", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this call deleteExpiredEntries like the other one?

c := *d
c.namespace = namespace
return &c
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep this seems like the cleanest way!

}

namespaceBucket := tx.Bucket(namespaceBucketName)
return errors.WithStack(namespaceBucket.Put(dbKey, []byte(namespace)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me why we need this extra bucket? AFAICT it's basically storing <namespace>/<sha> -> <namespace> but if we have the namespace in the key, we can just extract it?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTOH a <namespace> -> true mapping might be useful, though we could probably just keep that in memory computed from the initial walk, as the number of namespaces is never going to be very high.

var key Key

// Check format: composite "namespace/hexkey" or raw 32-byte key
slashIdx := bytes.IndexByte(k, '/')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using bytes.Cut(k, []byte("/")) would replace most of this switch here.

Comment on lines +14 to +20
func TestDiskNamespaceIsolation(t *testing.T) {
dir := t.TempDir()
_, ctx := logging.Configure(t.Context(), logging.Config{Level: slog.LevelDebug})

// Create base cache
baseCache, err := cache.NewDisk(ctx, cache.DiskConfig{
Root: dir,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namespace tests should be moved into the cachetest suite so that it tests namespacing uniformly across all implementations.

// Namespace creates a namespaced view (which is ignored by remote cache).
// Remote cache ignores namespaces since the server-side API v1 handles namespacing.
func (c *Remote) Namespace(_ string) Cache {
// Remote cache doesn't use namespacing on client side
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true? How else would the namespace get conveyed to the server?

for ns := range namespaceSet {
namespaces = append(namespaces, ns)
}
return namespaces, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always good to stablesort values out of a map so tests don't become flaky.

@@ -37,6 +37,7 @@ func NewAPIV1(ctx context.Context, _ struct{}, cache cache.Cache, mux Mux) (*API
mux.Handle("POST /api/v1/object/{key}", http.HandlerFunc(s.putObject))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like namespaces get through to the server in any way?

Suggested change
mux.Handle("POST /api/v1/object/{key}", http.HandlerFunc(s.putObject))
mux.Handle("POST /api/v1/object/{namespace}/{key}", http.HandlerFunc(s.putObject))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments