featurebase/dax/controller/sqldb/directiveversion.go
Travis Turner 8fca15e936
RetryWithTx (#2348)
* First pass at RetryWithTx

* Refactor RetryWithTx to take a writable bool (instead of reads, writes)

* Implment DirectiveMethodDiff

This commit adds support for a Directive to contain only the diffs (as
opposed to the full Directive).

* Update controller tests to allow for DirectiveMethodDiff (over Full)

* Update RetryWithTx to retry on duplicate key constraint.

If two concurrent processes call IngestShard() for the same shard, both
were trying to insert the same job into the jobs table. That resulted in
a duplicate key error from the database. We want to include that error
in the list of errors for which RetryWithTx should retry.

* Remove unused method: Directive.TranslatePartitions()

* Replace query in a loop with a single query

We had a query which was looking to see if a job already existed. That
query was inside a loop, and could potentially generate 256 queries (for
example). This commit replaces that logic so that we use a single query
wiht an `IN ()` clause.

* Convert to directive version-by-address

This commit uses a separate directive version per address. It moves the
version get/increment back inside the buildDirective method so that if
two concurrent processes are building a directive for the same address,
one of them will get rolled back trying to commit the version update.

* Migration for directive version by address

* Add a comment about DirectiveVersion lock/unlock logic

* Remove AddLastWins

* fix linter

* handle error in walkdir

* fix test failures from removing AddLastWins
2023-03-30 20:54:37 -05:00

70 lines
2 KiB
Go

package sqldb
import (
"github.com/featurebasedb/featurebase/v3/dax"
"github.com/featurebasedb/featurebase/v3/dax/models"
"github.com/featurebasedb/featurebase/v3/errors"
"github.com/featurebasedb/featurebase/v3/logger"
)
func NewDirectiveVersion(log logger.Logger) dax.DirectiveVersion {
if log == nil {
log = logger.NopLogger
}
return &directiveVersion{
log: log,
}
}
type directiveVersion struct {
log logger.Logger
}
func (d *directiveVersion) GetCurrent(tx dax.Transaction, addr dax.Address) (uint64, error) {
dt, ok := tx.(*DaxTransaction)
if !ok {
return 0, dax.NewErrInvalidTransaction("*sqldb.DaxTransaction")
}
dv := &models.DirectiveVersion{}
err := dt.C.Find(dv, addr)
if err == nil {
return uint64(dv.Version), nil
}
// If there is not yet a record for address, create one and return 0 as the
// "current version".
if err.Error() == "sql: no rows in result set" {
dv.ID = string(addr)
if err := dt.C.Create(dv); err != nil {
return 0, errors.Wrapf(err, "creating directive_version for address: %s", addr)
}
return 0, nil
}
return 0, errors.Wrapf(err, "finding directive_version for address: %s", addr)
}
func (d *directiveVersion) SetNext(tx dax.Transaction, addr dax.Address, current, next uint64) error {
dt, ok := tx.(*DaxTransaction)
if !ok {
return dax.NewErrInvalidTransaction("*sqldb.DaxTransaction")
}
dv := &models.DirectiveVersion{}
// Table is assumed to be pre-populated by a previous call to GetCurrent. We
// use the postgres specific "RETURNING" along with `.First()` to ensure
// that a record was updated. If no record matches the WHERE clause, then
// RETURNING would return a result set with 0 records, which causes
// `.First()` to return an error.
err := dt.C.RawQuery(`
UPDATE directive_versions
SET version = ?, updated_at = NOW()
WHERE id = ?
AND version = ?
RETURNING id, version`, next, addr, current).First(dv)
if err != nil {
return errors.Wrapf(err, "updating directive_version for address: %s", addr)
}
return nil
}