2020-12-09 12:24:37 +00:00
|
|
|
// Copyright (C) 2020 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package metabase
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"database/sql"
|
|
|
|
"errors"
|
2022-03-04 15:08:46 +00:00
|
|
|
"fmt"
|
2020-12-09 12:24:37 +00:00
|
|
|
|
2022-03-04 15:08:46 +00:00
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
|
|
|
"storj.io/common/storj"
|
2020-12-09 12:24:37 +00:00
|
|
|
"storj.io/common/uuid"
|
2021-04-23 10:52:40 +01:00
|
|
|
"storj.io/private/dbutil"
|
2022-03-04 15:08:46 +00:00
|
|
|
"storj.io/private/dbutil/txutil"
|
2021-04-23 10:52:40 +01:00
|
|
|
"storj.io/private/tagsql"
|
2020-12-09 12:24:37 +00:00
|
|
|
)
|
|
|
|
|
2021-06-25 09:19:32 +01:00
|
|
|
const (
|
2021-08-05 20:44:04 +01:00
|
|
|
deleteBatchSizeLimit = intLimitRange(50)
|
2021-06-25 09:19:32 +01:00
|
|
|
)
|
2021-06-24 16:14:58 +01:00
|
|
|
|
2020-12-09 12:24:37 +00:00
|
|
|
// DeleteBucketObjects contains arguments for deleting a whole bucket.
|
|
|
|
type DeleteBucketObjects struct {
|
|
|
|
Bucket BucketLocation
|
|
|
|
BatchSize int
|
|
|
|
|
|
|
|
// DeletePieces is called for every batch of objects.
|
|
|
|
// Slice `segments` will be reused between calls.
|
|
|
|
DeletePieces func(ctx context.Context, segments []DeletedSegmentInfo) error
|
|
|
|
}
|
|
|
|
|
2022-03-04 15:08:46 +00:00
|
|
|
var deleteObjectsCockroachSubSQL = `
|
|
|
|
DELETE FROM objects
|
|
|
|
WHERE project_id = $1 AND bucket_name = $2
|
|
|
|
LIMIT $3
|
|
|
|
`
|
|
|
|
|
|
|
|
// postgres does not support LIMIT in DELETE.
|
|
|
|
var deleteObjectsPostgresSubSQL = `
|
|
|
|
DELETE FROM objects
|
|
|
|
WHERE (objects.project_id, objects.bucket_name) IN (
|
|
|
|
SELECT project_id, bucket_name FROM objects
|
|
|
|
WHERE project_id = $1 AND bucket_name = $2
|
|
|
|
LIMIT $3
|
2022-04-04 15:31:30 +01:00
|
|
|
)`
|
2022-03-04 15:08:46 +00:00
|
|
|
|
|
|
|
var deleteBucketObjectsWithCopyFeaturePostgresSQL = fmt.Sprintf(
|
|
|
|
deleteBucketObjectsWithCopyFeatureSQL,
|
|
|
|
deleteObjectsPostgresSubSQL,
|
2022-04-04 15:31:30 +01:00
|
|
|
"", "",
|
2022-03-04 15:08:46 +00:00
|
|
|
)
|
|
|
|
var deleteBucketObjectsWithCopyFeatureCockroachSQL = fmt.Sprintf(
|
|
|
|
deleteBucketObjectsWithCopyFeatureSQL,
|
|
|
|
deleteObjectsCockroachSubSQL,
|
2022-04-04 15:31:30 +01:00
|
|
|
"", "",
|
2022-03-04 15:08:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func getDeleteBucketObjectsSQLWithCopyFeature(impl dbutil.Implementation) (string, error) {
|
|
|
|
switch impl {
|
|
|
|
case dbutil.Cockroach:
|
|
|
|
return deleteBucketObjectsWithCopyFeatureCockroachSQL, nil
|
|
|
|
case dbutil.Postgres:
|
|
|
|
return deleteBucketObjectsWithCopyFeaturePostgresSQL, nil
|
|
|
|
default:
|
|
|
|
return "", Error.New("unhandled database: %v", impl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-09 12:24:37 +00:00
|
|
|
// DeleteBucketObjects deletes all objects in the specified bucket.
|
2021-08-05 20:44:04 +01:00
|
|
|
// Deletion performs in batches, so in case of error while processing,
|
|
|
|
// this method will return the number of objects deleted to the moment
|
|
|
|
// when an error occurs.
|
2020-12-09 12:24:37 +00:00
|
|
|
func (db *DB) DeleteBucketObjects(ctx context.Context, opts DeleteBucketObjects) (deletedObjectCount int64, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
if err := opts.Bucket.Verify(); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2021-06-25 09:19:32 +01:00
|
|
|
deleteBatchSizeLimit.Ensure(&opts.BatchSize)
|
2021-06-24 16:14:58 +01:00
|
|
|
|
2022-03-04 15:08:46 +00:00
|
|
|
if db.config.ServerSideCopy {
|
|
|
|
return db.deleteBucketObjectsWithCopyFeatureEnabled(ctx, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
return db.deleteBucketObjectsWithCopyFeatureDisabled(ctx, opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) deleteBucketObjectsWithCopyFeatureEnabled(ctx context.Context, opts DeleteBucketObjects) (deletedObjectCount int64, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
for {
|
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
return deletedObjectCount, err
|
|
|
|
}
|
|
|
|
|
2022-08-02 03:07:02 +01:00
|
|
|
deletedBatchCount, err := db.deleteBucketObjectBatchWithCopyFeatureEnabled(ctx, opts)
|
|
|
|
deletedObjectCount += deletedBatchCount
|
2022-03-04 15:08:46 +00:00
|
|
|
|
2022-08-02 03:07:02 +01:00
|
|
|
if err != nil || deletedBatchCount == 0 {
|
|
|
|
return deletedObjectCount, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-04 15:08:46 +00:00
|
|
|
|
2022-08-02 03:07:02 +01:00
|
|
|
// deleteBucketObjectBatchWithCopyFeatureEnabled deletes a single batch from metabase.
|
|
|
|
// This function has been factored out for metric purposes.
|
|
|
|
func (db *DB) deleteBucketObjectBatchWithCopyFeatureEnabled(ctx context.Context, opts DeleteBucketObjects) (deletedObjectCount int64, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
2022-03-04 15:08:46 +00:00
|
|
|
|
2022-08-02 03:07:02 +01:00
|
|
|
query, err := getDeleteBucketObjectsSQLWithCopyFeature(db.impl)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var objects []deletedObjectInfo
|
|
|
|
err = txutil.WithTx(ctx, db.db, nil, func(ctx context.Context, tx tagsql.Tx) (err error) {
|
|
|
|
err = withRows(
|
|
|
|
tx.QueryContext(ctx, query, opts.Bucket.ProjectID, []byte(opts.Bucket.BucketName), opts.BatchSize),
|
|
|
|
)(func(rows tagsql.Rows) error {
|
|
|
|
objects, err = db.scanBucketObjectsDeletionServerSideCopy(ctx, opts.Bucket, rows)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2022-03-04 15:08:46 +00:00
|
|
|
}
|
|
|
|
|
2022-08-02 03:07:02 +01:00
|
|
|
return db.promoteNewAncestors(ctx, tx, objects)
|
|
|
|
})
|
2023-01-24 10:53:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2022-08-02 03:07:02 +01:00
|
|
|
|
|
|
|
deletedObjectCount = int64(len(objects))
|
|
|
|
|
|
|
|
if opts.DeletePieces == nil {
|
|
|
|
// no callback, this should only be in test path
|
|
|
|
return deletedObjectCount, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, object := range objects {
|
|
|
|
if object.PromotedAncestor != nil {
|
|
|
|
// don't remove pieces, they are now linked to the new ancestor
|
2022-03-04 15:08:46 +00:00
|
|
|
continue
|
|
|
|
}
|
2022-08-02 03:07:02 +01:00
|
|
|
for _, segment := range object.Segments {
|
|
|
|
// Is there an advantage to batching this?
|
|
|
|
err := opts.DeletePieces(ctx, []DeletedSegmentInfo{
|
|
|
|
{
|
|
|
|
RootPieceID: segment.RootPieceID,
|
|
|
|
Pieces: segment.Pieces,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return deletedObjectCount, err
|
2022-03-04 15:08:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-02 03:07:02 +01:00
|
|
|
|
|
|
|
return deletedObjectCount, err
|
2022-03-04 15:08:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) scanBucketObjectsDeletionServerSideCopy(ctx context.Context, location BucketLocation, rows tagsql.Rows) (result []deletedObjectInfo, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
defer func() { err = errs.Combine(err, rows.Close()) }()
|
|
|
|
|
|
|
|
result = make([]deletedObjectInfo, 0, 10)
|
|
|
|
var rootPieceID *storj.PieceID
|
|
|
|
var object deletedObjectInfo
|
|
|
|
var segment deletedRemoteSegmentInfo
|
|
|
|
var aliasPieces AliasPieces
|
2022-04-04 15:31:30 +01:00
|
|
|
var segmentPosition *SegmentPosition
|
2022-03-04 15:08:46 +00:00
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
object.ProjectID = location.ProjectID
|
|
|
|
object.BucketName = location.BucketName
|
|
|
|
|
|
|
|
err = rows.Scan(
|
|
|
|
&object.StreamID,
|
2022-04-04 15:31:30 +01:00
|
|
|
&segmentPosition,
|
2022-03-04 15:08:46 +00:00
|
|
|
&rootPieceID,
|
|
|
|
&aliasPieces,
|
|
|
|
&object.PromotedAncestor,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error.New("unable to delete bucket objects: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(result) == 0 || result[len(result)-1].StreamID != object.StreamID {
|
|
|
|
result = append(result, object)
|
|
|
|
}
|
|
|
|
if rootPieceID != nil {
|
2022-04-04 15:31:30 +01:00
|
|
|
segment.Position = *segmentPosition
|
2022-03-04 15:08:46 +00:00
|
|
|
segment.RootPieceID = *rootPieceID
|
|
|
|
segment.Pieces, err = db.aliasCache.ConvertAliasesToPieces(ctx, aliasPieces)
|
|
|
|
if err != nil {
|
|
|
|
return nil, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
if len(segment.Pieces) > 0 {
|
|
|
|
result[len(result)-1].Segments = append(result[len(result)-1].Segments, segment)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
|
|
return nil, Error.New("unable to delete object: %w", err)
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) deleteBucketObjectsWithCopyFeatureDisabled(ctx context.Context, opts DeleteBucketObjects) (deletedObjectCount int64, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2021-02-19 15:48:33 +00:00
|
|
|
var query string
|
2022-03-04 15:08:46 +00:00
|
|
|
|
2021-05-11 09:49:26 +01:00
|
|
|
switch db.impl {
|
2021-02-19 15:48:33 +00:00
|
|
|
case dbutil.Cockroach:
|
|
|
|
query = `
|
|
|
|
WITH deleted_objects AS (
|
|
|
|
DELETE FROM objects
|
|
|
|
WHERE project_id = $1 AND bucket_name = $2 LIMIT $3
|
|
|
|
RETURNING objects.stream_id
|
|
|
|
)
|
|
|
|
DELETE FROM segments
|
2022-02-22 14:41:20 +00:00
|
|
|
WHERE segments.stream_id IN (SELECT deleted_objects.stream_id FROM deleted_objects)
|
2021-02-19 15:48:33 +00:00
|
|
|
RETURNING segments.stream_id, segments.root_piece_id, segments.remote_alias_pieces
|
|
|
|
`
|
|
|
|
case dbutil.Postgres:
|
|
|
|
query = `
|
|
|
|
WITH deleted_objects AS (
|
|
|
|
DELETE FROM objects
|
|
|
|
WHERE stream_id IN (
|
|
|
|
SELECT stream_id FROM objects
|
|
|
|
WHERE project_id = $1 AND bucket_name = $2
|
|
|
|
LIMIT $3
|
|
|
|
)
|
|
|
|
RETURNING objects.stream_id
|
|
|
|
)
|
|
|
|
DELETE FROM segments
|
2022-02-22 14:41:20 +00:00
|
|
|
WHERE segments.stream_id IN (SELECT deleted_objects.stream_id FROM deleted_objects)
|
2021-02-19 15:48:33 +00:00
|
|
|
RETURNING segments.stream_id, segments.root_piece_id, segments.remote_alias_pieces
|
|
|
|
`
|
|
|
|
default:
|
2021-06-04 14:21:09 +01:00
|
|
|
return 0, Error.New("unhandled database: %v", db.impl)
|
2021-02-19 15:48:33 +00:00
|
|
|
}
|
|
|
|
|
2020-12-09 12:24:37 +00:00
|
|
|
// TODO: fix the count for objects without segments
|
2021-08-05 20:44:04 +01:00
|
|
|
deletedSegments := make([]DeletedSegmentInfo, 0, 100)
|
2020-12-09 12:24:37 +00:00
|
|
|
for {
|
2021-08-05 20:44:04 +01:00
|
|
|
if err := ctx.Err(); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
deletedSegments = deletedSegments[:0]
|
|
|
|
deletedObjects := 0
|
2021-07-28 14:44:22 +01:00
|
|
|
err = withRows(db.db.QueryContext(ctx, query,
|
2021-06-25 09:19:32 +01:00
|
|
|
opts.Bucket.ProjectID, []byte(opts.Bucket.BucketName), opts.BatchSize))(func(rows tagsql.Rows) error {
|
2020-12-09 12:24:37 +00:00
|
|
|
ids := map[uuid.UUID]struct{}{} // TODO: avoid map here
|
|
|
|
for rows.Next() {
|
|
|
|
var streamID uuid.UUID
|
|
|
|
var segment DeletedSegmentInfo
|
2021-02-08 09:33:45 +00:00
|
|
|
var aliasPieces AliasPieces
|
|
|
|
err := rows.Scan(&streamID, &segment.RootPieceID, &aliasPieces)
|
2020-12-09 12:24:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return Error.Wrap(err)
|
|
|
|
}
|
2021-02-08 09:33:45 +00:00
|
|
|
segment.Pieces, err = db.aliasCache.ConvertAliasesToPieces(ctx, aliasPieces)
|
|
|
|
if err != nil {
|
|
|
|
return Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-12-09 12:24:37 +00:00
|
|
|
ids[streamID] = struct{}{}
|
2021-08-05 20:44:04 +01:00
|
|
|
deletedSegments = append(deletedSegments, segment)
|
2020-12-09 12:24:37 +00:00
|
|
|
}
|
2021-08-05 20:44:04 +01:00
|
|
|
deletedObjects = len(ids)
|
|
|
|
deletedObjectCount += int64(deletedObjects)
|
2020-12-09 12:24:37 +00:00
|
|
|
return nil
|
|
|
|
})
|
2021-06-04 14:21:09 +01:00
|
|
|
|
2021-08-05 20:44:04 +01:00
|
|
|
mon.Meter("object_delete").Mark(deletedObjects)
|
|
|
|
mon.Meter("segment_delete").Mark(len(deletedSegments))
|
2021-06-04 14:21:09 +01:00
|
|
|
|
2020-12-09 12:24:37 +00:00
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
|
|
return deletedObjectCount, nil
|
|
|
|
}
|
|
|
|
return deletedObjectCount, Error.Wrap(err)
|
|
|
|
}
|
2021-08-05 20:44:04 +01:00
|
|
|
|
|
|
|
if len(deletedSegments) == 0 {
|
2020-12-09 12:24:37 +00:00
|
|
|
return deletedObjectCount, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.DeletePieces != nil {
|
2021-08-05 20:44:04 +01:00
|
|
|
err = opts.DeletePieces(ctx, deletedSegments)
|
2020-12-09 12:24:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return deletedObjectCount, Error.Wrap(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|