Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of pruning slabs after deletion of large number of objects #985

Merged
merged 10 commits into from
Feb 23, 2024
24 changes: 20 additions & 4 deletions stores/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -2729,20 +2729,32 @@ func archiveContracts(ctx context.Context, tx *gorm.DB, contracts []dbContract,
return nil
}

func pruneSlabs(tx *gorm.DB) error {
// delete slabs without any associated slices or buffers
return tx.Exec(`
DELETE
FROM slabs
WHERE NOT EXISTS (SELECT 1 FROM slices WHERE slices.db_slab_id = slabs.id)
AND slabs.db_buffered_slab_id IS NULL
ChrisSchinnerl marked this conversation as resolved.
Show resolved Hide resolved
`).Error
}

// deleteObject deletes an object from the store and prunes all slabs which are
// without an obect after the deletion. That means in case of packed uploads,
// the slab is only deleted when no more objects point to it.
func (s *SQLStore) deleteObject(tx *gorm.DB, bucket string, path string) (numDeleted int64, _ error) {
func (s *SQLStore) deleteObject(tx *gorm.DB, bucket string, path string) (int64, error) {
tx = tx.Where("object_id = ? AND ?", path, sqlWhereBucket("objects", bucket)).
Delete(&dbObject{})
if tx.Error != nil {
return 0, tx.Error
}
numDeleted = tx.RowsAffected
numDeleted := tx.RowsAffected
if numDeleted == 0 {
return 0, nil // nothing to prune if no object was deleted
} else if err := pruneSlabs(tx); err != nil {
return numDeleted, err
}
return
return numDeleted, nil
}

// deleteObjects deletes a batch of objects from the database. The order of
Expand Down Expand Up @@ -2771,8 +2783,12 @@ func (s *SQLStore) deleteObjects(bucket string, path string) (numDeleted int64,
if err := res.Error; err != nil {
return res.Error
}
duration = time.Since(start)
// prune slabs if we deleted an object
rowsAffected = res.RowsAffected
if rowsAffected > 0 {
return pruneSlabs(tx)
}
duration = time.Since(start)
return nil
}); err != nil {
return 0, fmt.Errorf("failed to delete objects: %w", err)
Expand Down
9 changes: 6 additions & 3 deletions stores/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3942,7 +3942,8 @@ func TestSlabCleanupTrigger(t *testing.T) {
}

// delete the object
if err := ss.db.Delete(&obj1).Error; err != nil {
err := ss.RemoveObject(context.Background(), api.DefaultBucketName, obj1.ObjectID)
if err != nil {
t.Fatal(err)
}

Expand All @@ -3955,7 +3956,8 @@ func TestSlabCleanupTrigger(t *testing.T) {
}

// delete second object
if err := ss.db.Delete(&obj2).Error; err != nil {
err = ss.RemoveObject(context.Background(), api.DefaultBucketName, obj2.ObjectID)
if err != nil {
t.Fatal(err)
}

Expand Down Expand Up @@ -3999,7 +4001,8 @@ func TestSlabCleanupTrigger(t *testing.T) {
}

// delete third object
if err := ss.db.Delete(&obj3).Error; err != nil {
err = ss.RemoveObject(context.Background(), api.DefaultBucketName, obj3.ObjectID)
if err != nil {
t.Fatal(err)
}
if err := ss.db.Model(&dbSlab{}).Count(&slabCntr).Error; err != nil {
Expand Down
6 changes: 6 additions & 0 deletions stores/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ func performMigrations(db *gorm.DB, logger *zap.SugaredLogger) error {
return performMigration(tx, dbIdentifier, "00003_idx_objects_size", logger)
},
},
{
ID: "00004_prune_slabs_cascade",
Migrate: func(tx *gorm.DB) error {
return performMigration(tx, dbIdentifier, "00004_prune_slabs_cascade", logger)
},
},
}

// Create migrator.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- add ON DELETE CASCADE to slices
ALTER TABLE slices DROP FOREIGN KEY fk_objects_slabs;
ALTER TABLE slices ADD CONSTRAINT fk_objects_slabs FOREIGN KEY (db_object_id) REFERENCES objects (id) ON DELETE CASCADE;

ALTER TABLE slices DROP FOREIGN KEY fk_multipart_parts_slabs;
ALTER TABLE slices ADD CONSTRAINT fk_multipart_parts_slabs FOREIGN KEY (db_multipart_part_id) REFERENCES multipart_parts (id) ON DELETE CASCADE;

-- add ON DELETE CASCADE to multipart_parts
ALTER TABLE multipart_parts DROP FOREIGN KEY fk_multipart_uploads_parts;
ALTER TABLE multipart_parts ADD CONSTRAINT fk_multipart_uploads_parts FOREIGN KEY (db_multipart_upload_id) REFERENCES multipart_uploads (id) ON DELETE CASCADE;

-- drop triggers
DROP TRIGGER IF EXISTS before_delete_on_objects_delete_slices;
DROP TRIGGER IF EXISTS before_delete_on_multipart_uploads_delete_multipart_parts;
DROP TRIGGER IF EXISTS before_delete_on_multipart_parts_delete_slices;
DROP TRIGGER IF EXISTS after_delete_on_slices_delete_slabs;
40 changes: 3 additions & 37 deletions stores/migrations/mysql/main/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ CREATE TABLE `multipart_parts` (
KEY `idx_multipart_parts_etag` (`etag`),
KEY `idx_multipart_parts_part_number` (`part_number`),
KEY `idx_multipart_parts_db_multipart_upload_id` (`db_multipart_upload_id`),
CONSTRAINT `fk_multipart_uploads_parts` FOREIGN KEY (`db_multipart_upload_id`) REFERENCES `multipart_uploads` (`id`)
CONSTRAINT `fk_multipart_uploads_parts` FOREIGN KEY (`db_multipart_upload_id`) REFERENCES `multipart_uploads` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- dbObject
Expand Down Expand Up @@ -374,8 +374,8 @@ CREATE TABLE `slices` (
KEY `idx_slices_object_index` (`object_index`),
KEY `idx_slices_db_multipart_part_id` (`db_multipart_part_id`),
KEY `idx_slices_db_slab_id` (`db_slab_id`),
CONSTRAINT `fk_multipart_parts_slabs` FOREIGN KEY (`db_multipart_part_id`) REFERENCES `multipart_parts` (`id`),
CONSTRAINT `fk_objects_slabs` FOREIGN KEY (`db_object_id`) REFERENCES `objects` (`id`),
CONSTRAINT `fk_multipart_parts_slabs` FOREIGN KEY (`db_multipart_part_id`) REFERENCES `multipart_parts` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_objects_slabs` FOREIGN KEY (`db_object_id`) REFERENCES `objects` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_slabs_slices` FOREIGN KEY (`db_slab_id`) REFERENCES `slabs` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Expand Down Expand Up @@ -421,39 +421,5 @@ CREATE TABLE `object_user_metadata` (
CONSTRAINT `fk_multipart_upload_user_metadata` FOREIGN KEY (`db_multipart_upload_id`) REFERENCES `multipart_uploads` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- dbObject trigger to delete from slices
CREATE TRIGGER before_delete_on_objects_delete_slices
BEFORE DELETE
ON objects FOR EACH ROW
DELETE FROM slices
WHERE slices.db_object_id = OLD.id;

-- dbMultipartUpload trigger to delete from dbMultipartPart
CREATE TRIGGER before_delete_on_multipart_uploads_delete_multipart_parts
BEFORE DELETE
ON multipart_uploads FOR EACH ROW
DELETE FROM multipart_parts
WHERE multipart_parts.db_multipart_upload_id = OLD.id;

-- dbMultipartPart trigger to delete from slices
CREATE TRIGGER before_delete_on_multipart_parts_delete_slices
BEFORE DELETE
ON multipart_parts FOR EACH ROW
DELETE FROM slices
WHERE slices.db_multipart_part_id = OLD.id;

-- dbSlices trigger to prune slabs
CREATE TRIGGER after_delete_on_slices_delete_slabs
AFTER DELETE
ON slices FOR EACH ROW
DELETE FROM slabs
WHERE slabs.id = OLD.db_slab_id
AND slabs.db_buffered_slab_id IS NULL
AND NOT EXISTS (
SELECT 1
FROM slices
WHERE slices.db_slab_id = OLD.db_slab_id
);

-- create default bucket
INSERT INTO buckets (created_at, name) VALUES (CURRENT_TIMESTAMP, 'default');
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
PRAGMA foreign_keys=off;
-- update constraints on slices
CREATE TABLE `slices_temp` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`db_object_id` integer,`object_index` integer,`db_multipart_part_id` integer,`db_slab_id` integer,`offset` integer,`length` integer,CONSTRAINT `fk_objects_slabs` FOREIGN KEY (`db_object_id`) REFERENCES `objects`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_multipart_parts_slabs` FOREIGN KEY (`db_multipart_part_id`) REFERENCES `multipart_parts`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_slabs_slices` FOREIGN KEY (`db_slab_id`) REFERENCES `slabs`(`id`));
INSERT INTO slices_temp SELECT `id`, `created_at`, `db_object_id`, `object_index`, `db_multipart_part_id`, `db_slab_id`, `offset`, `length` FROM slices;
DROP TABLE slices;
ALTER TABLE slices_temp RENAME TO slices;

CREATE INDEX `idx_slices_object_index` ON `slices`(`object_index`);
CREATE INDEX `idx_slices_db_object_id` ON `slices`(`db_object_id`);
CREATE INDEX `idx_slices_db_slab_id` ON `slices`(`db_slab_id`);
CREATE INDEX `idx_slices_db_multipart_part_id` ON `slices`(`db_multipart_part_id`);

-- update constraints multipart_parts
CREATE TABLE `multipart_parts_temp` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`etag` text,`part_number` integer,`size` integer,`db_multipart_upload_id` integer NOT NULL,CONSTRAINT `fk_multipart_uploads_parts` FOREIGN KEY (`db_multipart_upload_id`) REFERENCES `multipart_uploads`(`id`) ON DELETE CASCADE);
INSERT INTO multipart_parts_temp SELECT * FROM multipart_parts;
DROP TABLE multipart_parts;
ALTER TABLE multipart_parts_temp RENAME TO multipart_parts;

CREATE INDEX `idx_multipart_parts_db_multipart_upload_id` ON `multipart_parts`(`db_multipart_upload_id`);
CREATE INDEX `idx_multipart_parts_part_number` ON `multipart_parts`(`part_number`);
CREATE INDEX `idx_multipart_parts_etag` ON `multipart_parts`(`etag`);
PRAGMA foreign_keys=on;

-- drop triggers
DROP TRIGGER IF EXISTS before_delete_on_objects_delete_slices;
DROP TRIGGER IF EXISTS before_delete_on_multipart_uploads_delete_multipart_parts;
DROP TRIGGER IF EXISTS before_delete_on_multipart_parts_delete_slices;
DROP TRIGGER IF EXISTS after_delete_on_slices_delete_slabs;
42 changes: 2 additions & 40 deletions stores/migrations/sqlite/main/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ CREATE INDEX `idx_contract_sectors_db_contract_id` ON `contract_sectors`(`db_con
CREATE INDEX `idx_contract_sectors_db_sector_id` ON `contract_sectors`(`db_sector_id`);

-- dbMultipartPart
CREATE TABLE `multipart_parts` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`etag` text,`part_number` integer,`size` integer,`db_multipart_upload_id` integer NOT NULL,CONSTRAINT `fk_multipart_uploads_parts` FOREIGN KEY (`db_multipart_upload_id`) REFERENCES `multipart_uploads`(`id`));
CREATE TABLE `multipart_parts` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`etag` text,`part_number` integer,`size` integer,`db_multipart_upload_id` integer NOT NULL,CONSTRAINT `fk_multipart_uploads_parts` FOREIGN KEY (`db_multipart_upload_id`) REFERENCES `multipart_uploads`(`id`) ON DELETE CASCADE);
CREATE INDEX `idx_multipart_parts_db_multipart_upload_id` ON `multipart_parts`(`db_multipart_upload_id`);
CREATE INDEX `idx_multipart_parts_part_number` ON `multipart_parts`(`part_number`);
CREATE INDEX `idx_multipart_parts_etag` ON `multipart_parts`(`etag`);

-- dbSlice
CREATE TABLE `slices` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`db_object_id` integer,`object_index` integer,`db_multipart_part_id` integer,`db_slab_id` integer,`offset` integer,`length` integer,CONSTRAINT `fk_objects_slabs` FOREIGN KEY (`db_object_id`) REFERENCES `objects`(`id`),CONSTRAINT `fk_multipart_parts_slabs` FOREIGN KEY (`db_multipart_part_id`) REFERENCES `multipart_parts`(`id`),CONSTRAINT `fk_slabs_slices` FOREIGN KEY (`db_slab_id`) REFERENCES `slabs`(`id`));
CREATE TABLE `slices` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`db_object_id` integer,`object_index` integer,`db_multipart_part_id` integer,`db_slab_id` integer,`offset` integer,`length` integer,CONSTRAINT `fk_objects_slabs` FOREIGN KEY (`db_object_id`) REFERENCES `objects`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_multipart_parts_slabs` FOREIGN KEY (`db_multipart_part_id`) REFERENCES `multipart_parts`(`id`) ON DELETE CASCADE,CONSTRAINT `fk_slabs_slices` FOREIGN KEY (`db_slab_id`) REFERENCES `slabs`(`id`));
CREATE INDEX `idx_slices_object_index` ON `slices`(`object_index`);
CREATE INDEX `idx_slices_db_object_id` ON `slices`(`db_object_id`);
CREATE INDEX `idx_slices_db_slab_id` ON `slices`(`db_slab_id`);
Expand Down Expand Up @@ -148,43 +148,5 @@ CREATE UNIQUE INDEX `idx_module_event_url` ON `webhooks`(`module`,`event`,`url`)
CREATE TABLE `object_user_metadata` (`id` integer PRIMARY KEY AUTOINCREMENT,`created_at` datetime,`db_object_id` integer DEFAULT NULL,`db_multipart_upload_id` integer DEFAULT NULL,`key` text NOT NULL,`value` text, CONSTRAINT `fk_object_user_metadata` FOREIGN KEY (`db_object_id`) REFERENCES `objects` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_multipart_upload_user_metadata` FOREIGN KEY (`db_multipart_upload_id`) REFERENCES `multipart_uploads` (`id`) ON DELETE SET NULL);
CREATE UNIQUE INDEX `idx_object_user_metadata_key` ON `object_user_metadata`(`db_object_id`,`db_multipart_upload_id`,`key`);

-- dbObject trigger to delete from slices
CREATE TRIGGER before_delete_on_objects_delete_slices
BEFORE DELETE ON objects
BEGIN
DELETE FROM slices
WHERE slices.db_object_id = OLD.id;
END;

-- dbMultipartUpload trigger to delete from dbMultipartPart
CREATE TRIGGER before_delete_on_multipart_uploads_delete_multipart_parts
BEFORE DELETE ON multipart_uploads
BEGIN
DELETE FROM multipart_parts
WHERE multipart_parts.db_multipart_upload_id = OLD.id;
END;

-- dbMultipartPart trigger to delete from slices
CREATE TRIGGER before_delete_on_multipart_parts_delete_slices
BEFORE DELETE ON multipart_parts
BEGIN
DELETE FROM slices
WHERE slices.db_multipart_part_id = OLD.id;
END;

-- dbSlices trigger to prune slabs
CREATE TRIGGER after_delete_on_slices_delete_slabs
AFTER DELETE ON slices
BEGIN
DELETE FROM slabs
WHERE slabs.id = OLD.db_slab_id
AND slabs.db_buffered_slab_id IS NULL
AND NOT EXISTS (
SELECT 1
FROM slices
WHERE slices.db_slab_id = OLD.db_slab_id
);
END;

-- create default bucket
INSERT INTO buckets (created_at, name) VALUES (CURRENT_TIMESTAMP, 'default');
9 changes: 9 additions & 0 deletions stores/multipart.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ func (s *SQLStore) AbortMultipartUpload(ctx context.Context, bucket, path string
if err != nil {
return fmt.Errorf("failed to delete multipart upload: %w", err)
}
// Prune the slabs.
if err := pruneSlabs(tx); err != nil {
return fmt.Errorf("failed to prune slabs: %w", err)
}
return nil
})
}
Expand Down Expand Up @@ -435,6 +439,11 @@ func (s *SQLStore) CompleteMultipartUpload(ctx context.Context, bucket, path str
if err := tx.Delete(&mu).Error; err != nil {
return fmt.Errorf("failed to delete multipart upload: %w", err)
}

// Prune the slabs.
if err := pruneSlabs(tx); err != nil {
return fmt.Errorf("failed to prune slabs: %w", err)
}
return nil
})
if err != nil {
Expand Down
Loading