github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/mysql/gc.go (about)

     1  package mysql
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"time"
     7  
     8  	sq "github.com/Masterminds/squirrel"
     9  
    10  	"github.com/authzed/spicedb/internal/datastore/common"
    11  	"github.com/authzed/spicedb/internal/datastore/revisions"
    12  	log "github.com/authzed/spicedb/internal/logging"
    13  	"github.com/authzed/spicedb/pkg/datastore"
    14  )
    15  
    16  var _ common.GarbageCollector = (*Datastore)(nil)
    17  
    18  func (mds *Datastore) HasGCRun() bool {
    19  	return mds.gcHasRun.Load()
    20  }
    21  
    22  func (mds *Datastore) MarkGCCompleted() {
    23  	mds.gcHasRun.Store(true)
    24  }
    25  
    26  func (mds *Datastore) ResetGCCompleted() {
    27  	mds.gcHasRun.Store(false)
    28  }
    29  
    30  func (mds *Datastore) Now(ctx context.Context) (time.Time, error) {
    31  	// Retrieve the `now` time from the database.
    32  	nowSQL, nowArgs, err := getNow.ToSql()
    33  	if err != nil {
    34  		return time.Time{}, err
    35  	}
    36  
    37  	var now time.Time
    38  	err = mds.db.QueryRowContext(ctx, nowSQL, nowArgs...).Scan(&now)
    39  	if err != nil {
    40  		return time.Time{}, err
    41  	}
    42  
    43  	// This conversion should just be for convenience while debugging --
    44  	// MySQL and the driver do properly timezones properly.
    45  	return now.UTC(), nil
    46  }
    47  
    48  // - main difference is how the PSQL driver handles null values
    49  func (mds *Datastore) TxIDBefore(ctx context.Context, before time.Time) (datastore.Revision, error) {
    50  	// Find the highest transaction ID before the GC window.
    51  	query, args, err := mds.GetLastRevision.Where(sq.Lt{colTimestamp: before}).ToSql()
    52  	if err != nil {
    53  		return datastore.NoRevision, err
    54  	}
    55  
    56  	var value sql.NullInt64
    57  	err = mds.db.QueryRowContext(ctx, query, args...).Scan(&value)
    58  	if err != nil {
    59  		return datastore.NoRevision, err
    60  	}
    61  
    62  	if !value.Valid {
    63  		log.Ctx(ctx).Debug().Time("before", before).Msg("no stale transactions found in the datastore")
    64  		return datastore.NoRevision, nil
    65  	}
    66  
    67  	return revisions.NewForTransactionID(uint64(value.Int64)), nil
    68  }
    69  
    70  // - implementation misses metrics
    71  func (mds *Datastore) DeleteBeforeTx(
    72  	ctx context.Context,
    73  	txID datastore.Revision,
    74  ) (removed common.DeletionCounts, err error) {
    75  	// Delete any relationship rows with deleted_transaction <= the transaction ID.
    76  	removed.Relationships, err = mds.batchDelete(ctx, mds.driver.RelationTuple(), sq.LtOrEq{colDeletedTxn: txID})
    77  	if err != nil {
    78  		return
    79  	}
    80  
    81  	// Delete all transaction rows with ID < the transaction ID.
    82  	//
    83  	// We don't delete the transaction itself to ensure there is always at least
    84  	// one transaction present.
    85  	removed.Transactions, err = mds.batchDelete(ctx, mds.driver.RelationTupleTransaction(), sq.Lt{colID: txID})
    86  	if err != nil {
    87  		return
    88  	}
    89  
    90  	// Delete any namespace rows with deleted_transaction <= the transaction ID.
    91  	removed.Namespaces, err = mds.batchDelete(ctx, mds.driver.Namespace(), sq.LtOrEq{colDeletedTxn: txID})
    92  	return
    93  }
    94  
    95  // - query was reworked to make it compatible with Vitess
    96  // - API differences with PSQL driver
    97  func (mds *Datastore) batchDelete(ctx context.Context, tableName string, filter sqlFilter) (int64, error) {
    98  	query, args, err := sb.Delete(tableName).Where(filter).Limit(batchDeleteSize).ToSql()
    99  	if err != nil {
   100  		return -1, err
   101  	}
   102  
   103  	var deletedCount int64
   104  	for {
   105  		cr, err := mds.db.ExecContext(ctx, query, args...)
   106  		if err != nil {
   107  			return deletedCount, err
   108  		}
   109  
   110  		rowsDeleted, err := cr.RowsAffected()
   111  		if err != nil {
   112  			return deletedCount, err
   113  		}
   114  		deletedCount += rowsDeleted
   115  		if rowsDeleted < batchDeleteSize {
   116  			break
   117  		}
   118  	}
   119  
   120  	return deletedCount, nil
   121  }