storj/satellite/metabase/delete_expired.go
Egon Elbre 267506bb20 satellite/metabase: move package one level higher
metabase has become a central concept and it's more suitable for it to
be directly nested under satellite rather than being part of metainfo.

metainfo is going to be the "endpoint" logic for handling requests.

Change-Id: I53770d6761ac1e9a1283b5aa68f471b21e784198
2021-04-21 15:54:22 +03:00

178 lines
4.7 KiB
Go

// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package metabase
import (
"context"
"encoding/hex"
"fmt"
"time"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/stdlib"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/storj/private/dbutil"
"storj.io/storj/private/tagsql"
)
const (
expiredBatchsizeLimit = 1000
)
// DeleteExpiredObjects contains all the information necessary to delete expired objects and segments.
type DeleteExpiredObjects struct {
ExpiredBefore time.Time
AsOfSystemTime time.Time
BatchSize int
}
// DeleteExpiredObjects deletes all objects that expired before expiredBefore.
func (db *DB) DeleteExpiredObjects(ctx context.Context, opts DeleteExpiredObjects) (err error) {
defer mon.Task()(&ctx)(&err)
batchsize := opts.BatchSize
if opts.BatchSize == 0 || opts.BatchSize > expiredBatchsizeLimit {
batchsize = expiredBatchsizeLimit
}
var startAfter ObjectStream
for {
lastDeleted, err := db.deleteExpiredObjectsBatch(ctx, startAfter, opts.ExpiredBefore, opts.AsOfSystemTime, batchsize)
if err != nil {
return err
}
if lastDeleted.StreamID.IsZero() {
return nil
}
startAfter = lastDeleted
}
}
func (db *DB) deleteExpiredObjectsBatch(ctx context.Context, startAfter ObjectStream, expiredBefore time.Time, asOfSystemTime time.Time, batchsize int) (last ObjectStream, err error) {
defer mon.Task()(&ctx)(&err)
var asOfSystemTimeString string
if !asOfSystemTime.IsZero() && db.implementation == dbutil.Cockroach {
asOfSystemTimeString = fmt.Sprintf(` AS OF SYSTEM TIME '%d' `, asOfSystemTime.Add(1*time.Second).UTC().UnixNano())
}
query := `
SELECT
project_id, bucket_name, object_key, version, stream_id,
expires_at
FROM objects
` + asOfSystemTimeString + `
WHERE
(project_id, bucket_name, object_key, version) > ($1, $2, $3, $4)
AND expires_at < $5
ORDER BY project_id, bucket_name, object_key, version
LIMIT $6;`
expiredObjects := make([]ObjectStream, 0, batchsize)
err = withRows(db.db.QueryContext(ctx, query,
startAfter.ProjectID, []byte(startAfter.BucketName), []byte(startAfter.ObjectKey), startAfter.Version,
expiredBefore,
batchsize),
)(func(rows tagsql.Rows) error {
for rows.Next() {
var expiresAt time.Time
err = rows.Scan(
&last.ProjectID, &last.BucketName, &last.ObjectKey, &last.Version, &last.StreamID,
&expiresAt)
if err != nil {
return Error.New("unable to delete expired objects: %w", err)
}
db.log.Info("Deleting expired object",
zap.Stringer("Project", last.ProjectID),
zap.String("Bucket", last.BucketName),
zap.String("Object Key", string(last.ObjectKey)),
zap.Int64("Version", int64(last.Version)),
zap.String("StreamID", hex.EncodeToString(last.StreamID[:])),
zap.Time("Expired At", expiresAt),
)
expiredObjects = append(expiredObjects, last)
}
return nil
})
if err != nil {
return ObjectStream{}, Error.New("unable to delete expired objects: %w", err)
}
err = db.deleteExpiredObjects(ctx, expiredObjects)
if err != nil {
return ObjectStream{}, err
}
return last, nil
}
func (db *DB) deleteExpiredObjects(ctx context.Context, expiredObjects []ObjectStream) (err error) {
defer mon.Task()(&ctx)(&err)
if len(expiredObjects) == 0 {
return nil
}
conn, err := db.db.Conn(ctx)
if err != nil {
return Error.New("unable to get the raw conn: %w", err)
}
defer func() { err = errs.Combine(err, conn.Close()) }()
err = conn.Raw(ctx, func(driverConn interface{}) (err error) {
defer func() {
if closable, ok := driverConn.(interface{ Close() error }); ok {
err = errs.Combine(err, closable.Close())
}
}()
var pgxconn *pgx.Conn
switch conn := driverConn.(type) {
case interface{ StdlibConn() *stdlib.Conn }:
pgxconn = conn.StdlibConn().Conn()
case *stdlib.Conn:
pgxconn = conn.Conn()
default:
return Error.New("invalid raw conn driver %T", driverConn)
}
var batch pgx.Batch
for _, obj := range expiredObjects {
obj := obj
batch.Queue(`START TRANSACTION`)
batch.Queue(`
DELETE FROM objects
WHERE (project_id, bucket_name, object_key, version) = ($1::BYTEA, $2::BYTEA, $3::BYTEA, $4)
AND stream_id = $5::BYTEA
`, obj.ProjectID, []byte(obj.BucketName), []byte(obj.ObjectKey), obj.Version, obj.StreamID)
batch.Queue(`
DELETE FROM segments
WHERE segments.stream_id = $1::BYTEA
`, obj.StreamID)
batch.Queue(`COMMIT TRANSACTION`)
}
results := pgxconn.SendBatch(ctx, &batch)
defer func() { err = errs.Combine(err, results.Close()) }()
var errlist errs.Group
for i := 0; i < batch.Len(); i++ {
_, err := results.Exec()
errlist.Add(err)
}
return errlist.Err()
})
if err != nil {
return Error.New("unable to delete expired objects: %w", err)
}
return nil
}