mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44:59 +00:00
Switch Serverless from using BoltDB to Postgres as metadata store. Previously, the controller stored all metadata to BoltDB. This implements SQLDB (currently Postgres flavored) as the backing store for metadata. This will allow us to have multiple instances of the controller running for HA, and to easily inspect and repair the contents of the metadata store. Unfortunately, it was not straightforward to keep the BoltDB implementation working alongside the SQL one, so it will be removed in a later patch. Once that's done, the SQL implementation should allow for a number of simplifications of the schemar and balancer interfaces. Database migration is built directly into the application by embedding the migration files and logic from the `soda` command line tool. When connecting to the RDBMS, the app will always attempt to create the necessary database and apply any outstanding migrations. Integration tests truncate all tables upon start, but *not* at the end, so the state of the database can be inspected after integration tests. Had to refactor some of the controller's background tasks to make sure they get properly shut down on controller exit.
144 lines
3.3 KiB
Go
144 lines
3.3 KiB
Go
package boltdb
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/featurebasedb/featurebase/v3/dax"
|
|
"github.com/featurebasedb/featurebase/v3/dax/boltdb"
|
|
"github.com/featurebasedb/featurebase/v3/dax/controller"
|
|
"github.com/featurebasedb/featurebase/v3/errors"
|
|
"github.com/featurebasedb/featurebase/v3/logger"
|
|
)
|
|
|
|
// Ensure type implements interface.
|
|
var _ controller.NodeService = (*NodeService)(nil)
|
|
|
|
// NodeService represents a service for managing nodes.
|
|
type NodeService struct {
|
|
db *boltdb.DB
|
|
|
|
logger logger.Logger
|
|
}
|
|
|
|
// NewNodeService returns a new instance of NodeService with default values.
|
|
func NewNodeService(db *boltdb.DB, logger logger.Logger) *NodeService {
|
|
return &NodeService{
|
|
db: db,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
func (s *NodeService) CreateNode(tx dax.Transaction, addr dax.Address, node *dax.Node) error {
|
|
txx, ok := tx.(*boltdb.Tx)
|
|
if !ok {
|
|
return dax.NewErrInvalidTransaction("*boltdb.Tx")
|
|
}
|
|
|
|
bkt := txx.Bucket(bucketBalancer)
|
|
if bkt == nil {
|
|
return errors.Errorf(boltdb.ErrFmtBucketNotFound, bucketBalancer)
|
|
}
|
|
|
|
val, err := json.Marshal(node)
|
|
if err != nil {
|
|
return errors.Wrap(err, "marshalling node to json")
|
|
}
|
|
|
|
if err := bkt.Put(addressKey(addr), val); err != nil {
|
|
return errors.Wrap(err, "putting node")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *NodeService) ReadNode(tx dax.Transaction, addr dax.Address) (*dax.Node, error) {
|
|
txx, ok := tx.(*boltdb.Tx)
|
|
if !ok {
|
|
return nil, dax.NewErrInvalidTransaction("*boltdb.Tx")
|
|
}
|
|
|
|
bkt := txx.Bucket(bucketBalancer)
|
|
if bkt == nil {
|
|
return nil, errors.Errorf(boltdb.ErrFmtBucketNotFound, bucketBalancer)
|
|
}
|
|
|
|
b := bkt.Get(addressKey(addr))
|
|
if b == nil {
|
|
return nil, dax.NewErrNodeDoesNotExist(addr)
|
|
}
|
|
|
|
node := &dax.Node{}
|
|
if err := json.Unmarshal(b, node); err != nil {
|
|
return nil, errors.Wrap(err, "unmarshalling node json")
|
|
}
|
|
|
|
return node, nil
|
|
}
|
|
|
|
func (s *NodeService) DeleteNode(tx dax.Transaction, addr dax.Address) error {
|
|
txx, ok := tx.(*boltdb.Tx)
|
|
if !ok {
|
|
return dax.NewErrInvalidTransaction("*boltdb.Tx")
|
|
}
|
|
|
|
bkt := txx.Bucket(bucketBalancer)
|
|
if bkt == nil {
|
|
return errors.Errorf(boltdb.ErrFmtBucketNotFound, bucketBalancer)
|
|
}
|
|
|
|
if err := bkt.Delete(addressKey(addr)); err != nil {
|
|
return errors.Wrapf(err, "deleting node key: %s", addressKey(addr))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *NodeService) Nodes(tx dax.Transaction) ([]*dax.Node, error) {
|
|
txx, ok := tx.(*boltdb.Tx)
|
|
if !ok {
|
|
return nil, dax.NewErrInvalidTransaction("*boltdb.Tx")
|
|
}
|
|
|
|
nodes, err := s.getNodes(txx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getting nodes")
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
func (s *NodeService) getNodes(tx *boltdb.Tx) ([]*dax.Node, error) {
|
|
c := tx.Bucket(bucketBalancer).Cursor()
|
|
|
|
// Deserialize rows into Node objects.
|
|
nodes := make([]*dax.Node, 0)
|
|
|
|
prefix := []byte(prefixFmtNodes)
|
|
for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() {
|
|
if v == nil {
|
|
s.logger.Printf("nil value for key: %s", k)
|
|
continue
|
|
}
|
|
|
|
node := &dax.Node{}
|
|
if err := json.Unmarshal(v, node); err != nil {
|
|
return nil, errors.Wrap(err, "unmarshalling node json")
|
|
}
|
|
|
|
nodes = append(nodes, node)
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
const (
|
|
prefixFmtNodes = "nodes/"
|
|
)
|
|
|
|
// addressKey returns a key based on address.
|
|
func addressKey(addr dax.Address) []byte {
|
|
key := fmt.Sprintf(prefixFmtNodes+"%s", addr)
|
|
return []byte(key)
|
|
}
|