diff --git a/cmd/spinifex/cmd/admin.go b/cmd/spinifex/cmd/admin.go index 750a96662..9253cfaea 100644 --- a/cmd/spinifex/cmd/admin.go +++ b/cmd/spinifex/cmd/admin.go @@ -903,8 +903,10 @@ func runimagesRemoveCmd(cmd *cobra.Command, args []string) { os.Exit(1) } - fmt.Printf("✅ Removed AMI %s (freed %s across %d objects).\n", - imageID, utils.HumanBytes(utils.SafeInt64ToUint64(res.BytesFreed)), res.ObjectsDeleted) + // BytesDeleted is logical: predastore reclaims the underlying disk space + // asynchronously via background compaction, not at delete time. + fmt.Printf("✅ Removed AMI %s (%d objects, %s marked for deletion; disk space is reclaimed by background compaction).\n", + imageID, res.ObjectsDeleted, utils.HumanBytes(utils.SafeInt64ToUint64(res.BytesDeleted))) } func printDependents(w io.Writer, d admin.Dependents) { diff --git a/spinifex/admin/images_remove.go b/spinifex/admin/images_remove.go index e772ad6be..7c0e72efb 100644 --- a/spinifex/admin/images_remove.go +++ b/spinifex/admin/images_remove.go @@ -29,9 +29,14 @@ type RemoveImageOpts struct { } // RemoveImageResult summarises what was deleted from object storage. +// +// BytesDeleted is the logical size of the deleted objects, not physical disk +// space: predastore reclaims a deleted object's bytes asynchronously via +// background compaction, so this count can be freed well after the call +// returns, or briefly not at all if compaction is stalled. type RemoveImageResult struct { ObjectsDeleted int - BytesFreed int64 + BytesDeleted int64 } // Dependents lists every resource that transitively backs an admin-imported @@ -259,7 +264,7 @@ func RemoveSystemImage(store objectstore.ObjectStore, bucket string, opts Remove return nil, fmt.Errorf("delete config: %w", err) } result.ObjectsDeleted += n - result.BytesFreed += b + result.BytesDeleted += b } // Step 2: drop the rest of ami-/ (chunks, WAL, checkpoints). @@ -268,7 +273,7 @@ func RemoveSystemImage(store objectstore.ObjectStore, bucket string, opts Remove return nil, fmt.Errorf("delete ami prefix: %w", err) } result.ObjectsDeleted += n - result.BytesFreed += b + result.BytesDeleted += b // Step 3: drop snap-/ (the viperblock-internal snap checkpoint). n, b, err = deletePrefix(store, bucket, SnapPrefix(opts.ImageID)+"/") @@ -276,12 +281,12 @@ func RemoveSystemImage(store objectstore.ObjectStore, bucket string, opts Remove return nil, fmt.Errorf("delete snap prefix: %w", err) } result.ObjectsDeleted += n - result.BytesFreed += b + result.BytesDeleted += b slog.Info("RemoveSystemImage completed", "imageId", opts.ImageID, "objectsDeleted", result.ObjectsDeleted, - "bytesFreed", result.BytesFreed, + "bytesDeleted", result.BytesDeleted, "force", opts.Force, ) return result, nil diff --git a/spinifex/admin/images_remove_test.go b/spinifex/admin/images_remove_test.go index 44bc8e96d..8f69cbb10 100644 --- a/spinifex/admin/images_remove_test.go +++ b/spinifex/admin/images_remove_test.go @@ -128,7 +128,7 @@ func TestRemoveSystemImage_HappyPath(t *testing.T) { res, err := RemoveSystemImage(store, testRemoveBucket, RemoveImageOpts{ImageID: id}) require.NoError(t, err) assert.Equal(t, preview.AMIObjectCount+preview.SnapObjectCount, res.ObjectsDeleted) - assert.Equal(t, preview.AMIBytesTotal+preview.SnapBytesTotal, res.BytesFreed) + assert.Equal(t, preview.AMIBytesTotal+preview.SnapBytesTotal, res.BytesDeleted) assert.Equal(t, 0, store.Count()) }