mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44:59 +00:00
Rename some interfaces. Remove the ComputeAPI (#2333)
* Clean up dax service interfaces
Rename some of the `computer` interfaces and organize them in the
appropriate files.
Remove `dax/computer/alpha` package
* Remove ComputeAPI (it was replaced by batch.Importer)
* add nss-tools dependecy to smoke test
(cherry picked from commit 969bf055b2)
This commit is contained in:
parent
8fab5239b8
commit
ce32a1bde6
17 changed files with 846 additions and 1285 deletions
28
api.go
28
api.go
|
|
@ -3428,39 +3428,11 @@ type CreateFieldObj struct {
|
|||
Options []FieldOption
|
||||
}
|
||||
|
||||
// ComputeAPI is a subset of the API methods which have to do with compute
|
||||
// operations such as import.
|
||||
type ComputeAPI interface {
|
||||
Import(ctx context.Context, qcx *Qcx, req *ImportRequest, opts ...ImportOption) error
|
||||
ImportValue(ctx context.Context, qcx *Qcx, req *ImportValueRequest, opts ...ImportOption) error
|
||||
Txf() *TxFactory
|
||||
}
|
||||
|
||||
// QueryAPI is a subset of the API methods which have to do with query.
|
||||
type QueryAPI interface {
|
||||
Query(ctx context.Context, req *QueryRequest) (QueryResponse, error)
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ ComputeAPI = (*NopComputeAPI)(nil)
|
||||
|
||||
// NopComputeAPI is a no-op implementation of the ComputeAPI interface.
|
||||
type NopComputeAPI struct{}
|
||||
|
||||
func NewNopComputeAPI() *NopComputeAPI {
|
||||
return &NopComputeAPI{}
|
||||
}
|
||||
|
||||
func (c *NopComputeAPI) Import(ctx context.Context, qcx *Qcx, req *ImportRequest, opts ...ImportOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NopComputeAPI) ImportValue(ctx context.Context, qcx *Qcx, req *ImportValueRequest, opts ...ImportOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *NopComputeAPI) Txf() *TxFactory { return nil }
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ SchemaAPI = (*FeatureBaseSchemaAPI)(nil)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,103 +0,0 @@
|
|||
// Package alpha contains an implementation of the SnapshotReadWriter interface.
|
||||
// In the case where a sub-service (such as snapshotter) implements these
|
||||
// interfaces directly with both its service and its http client, then we don't
|
||||
// need this middle implementation layer. But in this case, the Snapshotter
|
||||
// operates as a third-party service might, meaning its API methods don't align
|
||||
// with what FeatureBase needs to call. So this implementation acts as a
|
||||
// translation later between the featurebase-to-snapshotter interface, and the
|
||||
// third-party Snapshotter service.
|
||||
package alpha
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
"github.com/molecula/featurebase/v3/dax/computer"
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
)
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ computer.SnapshotReadWriter = &alphaSnapshot{}
|
||||
|
||||
// alphaSnapshot uses a Snapshotter implementation (which could be, for
|
||||
// example, an http client or a locally running sub-service) to store its
|
||||
// snapshots.
|
||||
type alphaSnapshot struct {
|
||||
ss computer.Snapshotter
|
||||
}
|
||||
|
||||
func NewAlphaSnapshot(sser computer.Snapshotter) *alphaSnapshot {
|
||||
return &alphaSnapshot{
|
||||
ss: sser,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *alphaSnapshot) WriteShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, rc io.ReadCloser) error {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
key := shardKey(shard)
|
||||
|
||||
if err := s.ss.Write(bucket, key, version, rc); err != nil {
|
||||
return errors.Wrapf(err, "writing shard data: %s, %d", key, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *alphaSnapshot) ReadShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) (io.ReadCloser, error) {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
key := shardKey(shard)
|
||||
|
||||
rc, err := s.ss.Read(bucket, key, version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading shard data: %s, %s, %d", bucket, key, version)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (s *alphaSnapshot) WriteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, wrTo io.WriterTo) error {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
key := keysFileName
|
||||
|
||||
if err := s.ss.WriteTo(bucket, key, version, wrTo); err != nil {
|
||||
return errors.Wrapf(err, "writing table keys: %s, %d", key, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *alphaSnapshot) ReadTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) (io.ReadCloser, error) {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
key := keysFileName
|
||||
|
||||
rc, err := s.ss.Read(bucket, key, version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading table keys: %s, %s, %d", bucket, key, version)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (s *alphaSnapshot) WriteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, wrTo io.WriterTo) error {
|
||||
bucket := fieldBucket(qtid.Key(), field)
|
||||
key := keysFileName
|
||||
|
||||
if err := s.ss.WriteTo(bucket, key, version, wrTo); err != nil {
|
||||
return errors.Wrapf(err, "writing field keys: %s, %d", key, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *alphaSnapshot) ReadFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) (io.ReadCloser, error) {
|
||||
bucket := fieldBucket(qtid.Key(), field)
|
||||
key := keysFileName
|
||||
|
||||
rc, err := s.ss.Read(bucket, key, version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading field keys: %s, %s, %d", bucket, key, version)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
|
@ -1,322 +0,0 @@
|
|||
// Package alpha contains an implementation of the WriteLogReader and
|
||||
// WriteLogWriter interfaces. In the case where a sub-service (such as
|
||||
// writelogger) implements these interfaces directly with both its service and
|
||||
// its http client, then we don't need this middle implementation layer. But in
|
||||
// this case, the WriteLogger operates as a third-party service might, meaning
|
||||
// its API methods don't align with what FeatureBase needs to call. So this
|
||||
// implementation acts as a translation later between the
|
||||
// featurebase-to-writelogger interface, and the third-party WriteLogger
|
||||
// service.
|
||||
package alpha
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
"github.com/molecula/featurebase/v3/dax/computer"
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
)
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ computer.WriteLogReader = &alphaWriteLog{}
|
||||
var _ computer.WriteLogWriter = &alphaWriteLog{}
|
||||
|
||||
// alphaWriteLog is an implementation of the WriteLogReader and WriteLogWriter
|
||||
// interfaces. It uses a WriteLogger implementation (which could be, for
|
||||
// example, an http client or a locally running sub-service) to store its log
|
||||
// messages. I can't remember why we put this implementation in its own package
|
||||
// (a meaningless name called "alpha"); it probably has something to do with
|
||||
// wanting to adhere to a typical interface/implementation package structure,
|
||||
// but was confused by the current state of things where the "storage" package
|
||||
// (or perhaps "computer") is the top level package (i.e. "pilosa"). Until we
|
||||
// can correct the packaging, we will likely have weird cases like this.
|
||||
type alphaWriteLog struct {
|
||||
wl computer.WriteLogger
|
||||
}
|
||||
|
||||
func NewAlphaWriteLog(wler computer.WriteLogger) *alphaWriteLog {
|
||||
return &alphaWriteLog{
|
||||
wl: wler,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *alphaWriteLog) CreateTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, m map[string]uint64) error {
|
||||
msg := computer.PartitionKeyMap{
|
||||
TableKey: qtid.Key(),
|
||||
Partition: partition,
|
||||
StringToID: m,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling partition key map to json")
|
||||
}
|
||||
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
|
||||
if err := w.wl.AppendMessage(bucket, keysFileName, version, b); err != nil {
|
||||
return errors.Wrapf(err, "appending partition key message: %s, %d", keysFileName, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *alphaWriteLog) DeleteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) error {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
return w.wl.DeleteLog(bucket, keysFileName, version)
|
||||
}
|
||||
|
||||
func (w *alphaWriteLog) CreateFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, m map[string]uint64) error {
|
||||
msg := computer.FieldKeyMap{
|
||||
TableKey: qtid.Key(),
|
||||
Field: field,
|
||||
StringToID: m,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling field key map to json")
|
||||
}
|
||||
|
||||
bucket := fieldBucket(qtid.Key(), field)
|
||||
|
||||
if err := w.wl.AppendMessage(bucket, keysFileName, version, b); err != nil {
|
||||
return errors.Wrapf(err, "appending field key message: %s, %d", keysFileName, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *alphaWriteLog) DeleteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) error {
|
||||
bucket := fieldBucket(qtid.Key(), field)
|
||||
return w.wl.DeleteLog(bucket, keysFileName, version)
|
||||
}
|
||||
|
||||
func (w *alphaWriteLog) WriteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, msg computer.LogMessage) error {
|
||||
b, err := computer.MarshalLogMessage(msg, computer.EncodeTypeJSON)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling log message")
|
||||
}
|
||||
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
shardKey := shardKey(shard)
|
||||
|
||||
if err := w.wl.AppendMessage(bucket, shardKey, version, b); err != nil {
|
||||
return errors.Wrapf(err, "appending shard key message: %s, %d", shardKey, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *alphaWriteLog) DeleteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) error {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
shardKey := shardKey(shard)
|
||||
|
||||
return w.wl.DeleteLog(bucket, shardKey, version)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
func (w *alphaWriteLog) TableKeyReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) computer.TableKeyReader {
|
||||
return newTableKeyReader(w.wl, qtid, partition, version)
|
||||
}
|
||||
|
||||
type tableKeyReader struct {
|
||||
wl computer.WriteLogger
|
||||
table dax.TableKey
|
||||
partition dax.PartitionNum
|
||||
version int
|
||||
scanner *bufio.Scanner
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func newTableKeyReader(wl computer.WriteLogger, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) *tableKeyReader {
|
||||
r := &tableKeyReader{
|
||||
wl: wl,
|
||||
table: qtid.Key(),
|
||||
partition: partition,
|
||||
version: version,
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *tableKeyReader) Open() error {
|
||||
bucket := partitionBucket(r.table, r.partition)
|
||||
|
||||
reader, closer, err := r.wl.LogReader(bucket, keysFileName, r.version)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting log reader: %s, %s, %d", bucket, keysFileName, r.version)
|
||||
}
|
||||
|
||||
r.closer = closer
|
||||
r.scanner = bufio.NewScanner(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *tableKeyReader) Read() (computer.PartitionKeyMap, error) {
|
||||
if r.scanner == nil {
|
||||
return computer.PartitionKeyMap{}, io.EOF
|
||||
}
|
||||
|
||||
var b []byte
|
||||
var out computer.PartitionKeyMap
|
||||
|
||||
if r.scanner.Scan() {
|
||||
b = r.scanner.Bytes()
|
||||
if err := json.Unmarshal(b, &out); err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
if err := r.scanner.Err(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
return out, io.EOF
|
||||
}
|
||||
|
||||
func (r *tableKeyReader) Close() error {
|
||||
if r.closer != nil {
|
||||
return r.closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
func (w *alphaWriteLog) FieldKeyReader(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) computer.FieldKeyReader {
|
||||
return newFieldKeyReader(w.wl, qtid, field, version)
|
||||
}
|
||||
|
||||
type fieldKeyReader struct {
|
||||
wl computer.WriteLogger
|
||||
table dax.TableKey
|
||||
field dax.FieldName
|
||||
version int
|
||||
scanner *bufio.Scanner
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func newFieldKeyReader(wl computer.WriteLogger, qtid dax.QualifiedTableID, field dax.FieldName, version int) *fieldKeyReader {
|
||||
r := &fieldKeyReader{
|
||||
wl: wl,
|
||||
table: qtid.Key(),
|
||||
field: field,
|
||||
version: version,
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *fieldKeyReader) Open() error {
|
||||
bucket := fieldBucket(r.table, r.field)
|
||||
|
||||
reader, closer, err := r.wl.LogReader(bucket, keysFileName, r.version)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting log reader: %s, %s, %d", bucket, keysFileName, r.version)
|
||||
}
|
||||
|
||||
r.closer = closer
|
||||
r.scanner = bufio.NewScanner(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *fieldKeyReader) Read() (computer.FieldKeyMap, error) {
|
||||
if r.scanner == nil {
|
||||
return computer.FieldKeyMap{}, io.EOF
|
||||
}
|
||||
|
||||
var b []byte
|
||||
var out computer.FieldKeyMap
|
||||
|
||||
if r.scanner.Scan() {
|
||||
b = r.scanner.Bytes()
|
||||
if err := json.Unmarshal(b, &out); err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
if err := r.scanner.Err(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
return out, io.EOF
|
||||
}
|
||||
|
||||
func (r *fieldKeyReader) Close() error {
|
||||
if r.closer != nil {
|
||||
return r.closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
func (w *alphaWriteLog) ShardReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) computer.ShardReader {
|
||||
return newShardReader(w.wl, qtid, partition, shard, version)
|
||||
}
|
||||
|
||||
type shardReader struct {
|
||||
wl computer.WriteLogger
|
||||
table dax.TableKey
|
||||
partition dax.PartitionNum
|
||||
shard dax.ShardNum
|
||||
version int
|
||||
scanner *bufio.Scanner
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func newShardReader(wl computer.WriteLogger, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) *shardReader {
|
||||
r := &shardReader{
|
||||
wl: wl,
|
||||
table: qtid.Key(),
|
||||
partition: partition,
|
||||
shard: shard,
|
||||
version: version,
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *shardReader) Open() error {
|
||||
bucket := partitionBucket(r.table, r.partition)
|
||||
shardKey := shardKey(r.shard)
|
||||
|
||||
reader, closer, err := r.wl.LogReader(bucket, shardKey, r.version)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting log reader: %s, %s, %d", bucket, shardKey, r.version)
|
||||
}
|
||||
|
||||
r.closer = closer
|
||||
r.scanner = bufio.NewScanner(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *shardReader) Read() (computer.LogMessage, error) {
|
||||
if r.scanner == nil {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
if r.scanner.Scan() {
|
||||
return computer.UnmarshalLogMessage(r.scanner.Bytes())
|
||||
}
|
||||
if err := r.scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
func (r *shardReader) Close() error {
|
||||
if r.closer != nil {
|
||||
return r.closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -14,18 +14,86 @@ type Registrar interface {
|
|||
CheckInNode(ctx context.Context, node *dax.Node) error
|
||||
}
|
||||
|
||||
// WriteLogger represents the WriteLogger methods which Computer uses. These are
|
||||
// typically implemented by the WriteLogger client.
|
||||
type WriteLogger interface {
|
||||
// WriteLogService represents the WriteLogService methods which Computer uses.
|
||||
// These are typically implemented by the WriteLogger client.
|
||||
type WriteLogService interface {
|
||||
AppendMessage(bucket string, key string, version int, msg []byte) error
|
||||
LogReader(bucket string, key string, version int) (io.Reader, io.Closer, error)
|
||||
DeleteLog(bucket string, key string, version int) error
|
||||
}
|
||||
|
||||
// Snapshotter represents the Snapshotter methods which Computer uses. These are
|
||||
// typically implemented by both the Snapshotter client.
|
||||
type Snapshotter interface {
|
||||
// SnapshotService represents the SnapshotService methods which Computer uses.
|
||||
// These are typically implemented by both the Snapshotter client.
|
||||
type SnapshotService interface {
|
||||
Read(bucket string, key string, version int) (io.ReadCloser, error)
|
||||
Write(bucket string, key string, version int, rc io.ReadCloser) error
|
||||
WriteTo(bucket string, key string, version int, wrTo io.WriterTo) error
|
||||
}
|
||||
|
||||
// SnapshotReadWriter provides the interface for all snapshot read and writes in
|
||||
// FeatureBase.
|
||||
type SnapshotReadWriter interface {
|
||||
WriteShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, rc io.ReadCloser) error
|
||||
ReadShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) (io.ReadCloser, error)
|
||||
|
||||
WriteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, wrTo io.WriterTo) error
|
||||
ReadTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) (io.ReadCloser, error)
|
||||
|
||||
WriteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, wrTo io.WriterTo) error
|
||||
ReadFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// WriteLogWriter provides the interface for all data writes to FeatureBase. After
|
||||
// data has been written to the local FeatureBase node, the respective interface
|
||||
// method(s) will be called.
|
||||
type WriteLogWriter interface {
|
||||
// CreateTableKeys sends a map of string key to uint64 ID for the table and
|
||||
// partition provided.
|
||||
CreateTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, _ map[string]uint64) error
|
||||
|
||||
// DeleteTableKeys deletes all table keys for the table and partition
|
||||
// provided.
|
||||
DeleteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) error
|
||||
|
||||
// CreateFieldKeys sends a map of string key to uint64 ID for the table and
|
||||
// field provided.
|
||||
CreateFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, _ map[string]uint64) error
|
||||
|
||||
// DeleteTableKeys deletes all field keys for the table and field provided.
|
||||
DeleteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) error
|
||||
|
||||
// WriteShard sends shard data for the table and shard provided.
|
||||
WriteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, msg LogMessage) error
|
||||
|
||||
// DeleteShard deletes all data for the table and shard provided.
|
||||
DeleteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) error
|
||||
}
|
||||
|
||||
// WriteLogReader provides the interface for all reads from the write log.
|
||||
type WriteLogReader interface {
|
||||
ShardReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) ShardReader
|
||||
TableKeyReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) TableKeyReader
|
||||
FieldKeyReader(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) FieldKeyReader
|
||||
}
|
||||
|
||||
type TableKeyReader interface {
|
||||
Open() error
|
||||
Read() (PartitionKeyMap, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type FieldKeyReader interface {
|
||||
Open() error
|
||||
Read() (FieldKeyMap, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type ShardReader interface {
|
||||
Open() error
|
||||
Read() (LogMessage, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// LogMessage is implemented by a variety of types which can be serialized as
|
||||
// messages to the WriteLogger.
|
||||
type LogMessage interface{}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package alpha
|
||||
package computer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
265
dax/computer/logmessage.go
Normal file
265
dax/computer/logmessage.go
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
package computer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
)
|
||||
|
||||
//////////////// Messages ///////////////////////
|
||||
|
||||
type PartitionKeyMap struct {
|
||||
TableKey dax.TableKey `json:"table-key"`
|
||||
Partition dax.PartitionNum `json:"partition"`
|
||||
StringToID map[string]uint64 `json:"string-to-id"`
|
||||
}
|
||||
|
||||
type FieldKeyMap struct {
|
||||
TableKey dax.TableKey `json:"table-key"`
|
||||
Field dax.FieldName `json:"field"`
|
||||
StringToID map[string]uint64 `json:"string-to-id"`
|
||||
}
|
||||
|
||||
const (
|
||||
logMessageTypeImportRoaring byte = iota
|
||||
logMessageTypeImport
|
||||
logMessageTypeImportValue
|
||||
logMessageTypeImportRoaringShard
|
||||
)
|
||||
|
||||
// encoderKey* are part of the log message header. They indicate what encoding
|
||||
// type a specific log message is serialized with.
|
||||
const (
|
||||
encoderKeyJSON byte = iota
|
||||
)
|
||||
|
||||
const (
|
||||
EncodeTypeJSON string = "json"
|
||||
|
||||
// encodeVersion refers to the version of the structs used to represent the
|
||||
// log messages. If we change structs, we'll need to modify this version
|
||||
// number and maintain the previous version of the structs somewhere for
|
||||
// deserialization.
|
||||
encodeVersion byte = 1
|
||||
)
|
||||
|
||||
// logMessageEncoder is implemented by any encoder used to serialize LogMessages
|
||||
// to []byte.
|
||||
type logMessageEncoder interface {
|
||||
Key() byte
|
||||
Marshal(LogMessage) ([]byte, error)
|
||||
Unmarshal([]byte, LogMessage) error
|
||||
}
|
||||
|
||||
// MarshalLogMessage serializes the log message and prepends additional encoding
|
||||
// information to each message. Currently, we prepend three bytes to each log
|
||||
// message:
|
||||
// byte[0]: encodeVersion - this is currently a constant within the code. If we
|
||||
// modify structs such that they encode differently, we'll have to change the
|
||||
// constant and keep previous versions of structs for deserialization.
|
||||
// byte[1]: encodeType (e.g. "json", etc.)
|
||||
// byte[2]: logMessageType
|
||||
//
|
||||
// If we get into a situation where we want more flexibility in these message
|
||||
// header bytes—for example, if we want to use more than three bytes—we could do
|
||||
// something with the first bit of the encodeVersion: if it's 1, that could
|
||||
// indicate that there are additional header bytes, and the following seven bits
|
||||
// could indicate how many.
|
||||
func MarshalLogMessage(msg LogMessage, encode string) ([]byte, error) {
|
||||
encoder, err := getEncoderByType(encode)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting encoder by type")
|
||||
}
|
||||
|
||||
logMessageType, err := getLogMessageType(msg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting log message type")
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
|
||||
buf, err = encoder.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshaling log message")
|
||||
}
|
||||
|
||||
return append([]byte{encodeVersion, encoder.Key(), logMessageType}, buf...), nil
|
||||
}
|
||||
|
||||
// UnmarshalLogMessage deserializes the log message based on the log message
|
||||
// type info.
|
||||
func UnmarshalLogMessage(b []byte) (LogMessage, error) {
|
||||
if len(b) < 3 {
|
||||
return nil, errors.New(errors.ErrUncoded, "log record does not contain a full header")
|
||||
}
|
||||
|
||||
encVersion := b[0]
|
||||
encKey := b[1]
|
||||
logMessageType := b[2]
|
||||
|
||||
// Ensure that the log message is able to be handled by this code. If we
|
||||
// increment the constant encodeVersion, we'll need to modify this to handle
|
||||
// the log based on previous encodeVersions.
|
||||
if encVersion != encodeVersion {
|
||||
return nil, errors.Errorf("encode version is unsupported: %d", encVersion)
|
||||
}
|
||||
|
||||
msg, err := logMessageByType(logMessageType)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting log message by type")
|
||||
}
|
||||
|
||||
encoder, err := getEncoderByKey(encKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting encoder by key")
|
||||
}
|
||||
|
||||
if err := encoder.Unmarshal(b[3:], &msg); err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshaling log message")
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func logMessageByType(typ byte) (LogMessage, error) {
|
||||
switch typ {
|
||||
case logMessageTypeImportRoaring:
|
||||
return &ImportRoaringMessage{}, nil
|
||||
case logMessageTypeImport:
|
||||
return &ImportMessage{}, nil
|
||||
case logMessageTypeImportValue:
|
||||
return &ImportValueMessage{}, nil
|
||||
case logMessageTypeImportRoaringShard:
|
||||
return &ImportRoaringShardMessage{}, nil
|
||||
default:
|
||||
return nil, errors.Errorf("unknown message type %d", typ)
|
||||
}
|
||||
}
|
||||
|
||||
func getLogMessageType(m LogMessage) (byte, error) {
|
||||
switch m.(type) {
|
||||
case *ImportRoaringMessage:
|
||||
return logMessageTypeImportRoaring, nil
|
||||
case *ImportMessage:
|
||||
return logMessageTypeImport, nil
|
||||
case *ImportValueMessage:
|
||||
return logMessageTypeImportValue, nil
|
||||
case *ImportRoaringShardMessage:
|
||||
return logMessageTypeImportRoaringShard, nil
|
||||
default:
|
||||
return 0, errors.Errorf("don't have type for message %#v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func getEncoderByType(encode string) (logMessageEncoder, error) {
|
||||
switch encode {
|
||||
case EncodeTypeJSON:
|
||||
return &encoderJSON{}, nil
|
||||
default:
|
||||
return nil, errors.Errorf("invalid encode type: %s", encode)
|
||||
}
|
||||
}
|
||||
|
||||
func getEncoderByKey(id byte) (logMessageEncoder, error) {
|
||||
switch id {
|
||||
case encoderKeyJSON:
|
||||
return &encoderJSON{}, nil
|
||||
default:
|
||||
return nil, errors.Errorf("invalid encode type: %d", id)
|
||||
}
|
||||
}
|
||||
|
||||
type ImportRoaringMessage struct {
|
||||
LogMessage `json:"-"`
|
||||
|
||||
Table string `json:"table"`
|
||||
Field string `json:"field"`
|
||||
Partition int `json:"partition"`
|
||||
Shard uint64 `json:"shard"`
|
||||
Clear bool `json:"clear"`
|
||||
Action string `json:"action"` // [set, clear, overwrite]
|
||||
Block int `json:"block"`
|
||||
Views map[string][]byte `json:"views"`
|
||||
UpdateExistence bool `json:"update-existence"`
|
||||
}
|
||||
|
||||
type ImportMessage struct {
|
||||
LogMessage `json:"-"`
|
||||
|
||||
Table string `json:"table"`
|
||||
Field string `json:"field"`
|
||||
Partition int `json:"partition"`
|
||||
Shard uint64 `json:"shard"`
|
||||
RowIDs []uint64 `json:"row-ids"`
|
||||
ColumnIDs []uint64 `json:"column-ids"`
|
||||
RowKeys []string `json:"row-keys"`
|
||||
ColumnKeys []string `json:"column-keys"`
|
||||
Timestamps []int64 `json:"timestamps"`
|
||||
Clear bool `json:"clear"`
|
||||
|
||||
// options
|
||||
IgnoreKeyCheck bool `json:"ignore-key-check"`
|
||||
Presorted bool `json:"presorted"`
|
||||
}
|
||||
|
||||
type ImportValueMessage struct {
|
||||
LogMessage `json:"-"`
|
||||
|
||||
Table string `json:"table"`
|
||||
Field string `json:"field"`
|
||||
Partition int `json:"partition"`
|
||||
Shard uint64 `json:"shard"`
|
||||
ColumnIDs []uint64 `json:"column-ids"`
|
||||
ColumnKeys []string `json:"column-keys"`
|
||||
Values []int64 `json:"values"`
|
||||
FloatValues []float64 `json:"float-values"`
|
||||
TimestampValues []time.Time `json:"timestamp-values"`
|
||||
StringValues []string `json:"string-values"`
|
||||
Clear bool `json:"clear"`
|
||||
|
||||
// options
|
||||
IgnoreKeyCheck bool `json:"ignore-key-check"`
|
||||
Presorted bool `json:"presorted"`
|
||||
}
|
||||
|
||||
type ImportRoaringShardMessage struct {
|
||||
LogMessage `json:"-"`
|
||||
|
||||
Table string `json:"table"`
|
||||
Partition int `json:"partition"`
|
||||
Shard uint64 `json:"shard"`
|
||||
Views []RoaringUpdate `json:"views"`
|
||||
}
|
||||
|
||||
// RoaringUpdate is identical to featurebase.RoaringUpdate, but we
|
||||
// can't import it due to import cycles. TODO featurebase top level
|
||||
// shouldn't import dax stuff... all the types it needs should just be
|
||||
// in the top level.
|
||||
type RoaringUpdate struct {
|
||||
Field string `json:"field"`
|
||||
View string `json:"view"`
|
||||
Clear []byte `json:"clear"`
|
||||
Set []byte `json:"set"`
|
||||
ClearRecords bool `json:"clear-records"`
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ logMessageEncoder = (*encoderJSON)(nil)
|
||||
|
||||
// encoderJSON is an implementation of the logMessageEncoder interface which
|
||||
// encodes LogMessages as JSON.
|
||||
type encoderJSON struct{}
|
||||
|
||||
func (e *encoderJSON) Key() byte {
|
||||
return encoderKeyJSON
|
||||
}
|
||||
|
||||
func (e *encoderJSON) Marshal(msg LogMessage) ([]byte, error) {
|
||||
return json.Marshal(msg)
|
||||
}
|
||||
|
||||
func (e *encoderJSON) Unmarshal(b []byte, msg LogMessage) error {
|
||||
return json.Unmarshal(b, msg)
|
||||
}
|
||||
162
dax/computer/noop.go
Normal file
162
dax/computer/noop.go
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
package computer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
)
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ WriteLogWriter = (*NopWriteLogWriter)(nil)
|
||||
|
||||
// NopWriteLogWriter is a no-op implementation of the WriteLogWriter interface.
|
||||
type NopWriteLogWriter struct{}
|
||||
|
||||
func NewNopWriteLogWriter() *NopWriteLogWriter {
|
||||
return &NopWriteLogWriter{}
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) CreateTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, m map[string]uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) DeleteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) CreateFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, m map[string]uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) DeleteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) WriteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, msg LogMessage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) DeleteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ WriteLogReader = (*NopWriteLogReader)(nil)
|
||||
|
||||
// NopWriteLogReader is a no-op implementation of the WriteLogReader interface.
|
||||
type NopWriteLogReader struct{}
|
||||
|
||||
func NewNopWriteLogReader() *NopWriteLogReader {
|
||||
return &NopWriteLogReader{}
|
||||
}
|
||||
|
||||
func (w *NopWriteLogReader) TableKeyReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) TableKeyReader {
|
||||
return NewNopTableKeyReader()
|
||||
}
|
||||
|
||||
func (w *NopWriteLogReader) FieldKeyReader(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) FieldKeyReader {
|
||||
return NewNopFieldKeyReader()
|
||||
}
|
||||
|
||||
func (w *NopWriteLogReader) ShardReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) ShardReader {
|
||||
return NewNopShardReader()
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ TableKeyReader = &NopTableKeyReader{}
|
||||
|
||||
// NopTableKeyReader is a no-op implementation of the TableKeyReader
|
||||
// interface.
|
||||
type NopTableKeyReader struct{}
|
||||
|
||||
func NewNopTableKeyReader() *NopTableKeyReader {
|
||||
return &NopTableKeyReader{}
|
||||
}
|
||||
|
||||
func (r *NopTableKeyReader) Open() error { return nil }
|
||||
func (r *NopTableKeyReader) Read() (PartitionKeyMap, error) {
|
||||
return PartitionKeyMap{}, io.EOF
|
||||
}
|
||||
func (r *NopTableKeyReader) Close() error { return nil }
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ FieldKeyReader = &NopFieldKeyReader{}
|
||||
|
||||
// NopFieldKeyReader is a no-op implementation of the FieldKeyReader
|
||||
// interface.
|
||||
type NopFieldKeyReader struct{}
|
||||
|
||||
func NewNopFieldKeyReader() *NopFieldKeyReader {
|
||||
return &NopFieldKeyReader{}
|
||||
}
|
||||
|
||||
func (r *NopFieldKeyReader) Open() error { return nil }
|
||||
func (r *NopFieldKeyReader) Read() (FieldKeyMap, error) {
|
||||
return FieldKeyMap{}, io.EOF
|
||||
}
|
||||
func (r *NopFieldKeyReader) Close() error { return nil }
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ ShardReader = &NopShardReader{}
|
||||
|
||||
// NopShardReader is a no-op implementation of the ShardReader interface.
|
||||
type NopShardReader struct{}
|
||||
|
||||
func NewNopShardReader() *NopShardReader {
|
||||
return &NopShardReader{}
|
||||
}
|
||||
|
||||
func (r *NopShardReader) Open() error { return nil }
|
||||
func (r *NopShardReader) Read() (LogMessage, error) {
|
||||
return nil, io.EOF
|
||||
}
|
||||
func (r *NopShardReader) Close() error { return nil }
|
||||
|
||||
////////////// SNAPSHOT ////////////////////////
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ SnapshotReadWriter = &NopSnapshotReadWriter{}
|
||||
|
||||
// NopSnapshotReadWriter is a no-op implementation of the SnapshotReadWriter
|
||||
// interface.
|
||||
type NopSnapshotReadWriter struct{}
|
||||
|
||||
func NewNopSnapshotReadWriter() *NopSnapshotReadWriter {
|
||||
return &NopSnapshotReadWriter{}
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) WriteShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, rc io.ReadCloser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) ReadShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) (io.ReadCloser, error) {
|
||||
return &nopReadCloser{}, nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) WriteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, wrTo io.WriterTo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) ReadTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) (io.ReadCloser, error) {
|
||||
return &nopReadCloser{}, nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) WriteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, wrTo io.WriterTo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) ReadFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) (io.ReadCloser, error) {
|
||||
return &nopReadCloser{}, nil
|
||||
}
|
||||
|
||||
type nopReadCloser struct{}
|
||||
|
||||
func (n *nopReadCloser) Read([]byte) (int, error) { return 0, nil }
|
||||
func (n *nopReadCloser) Close() error { return nil }
|
||||
|
|
@ -155,7 +155,7 @@ func newCommand(addr dax.Address, cfg CommandConfig) *fbserver.Command {
|
|||
cfg.ComputerConfig.GRPCListener = &nopListener{}
|
||||
cfg.ComputerConfig.DataDir = cfg.RootDataDir + "/" + cfg.Name
|
||||
|
||||
var writeLoggerImpl computer.WriteLogger
|
||||
var writeLoggerImpl computer.WriteLogService
|
||||
if cfg.ComputerConfig.WriteLogger != "" {
|
||||
writeLoggerImpl = writeloggerclient.New(dax.Address(cfg.ComputerConfig.WriteLogger))
|
||||
} else if wlSvc != nil {
|
||||
|
|
@ -164,7 +164,7 @@ func newCommand(addr dax.Address, cfg CommandConfig) *fbserver.Command {
|
|||
cfg.Logger.Warnf("No writelogger configured, dynamic scaling will not function properly.")
|
||||
}
|
||||
|
||||
var snapshotterImpl computer.Snapshotter
|
||||
var snapshotterImpl computer.SnapshotService
|
||||
if cfg.ComputerConfig.Snapshotter != "" {
|
||||
snapshotterImpl = snapshotterclient.New(dax.Address(cfg.ComputerConfig.Snapshotter))
|
||||
} else if ssSvc != nil {
|
||||
|
|
|
|||
|
|
@ -5,57 +5,90 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
)
|
||||
|
||||
// SnapshotReadWriter provides the interface for all snapshot read and writes in
|
||||
// FeatureBase.
|
||||
type SnapshotReadWriter interface {
|
||||
WriteShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, rc io.ReadCloser) error
|
||||
ReadShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) (io.ReadCloser, error)
|
||||
|
||||
WriteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, wrTo io.WriterTo) error
|
||||
ReadTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) (io.ReadCloser, error)
|
||||
|
||||
WriteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, wrTo io.WriterTo) error
|
||||
ReadFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ SnapshotReadWriter = &NopSnapshotReadWriter{}
|
||||
var _ SnapshotReadWriter = &snapshotReadWriter{}
|
||||
|
||||
// NopSnapshotReadWriter is a no-op implementation of the SnapshotReadWriter
|
||||
// interface.
|
||||
type NopSnapshotReadWriter struct{}
|
||||
|
||||
func NewNopSnapshotReadWriter() *NopSnapshotReadWriter {
|
||||
return &NopSnapshotReadWriter{}
|
||||
// snapshotReadWriter uses a SnapshotService implementation (which could be, for
|
||||
// example, an http client or a locally running sub-service) to store its
|
||||
// snapshots.
|
||||
type snapshotReadWriter struct {
|
||||
ss SnapshotService
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) WriteShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, rc io.ReadCloser) error {
|
||||
func NewSnapshotReadWriter(ss SnapshotService) *snapshotReadWriter {
|
||||
return &snapshotReadWriter{
|
||||
ss: ss,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *snapshotReadWriter) WriteShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, rc io.ReadCloser) error {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
key := shardKey(shard)
|
||||
|
||||
if err := s.ss.Write(bucket, key, version, rc); err != nil {
|
||||
return errors.Wrapf(err, "writing shard data: %s, %d", key, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) ReadShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) (io.ReadCloser, error) {
|
||||
return &nopReadCloser{}, nil
|
||||
func (s *snapshotReadWriter) ReadShardData(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) (io.ReadCloser, error) {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
key := shardKey(shard)
|
||||
|
||||
rc, err := s.ss.Read(bucket, key, version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading shard data: %s, %s, %d", bucket, key, version)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) WriteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, wrTo io.WriterTo) error {
|
||||
func (s *snapshotReadWriter) WriteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, wrTo io.WriterTo) error {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
key := keysFileName
|
||||
|
||||
if err := s.ss.WriteTo(bucket, key, version, wrTo); err != nil {
|
||||
return errors.Wrapf(err, "writing table keys: %s, %d", key, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) ReadTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) (io.ReadCloser, error) {
|
||||
return &nopReadCloser{}, nil
|
||||
func (s *snapshotReadWriter) ReadTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) (io.ReadCloser, error) {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
key := keysFileName
|
||||
|
||||
rc, err := s.ss.Read(bucket, key, version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading table keys: %s, %s, %d", bucket, key, version)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) WriteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, wrTo io.WriterTo) error {
|
||||
func (s *snapshotReadWriter) WriteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, wrTo io.WriterTo) error {
|
||||
bucket := fieldBucket(qtid.Key(), field)
|
||||
key := keysFileName
|
||||
|
||||
if err := s.ss.WriteTo(bucket, key, version, wrTo); err != nil {
|
||||
return errors.Wrapf(err, "writing field keys: %s, %d", key, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopSnapshotReadWriter) ReadFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) (io.ReadCloser, error) {
|
||||
return &nopReadCloser{}, nil
|
||||
func (s *snapshotReadWriter) ReadFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) (io.ReadCloser, error) {
|
||||
bucket := fieldBucket(qtid.Key(), field)
|
||||
key := keysFileName
|
||||
|
||||
rc, err := s.ss.Read(bucket, key, version)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "reading field keys: %s, %s, %d", bucket, key, version)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
type nopReadCloser struct{}
|
||||
|
||||
func (n *nopReadCloser) Read([]byte) (int, error) { return 0, nil }
|
||||
func (n *nopReadCloser) Close() error { return nil }
|
||||
|
|
|
|||
|
|
@ -1,434 +1,307 @@
|
|||
package computer
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
)
|
||||
|
||||
// WriteLogWriter provides the interface for all data writes to FeatureBase. After
|
||||
// data has been written to the local FeatureBase node, the respective interface
|
||||
// method(s) will be called.
|
||||
type WriteLogWriter interface {
|
||||
// CreateTableKeys sends a map of string key to uint64 ID for the table and
|
||||
// partition provided.
|
||||
CreateTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, _ map[string]uint64) error
|
||||
|
||||
// DeleteTableKeys deletes all table keys for the table and partition
|
||||
// provided.
|
||||
DeleteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) error
|
||||
|
||||
// CreateFieldKeys sends a map of string key to uint64 ID for the table and
|
||||
// field provided.
|
||||
CreateFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, _ map[string]uint64) error
|
||||
|
||||
// DeleteTableKeys deletes all field keys for the table and field provided.
|
||||
DeleteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) error
|
||||
|
||||
// WriteShard sends shard data for the table and shard provided.
|
||||
WriteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, msg LogMessage) error
|
||||
|
||||
// DeleteShard deletes all data for the table and shard provided.
|
||||
DeleteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) error
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ WriteLogWriter = (*NopWriteLogWriter)(nil)
|
||||
var _ WriteLogReader = &writeLogReadWriter{}
|
||||
var _ WriteLogWriter = &writeLogReadWriter{}
|
||||
|
||||
// NopWriteLogWriter is a no-op implementation of the WriteLogWriter interface.
|
||||
type NopWriteLogWriter struct{}
|
||||
|
||||
func NewNopWriteLogWriter() *NopWriteLogWriter {
|
||||
return &NopWriteLogWriter{}
|
||||
// writeLogReadWriter is an implementation of the WriteLogReader and WriteLogWriter
|
||||
// interfaces. It uses a WriteLogService implementation (which could be, for
|
||||
// example, an http client or a locally running sub-service) to store its log
|
||||
// messages.
|
||||
type writeLogReadWriter struct {
|
||||
wls WriteLogService
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) CreateTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, m map[string]uint64) error {
|
||||
func NewWriteLogReadWriter(wls WriteLogService) *writeLogReadWriter {
|
||||
return &writeLogReadWriter{
|
||||
wls: wls,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *writeLogReadWriter) CreateTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int, m map[string]uint64) error {
|
||||
msg := PartitionKeyMap{
|
||||
TableKey: qtid.Key(),
|
||||
Partition: partition,
|
||||
StringToID: m,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling partition key map to json")
|
||||
}
|
||||
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
|
||||
if err := w.wls.AppendMessage(bucket, keysFileName, version, b); err != nil {
|
||||
return errors.Wrapf(err, "appending partition key message: %s, %d", keysFileName, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) DeleteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) error {
|
||||
func (w *writeLogReadWriter) DeleteTableKeys(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) error {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
return w.wls.DeleteLog(bucket, keysFileName, version)
|
||||
}
|
||||
|
||||
func (w *writeLogReadWriter) CreateFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, m map[string]uint64) error {
|
||||
msg := FieldKeyMap{
|
||||
TableKey: qtid.Key(),
|
||||
Field: field,
|
||||
StringToID: m,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling field key map to json")
|
||||
}
|
||||
|
||||
bucket := fieldBucket(qtid.Key(), field)
|
||||
|
||||
if err := w.wls.AppendMessage(bucket, keysFileName, version, b); err != nil {
|
||||
return errors.Wrapf(err, "appending field key message: %s, %d", keysFileName, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) CreateFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int, m map[string]uint64) error {
|
||||
func (w *writeLogReadWriter) DeleteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) error {
|
||||
bucket := fieldBucket(qtid.Key(), field)
|
||||
return w.wls.DeleteLog(bucket, keysFileName, version)
|
||||
}
|
||||
|
||||
func (w *writeLogReadWriter) WriteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, msg LogMessage) error {
|
||||
b, err := MarshalLogMessage(msg, EncodeTypeJSON)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalling log message")
|
||||
}
|
||||
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
shardKey := shardKey(shard)
|
||||
|
||||
if err := w.wls.AppendMessage(bucket, shardKey, version, b); err != nil {
|
||||
return errors.Wrapf(err, "appending shard key message: %s, %d", shardKey, version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) DeleteFieldKeys(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) error {
|
||||
return nil
|
||||
}
|
||||
func (w *writeLogReadWriter) DeleteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) error {
|
||||
bucket := partitionBucket(qtid.Key(), partition)
|
||||
shardKey := shardKey(shard)
|
||||
|
||||
func (w *NopWriteLogWriter) WriteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int, msg LogMessage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *NopWriteLogWriter) DeleteShard(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteLogReader provides the interface for all reads from the write log.
|
||||
type WriteLogReader interface {
|
||||
ShardReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) ShardReader
|
||||
TableKeyReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) TableKeyReader
|
||||
FieldKeyReader(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) FieldKeyReader
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ WriteLogReader = (*NopWriteLogReader)(nil)
|
||||
|
||||
// NopWriteLogReader is a no-op implementation of the WriteLogReader interface.
|
||||
type NopWriteLogReader struct{}
|
||||
|
||||
func NewNopWriteLogReader() *NopWriteLogReader {
|
||||
return &NopWriteLogReader{}
|
||||
}
|
||||
|
||||
func (w *NopWriteLogReader) TableKeyReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) TableKeyReader {
|
||||
return NewNopTableKeyReader()
|
||||
}
|
||||
|
||||
func (w *NopWriteLogReader) FieldKeyReader(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) FieldKeyReader {
|
||||
return NewNopFieldKeyReader()
|
||||
}
|
||||
|
||||
func (w *NopWriteLogReader) ShardReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) ShardReader {
|
||||
return NewNopShardReader()
|
||||
return w.wls.DeleteLog(bucket, shardKey, version)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
type TableKeyReader interface {
|
||||
Open() error
|
||||
Read() (PartitionKeyMap, error)
|
||||
Close() error
|
||||
func (w *writeLogReadWriter) TableKeyReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) TableKeyReader {
|
||||
return newTableKeyReader(w.wls, qtid, partition, version)
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ TableKeyReader = &NopTableKeyReader{}
|
||||
|
||||
// NopTableKeyReader is a no-op implementation of the TableKeyReader
|
||||
// interface.
|
||||
type NopTableKeyReader struct{}
|
||||
|
||||
func NewNopTableKeyReader() *NopTableKeyReader {
|
||||
return &NopTableKeyReader{}
|
||||
type tableKeyReader struct {
|
||||
wl WriteLogService
|
||||
table dax.TableKey
|
||||
partition dax.PartitionNum
|
||||
version int
|
||||
scanner *bufio.Scanner
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func (r *NopTableKeyReader) Open() error { return nil }
|
||||
func (r *NopTableKeyReader) Read() (PartitionKeyMap, error) {
|
||||
return PartitionKeyMap{}, io.EOF
|
||||
func newTableKeyReader(wl WriteLogService, qtid dax.QualifiedTableID, partition dax.PartitionNum, version int) *tableKeyReader {
|
||||
r := &tableKeyReader{
|
||||
wl: wl,
|
||||
table: qtid.Key(),
|
||||
partition: partition,
|
||||
version: version,
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *tableKeyReader) Open() error {
|
||||
bucket := partitionBucket(r.table, r.partition)
|
||||
|
||||
reader, closer, err := r.wl.LogReader(bucket, keysFileName, r.version)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting log reader: %s, %s, %d", bucket, keysFileName, r.version)
|
||||
}
|
||||
|
||||
r.closer = closer
|
||||
r.scanner = bufio.NewScanner(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *tableKeyReader) Read() (PartitionKeyMap, error) {
|
||||
if r.scanner == nil {
|
||||
return PartitionKeyMap{}, io.EOF
|
||||
}
|
||||
|
||||
var b []byte
|
||||
var out PartitionKeyMap
|
||||
|
||||
if r.scanner.Scan() {
|
||||
b = r.scanner.Bytes()
|
||||
if err := json.Unmarshal(b, &out); err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
if err := r.scanner.Err(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
return out, io.EOF
|
||||
}
|
||||
|
||||
func (r *tableKeyReader) Close() error {
|
||||
if r.closer != nil {
|
||||
return r.closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (r *NopTableKeyReader) Close() error { return nil }
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
type FieldKeyReader interface {
|
||||
Open() error
|
||||
Read() (FieldKeyMap, error)
|
||||
Close() error
|
||||
func (w *writeLogReadWriter) FieldKeyReader(ctx context.Context, qtid dax.QualifiedTableID, field dax.FieldName, version int) FieldKeyReader {
|
||||
return newFieldKeyReader(w.wls, qtid, field, version)
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ FieldKeyReader = &NopFieldKeyReader{}
|
||||
|
||||
// NopFieldKeyReader is a no-op implementation of the FieldKeyReader
|
||||
// interface.
|
||||
type NopFieldKeyReader struct{}
|
||||
|
||||
func NewNopFieldKeyReader() *NopFieldKeyReader {
|
||||
return &NopFieldKeyReader{}
|
||||
type fieldKeyReader struct {
|
||||
wl WriteLogService
|
||||
table dax.TableKey
|
||||
field dax.FieldName
|
||||
version int
|
||||
scanner *bufio.Scanner
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func (r *NopFieldKeyReader) Open() error { return nil }
|
||||
func (r *NopFieldKeyReader) Read() (FieldKeyMap, error) {
|
||||
return FieldKeyMap{}, io.EOF
|
||||
func newFieldKeyReader(wl WriteLogService, qtid dax.QualifiedTableID, field dax.FieldName, version int) *fieldKeyReader {
|
||||
r := &fieldKeyReader{
|
||||
wl: wl,
|
||||
table: qtid.Key(),
|
||||
field: field,
|
||||
version: version,
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *fieldKeyReader) Open() error {
|
||||
bucket := fieldBucket(r.table, r.field)
|
||||
|
||||
reader, closer, err := r.wl.LogReader(bucket, keysFileName, r.version)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting log reader: %s, %s, %d", bucket, keysFileName, r.version)
|
||||
}
|
||||
|
||||
r.closer = closer
|
||||
r.scanner = bufio.NewScanner(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *fieldKeyReader) Read() (FieldKeyMap, error) {
|
||||
if r.scanner == nil {
|
||||
return FieldKeyMap{}, io.EOF
|
||||
}
|
||||
|
||||
var b []byte
|
||||
var out FieldKeyMap
|
||||
|
||||
if r.scanner.Scan() {
|
||||
b = r.scanner.Bytes()
|
||||
if err := json.Unmarshal(b, &out); err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
if err := r.scanner.Err(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
return out, io.EOF
|
||||
}
|
||||
|
||||
func (r *fieldKeyReader) Close() error {
|
||||
if r.closer != nil {
|
||||
return r.closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (r *NopFieldKeyReader) Close() error { return nil }
|
||||
|
||||
////////////////////////////////////////////////
|
||||
|
||||
type ShardReader interface {
|
||||
Open() error
|
||||
Read() (LogMessage, error)
|
||||
Close() error
|
||||
func (w *writeLogReadWriter) ShardReader(ctx context.Context, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) ShardReader {
|
||||
return newShardReader(w.wls, qtid, partition, shard, version)
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ ShardReader = &NopShardReader{}
|
||||
|
||||
// NopShardReader is a no-op implementation of the ShardReader interface.
|
||||
type NopShardReader struct{}
|
||||
|
||||
func NewNopShardReader() *NopShardReader {
|
||||
return &NopShardReader{}
|
||||
type shardReader struct {
|
||||
wl WriteLogService
|
||||
table dax.TableKey
|
||||
partition dax.PartitionNum
|
||||
shard dax.ShardNum
|
||||
version int
|
||||
scanner *bufio.Scanner
|
||||
closer io.Closer
|
||||
}
|
||||
|
||||
func (r *NopShardReader) Open() error { return nil }
|
||||
func (r *NopShardReader) Read() (LogMessage, error) {
|
||||
func newShardReader(wl WriteLogService, qtid dax.QualifiedTableID, partition dax.PartitionNum, shard dax.ShardNum, version int) *shardReader {
|
||||
r := &shardReader{
|
||||
wl: wl,
|
||||
table: qtid.Key(),
|
||||
partition: partition,
|
||||
shard: shard,
|
||||
version: version,
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *shardReader) Open() error {
|
||||
bucket := partitionBucket(r.table, r.partition)
|
||||
shardKey := shardKey(r.shard)
|
||||
|
||||
reader, closer, err := r.wl.LogReader(bucket, shardKey, r.version)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting log reader: %s, %s, %d", bucket, shardKey, r.version)
|
||||
}
|
||||
|
||||
r.closer = closer
|
||||
r.scanner = bufio.NewScanner(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *shardReader) Read() (LogMessage, error) {
|
||||
if r.scanner == nil {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
if r.scanner.Scan() {
|
||||
return UnmarshalLogMessage(r.scanner.Bytes())
|
||||
}
|
||||
if err := r.scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, io.EOF
|
||||
}
|
||||
func (r *NopShardReader) Close() error { return nil }
|
||||
|
||||
//////////////// Messages ///////////////////////
|
||||
|
||||
type PartitionKeyMap struct {
|
||||
TableKey dax.TableKey `json:"table-key"`
|
||||
Partition dax.PartitionNum `json:"partition"`
|
||||
StringToID map[string]uint64 `json:"string-to-id"`
|
||||
}
|
||||
|
||||
type FieldKeyMap struct {
|
||||
TableKey dax.TableKey `json:"table-key"`
|
||||
Field dax.FieldName `json:"field"`
|
||||
StringToID map[string]uint64 `json:"string-to-id"`
|
||||
}
|
||||
|
||||
const (
|
||||
logMessageTypeImportRoaring byte = iota
|
||||
logMessageTypeImport
|
||||
logMessageTypeImportValue
|
||||
logMessageTypeImportRoaringShard
|
||||
)
|
||||
|
||||
// encoderKey* are part of the log message header. They indicate what encoding
|
||||
// type a specific log message is serialized with.
|
||||
const (
|
||||
encoderKeyJSON byte = iota
|
||||
)
|
||||
|
||||
const (
|
||||
EncodeTypeJSON string = "json"
|
||||
|
||||
// encodeVersion refers to the version of the structs used to represent the
|
||||
// log messages. If we change structs, we'll need to modify this version
|
||||
// number and maintain the previous version of the structs somewhere for
|
||||
// deserialization.
|
||||
encodeVersion byte = 1
|
||||
)
|
||||
|
||||
// logMessageEncoder is implemented by any encoder used to serialize LogMessages
|
||||
// to []byte.
|
||||
type logMessageEncoder interface {
|
||||
Key() byte
|
||||
Marshal(LogMessage) ([]byte, error)
|
||||
Unmarshal([]byte, LogMessage) error
|
||||
}
|
||||
|
||||
// LogMessage is implemented by a variety of types which can be serialized as
|
||||
// messages to the WriteLogger.
|
||||
type LogMessage interface{}
|
||||
|
||||
// MarshalLogMessage serializes the log message and prepends additional encoding
|
||||
// information to each message. Currently, we prepend three bytes to each log
|
||||
// message:
|
||||
// byte[0]: encodeVersion - this is currently a constant within the code. If we
|
||||
// modify structs such that they encode differently, we'll have to change the
|
||||
// constant and keep previous versions of structs for deserialization.
|
||||
// byte[1]: encodeType (e.g. "json", etc.)
|
||||
// byte[2]: logMessageType
|
||||
//
|
||||
// If we get into a situation where we want more flexibility in these message
|
||||
// header bytes—for example, if we want to use more than three bytes—we could do
|
||||
// something with the first bit of the encodeVersion: if it's 1, that could
|
||||
// indicate that there are additional header bytes, and the following seven bits
|
||||
// could indicate how many.
|
||||
func MarshalLogMessage(msg LogMessage, encode string) ([]byte, error) {
|
||||
encoder, err := getEncoderByType(encode)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting encoder by type")
|
||||
func (r *shardReader) Close() error {
|
||||
if r.closer != nil {
|
||||
return r.closer.Close()
|
||||
}
|
||||
|
||||
logMessageType, err := getLogMessageType(msg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting log message type")
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
|
||||
buf, err = encoder.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshaling log message")
|
||||
}
|
||||
|
||||
return append([]byte{encodeVersion, encoder.Key(), logMessageType}, buf...), nil
|
||||
}
|
||||
|
||||
// UnmarshalLogMessage deserializes the log message based on the log message
|
||||
// type info.
|
||||
func UnmarshalLogMessage(b []byte) (LogMessage, error) {
|
||||
if len(b) < 3 {
|
||||
return nil, errors.New(errors.ErrUncoded, "log record does not contain a full header")
|
||||
}
|
||||
|
||||
encVersion := b[0]
|
||||
encKey := b[1]
|
||||
logMessageType := b[2]
|
||||
|
||||
// Ensure that the log message is able to be handled by this code. If we
|
||||
// increment the constant encodeVersion, we'll need to modify this to handle
|
||||
// the log based on previous encodeVersions.
|
||||
if encVersion != encodeVersion {
|
||||
return nil, errors.Errorf("encode version is unsupported: %d", encVersion)
|
||||
}
|
||||
|
||||
msg, err := logMessageByType(logMessageType)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting log message by type")
|
||||
}
|
||||
|
||||
encoder, err := getEncoderByKey(encKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting encoder by key")
|
||||
}
|
||||
|
||||
if err := encoder.Unmarshal(b[3:], &msg); err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshaling log message")
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func logMessageByType(typ byte) (LogMessage, error) {
|
||||
switch typ {
|
||||
case logMessageTypeImportRoaring:
|
||||
return &ImportRoaringMessage{}, nil
|
||||
case logMessageTypeImport:
|
||||
return &ImportMessage{}, nil
|
||||
case logMessageTypeImportValue:
|
||||
return &ImportValueMessage{}, nil
|
||||
case logMessageTypeImportRoaringShard:
|
||||
return &ImportRoaringShardMessage{}, nil
|
||||
default:
|
||||
return nil, errors.Errorf("unknown message type %d", typ)
|
||||
}
|
||||
}
|
||||
|
||||
func getLogMessageType(m LogMessage) (byte, error) {
|
||||
switch m.(type) {
|
||||
case *ImportRoaringMessage:
|
||||
return logMessageTypeImportRoaring, nil
|
||||
case *ImportMessage:
|
||||
return logMessageTypeImport, nil
|
||||
case *ImportValueMessage:
|
||||
return logMessageTypeImportValue, nil
|
||||
case *ImportRoaringShardMessage:
|
||||
return logMessageTypeImportRoaringShard, nil
|
||||
default:
|
||||
return 0, errors.Errorf("don't have type for message %#v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func getEncoderByType(encode string) (logMessageEncoder, error) {
|
||||
switch encode {
|
||||
case EncodeTypeJSON:
|
||||
return &encoderJSON{}, nil
|
||||
default:
|
||||
return nil, errors.Errorf("invalid encode type: %s", encode)
|
||||
}
|
||||
}
|
||||
|
||||
func getEncoderByKey(id byte) (logMessageEncoder, error) {
|
||||
switch id {
|
||||
case encoderKeyJSON:
|
||||
return &encoderJSON{}, nil
|
||||
default:
|
||||
return nil, errors.Errorf("invalid encode type: %d", id)
|
||||
}
|
||||
}
|
||||
|
||||
type ImportRoaringMessage struct {
|
||||
LogMessage `json:"-"`
|
||||
|
||||
Table string `json:"table"`
|
||||
Field string `json:"field"`
|
||||
Partition int `json:"partition"`
|
||||
Shard uint64 `json:"shard"`
|
||||
Clear bool `json:"clear"`
|
||||
Action string `json:"action"` // [set, clear, overwrite]
|
||||
Block int `json:"block"`
|
||||
Views map[string][]byte `json:"views"`
|
||||
UpdateExistence bool `json:"update-existence"`
|
||||
}
|
||||
|
||||
type ImportMessage struct {
|
||||
LogMessage `json:"-"`
|
||||
|
||||
Table string `json:"table"`
|
||||
Field string `json:"field"`
|
||||
Partition int `json:"partition"`
|
||||
Shard uint64 `json:"shard"`
|
||||
RowIDs []uint64 `json:"row-ids"`
|
||||
ColumnIDs []uint64 `json:"column-ids"`
|
||||
RowKeys []string `json:"row-keys"`
|
||||
ColumnKeys []string `json:"column-keys"`
|
||||
Timestamps []int64 `json:"timestamps"`
|
||||
Clear bool `json:"clear"`
|
||||
|
||||
// options
|
||||
IgnoreKeyCheck bool `json:"ignore-key-check"`
|
||||
Presorted bool `json:"presorted"`
|
||||
}
|
||||
|
||||
type ImportValueMessage struct {
|
||||
LogMessage `json:"-"`
|
||||
|
||||
Table string `json:"table"`
|
||||
Field string `json:"field"`
|
||||
Partition int `json:"partition"`
|
||||
Shard uint64 `json:"shard"`
|
||||
ColumnIDs []uint64 `json:"column-ids"`
|
||||
ColumnKeys []string `json:"column-keys"`
|
||||
Values []int64 `json:"values"`
|
||||
FloatValues []float64 `json:"float-values"`
|
||||
TimestampValues []time.Time `json:"timestamp-values"`
|
||||
StringValues []string `json:"string-values"`
|
||||
Clear bool `json:"clear"`
|
||||
|
||||
// options
|
||||
IgnoreKeyCheck bool `json:"ignore-key-check"`
|
||||
Presorted bool `json:"presorted"`
|
||||
}
|
||||
|
||||
type ImportRoaringShardMessage struct {
|
||||
LogMessage `json:"-"`
|
||||
|
||||
Table string `json:"table"`
|
||||
Partition int `json:"partition"`
|
||||
Shard uint64 `json:"shard"`
|
||||
Views []RoaringUpdate `json:"views"`
|
||||
}
|
||||
|
||||
// RoaringUpdate is identical to featurebase.RoaringUpdate, but we
|
||||
// can't import it due to import cycles. TODO featurebase top level
|
||||
// shouldn't import dax stuff... all the types it needs should just be
|
||||
// in the top level.
|
||||
type RoaringUpdate struct {
|
||||
Field string `json:"field"`
|
||||
View string `json:"view"`
|
||||
Clear []byte `json:"clear"`
|
||||
Set []byte `json:"set"`
|
||||
ClearRecords bool `json:"clear-records"`
|
||||
}
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ logMessageEncoder = (*encoderJSON)(nil)
|
||||
|
||||
// encoderJSON is an implementation of the logMessageEncoder interface which
|
||||
// encodes LogMessages as JSON.
|
||||
type encoderJSON struct{}
|
||||
|
||||
func (e *encoderJSON) Key() byte {
|
||||
return encoderKeyJSON
|
||||
}
|
||||
|
||||
func (e *encoderJSON) Marshal(msg LogMessage) ([]byte, error) {
|
||||
return json.Marshal(msg)
|
||||
}
|
||||
|
||||
func (e *encoderJSON) Unmarshal(b []byte, msg LogMessage) error {
|
||||
return json.Unmarshal(b, msg)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,318 +0,0 @@
|
|||
package queryer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
featurebase "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
"github.com/molecula/featurebase/v3/dax/mds/controller/partitioner"
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
)
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ featurebase.ComputeAPI = &qualifiedComputeAPI{}
|
||||
|
||||
type qualifiedComputeAPI struct {
|
||||
mds MDS
|
||||
qual dax.TableQualifier
|
||||
}
|
||||
|
||||
func NewQualifiedComputeAPI(qual dax.TableQualifier, mds MDS) *qualifiedComputeAPI {
|
||||
return &qualifiedComputeAPI{
|
||||
mds: mds,
|
||||
qual: qual,
|
||||
}
|
||||
}
|
||||
|
||||
// importer is used to get the Importer based on the provided address. We used
|
||||
// to maintain a map of different importers (pointers to computers) running
|
||||
// in-process, but since getting rid of that logic this method is currently just
|
||||
// a wrapper around NewComputeImporter. I'm leaving it like this for now in case
|
||||
// it makes sense for this to become a cache of computer clients.
|
||||
func (c *qualifiedComputeAPI) importer(addr dax.Address) (Importer, error) {
|
||||
return NewComputeImporter(addr), nil
|
||||
}
|
||||
|
||||
func (c *qualifiedComputeAPI) Import(ctx context.Context, qcx *featurebase.Qcx, req *featurebase.ImportRequest, opts ...featurebase.ImportOption) error {
|
||||
// If the request is empty, return early.
|
||||
if len(req.ColumnKeys) == 0 && len(req.ColumnIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine if the columns use string keys or not.
|
||||
var hasColKeys bool
|
||||
if len(req.ColumnKeys) > 0 {
|
||||
hasColKeys = true
|
||||
if len(req.ColumnIDs) > 0 {
|
||||
return errors.Errorf("import request has both column ids and keys")
|
||||
}
|
||||
}
|
||||
// Determine if the rows use string keys or not.
|
||||
var hasRowKeys bool
|
||||
if len(req.RowKeys) > 0 {
|
||||
hasRowKeys = true
|
||||
if len(req.RowIDs) > 0 {
|
||||
return errors.Errorf("import request has both row ids and keys")
|
||||
}
|
||||
}
|
||||
|
||||
partitioner := partitioner.NewPartitioner()
|
||||
|
||||
reqPerShard := make(map[uint64]*featurebase.ImportRequest)
|
||||
|
||||
tkey, err := c.indexToQualifiedTableKey(ctx, req.Index)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "converting index to qualified table key")
|
||||
}
|
||||
stkey := string(tkey)
|
||||
|
||||
qtid := tkey.QualifiedTableID()
|
||||
|
||||
qtbl, err := c.mds.Table(ctx, qtid)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting table for import: %s", req.Index)
|
||||
}
|
||||
|
||||
// Translate column keys.
|
||||
if hasColKeys {
|
||||
// Get the partitions (and therefore, nodes) responsible for the keys.
|
||||
pMap := partitioner.PartitionsForKeys(qtbl.Key(), qtbl.PartitionN, req.ColumnKeys...)
|
||||
|
||||
colIDs := make([]uint64, 0, len(req.ColumnKeys))
|
||||
for pNum := range pMap {
|
||||
addr, err := c.mds.IngestPartition(ctx, qtid, pNum)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting ingest partition: %d", pNum)
|
||||
}
|
||||
|
||||
importer, err := c.importer(addr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting importer for address: %s", addr)
|
||||
}
|
||||
|
||||
colKeyMap, err := importer.CreateIndexKeys(ctx, stkey, req.ColumnKeys...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating index keys")
|
||||
}
|
||||
for i := range req.ColumnKeys {
|
||||
colIDs = append(colIDs, colKeyMap[req.ColumnKeys[i]])
|
||||
}
|
||||
}
|
||||
req.ColumnIDs = colIDs
|
||||
}
|
||||
|
||||
// Translate row keys.
|
||||
if hasRowKeys {
|
||||
addr, err := c.mds.IngestPartition(ctx, qtid, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting ingest partition: %d", 0)
|
||||
}
|
||||
|
||||
importer, err := c.importer(addr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting importer for address: %s", addr)
|
||||
}
|
||||
|
||||
rowKeyMap, err := importer.CreateFieldKeys(ctx, stkey, req.Field, req.RowKeys...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating field keys")
|
||||
}
|
||||
rowIDs := make([]uint64, len(req.RowKeys))
|
||||
for i := range req.RowKeys {
|
||||
rowIDs[i] = rowKeyMap[req.RowKeys[i]]
|
||||
}
|
||||
req.RowIDs = rowIDs
|
||||
}
|
||||
|
||||
// Loop over the column ids and split them up by shard.
|
||||
for ii := range req.ColumnIDs {
|
||||
// Determine shard.
|
||||
shard := req.ColumnIDs[ii] / featurebase.ShardWidth
|
||||
|
||||
// Get or create the ImportRequest for this shard.
|
||||
shardedReq, found := reqPerShard[shard]
|
||||
if !found {
|
||||
shardedReq = &featurebase.ImportRequest{
|
||||
Index: stkey,
|
||||
IndexCreatedAt: req.IndexCreatedAt,
|
||||
Field: req.Field,
|
||||
FieldCreatedAt: req.FieldCreatedAt,
|
||||
Shard: shard,
|
||||
RowIDs: []uint64{},
|
||||
ColumnIDs: []uint64{},
|
||||
RowKeys: []string{},
|
||||
ColumnKeys: []string{},
|
||||
Timestamps: []int64{},
|
||||
Clear: req.Clear,
|
||||
}
|
||||
reqPerShard[shard] = shardedReq
|
||||
}
|
||||
|
||||
shardedReq.ColumnIDs = append(shardedReq.ColumnIDs, req.ColumnIDs[ii])
|
||||
if len(req.RowIDs) > 0 {
|
||||
shardedReq.RowIDs = append(shardedReq.RowIDs, req.RowIDs[ii])
|
||||
}
|
||||
if len(req.Timestamps) > 0 {
|
||||
shardedReq.Timestamps = append(shardedReq.Timestamps, req.Timestamps[ii])
|
||||
}
|
||||
}
|
||||
|
||||
// Send each of the sharded ImportRequests to the appropriate compute node.
|
||||
for shard, req := range reqPerShard {
|
||||
addr, err := c.mds.IngestShard(ctx, qtid, dax.ShardNum(shard))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting ingest shard: %d", shard)
|
||||
}
|
||||
|
||||
importer, err := c.importer(addr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting importer for address: %s", addr)
|
||||
}
|
||||
|
||||
importer.Import(ctx, req,
|
||||
featurebase.OptImportOptionsClear(req.Clear),
|
||||
featurebase.OptImportOptionsIgnoreKeyCheck(true),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *qualifiedComputeAPI) ImportValue(ctx context.Context, qcx *featurebase.Qcx, req *featurebase.ImportValueRequest, opts ...featurebase.ImportOption) error {
|
||||
// If the request is empty, return early.
|
||||
if len(req.ColumnKeys) == 0 && len(req.ColumnIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine if the columns use string keys or not.
|
||||
var hasColKeys bool
|
||||
if len(req.ColumnKeys) > 0 {
|
||||
hasColKeys = true
|
||||
if len(req.ColumnIDs) > 0 {
|
||||
return errors.Errorf("import value request has both column ids and keys")
|
||||
}
|
||||
}
|
||||
|
||||
partitioner := partitioner.NewPartitioner()
|
||||
|
||||
reqPerShard := make(map[uint64]*featurebase.ImportValueRequest)
|
||||
|
||||
tkey, err := c.indexToQualifiedTableKey(ctx, req.Index)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "converting index to qualified table key")
|
||||
}
|
||||
stkey := string(tkey)
|
||||
|
||||
qtid := tkey.QualifiedTableID()
|
||||
|
||||
qtbl, err := c.mds.Table(ctx, qtid)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting table for importvalue: %s", req.Index)
|
||||
}
|
||||
|
||||
// Translate column keys.
|
||||
if hasColKeys {
|
||||
// Get the partitions (and therefore, nodes) responsible for the keys.
|
||||
pMap := partitioner.PartitionsForKeys(qtbl.Key(), qtbl.PartitionN, req.ColumnKeys...)
|
||||
|
||||
colIDs := make([]uint64, 0, len(req.ColumnKeys))
|
||||
for pNum := range pMap {
|
||||
addr, err := c.mds.IngestPartition(ctx, qtid, pNum)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting ingest partition: %d", pNum)
|
||||
}
|
||||
|
||||
importer, err := c.importer(addr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting importer for address: %s", addr)
|
||||
}
|
||||
|
||||
colKeyMap, err := importer.CreateIndexKeys(ctx, stkey, req.ColumnKeys...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating index keys")
|
||||
}
|
||||
for i := range req.ColumnKeys {
|
||||
colIDs = append(colIDs, colKeyMap[req.ColumnKeys[i]])
|
||||
}
|
||||
}
|
||||
req.ColumnIDs = colIDs
|
||||
}
|
||||
|
||||
// Loop over the column ids and split them up by shard.
|
||||
for ii := range req.ColumnIDs {
|
||||
// Determine shard.
|
||||
shard := req.ColumnIDs[ii] / featurebase.ShardWidth
|
||||
|
||||
// Get or create the ImportRequest for this shard.
|
||||
shardedReq, found := reqPerShard[shard]
|
||||
if !found {
|
||||
shardedReq = &featurebase.ImportValueRequest{
|
||||
Index: stkey,
|
||||
IndexCreatedAt: req.IndexCreatedAt,
|
||||
Field: req.Field,
|
||||
FieldCreatedAt: req.FieldCreatedAt,
|
||||
Shard: shard,
|
||||
ColumnIDs: []uint64{},
|
||||
ColumnKeys: []string{},
|
||||
Values: []int64{},
|
||||
FloatValues: []float64{},
|
||||
TimestampValues: []time.Time{},
|
||||
StringValues: []string{},
|
||||
Clear: req.Clear,
|
||||
}
|
||||
reqPerShard[shard] = shardedReq
|
||||
}
|
||||
|
||||
shardedReq.ColumnIDs = append(shardedReq.ColumnIDs, req.ColumnIDs[ii])
|
||||
if len(req.Values) > 0 {
|
||||
shardedReq.Values = append(shardedReq.Values, req.Values[ii])
|
||||
}
|
||||
// TODO: The following would populate the other value types, but the
|
||||
// EncodeImportValues doesn't seem to use this data. So we need to track
|
||||
// down how this is being used.
|
||||
//
|
||||
// if len(req.FloatValues) > 0 {
|
||||
// shardedReq.FloatValues = append(shardedReq.FloatValues, req.FloatValues[ii])
|
||||
// }
|
||||
// if len(req.TimestampValues) > 0 {
|
||||
// shardedReq.TimestampValues = append(shardedReq.TimestampValues, req.TimestampValues[ii])
|
||||
// }
|
||||
// if len(req.StringValues) > 0 {
|
||||
// shardedReq.StringValues = append(shardedReq.StringValues, req.StringValues[ii])
|
||||
// }
|
||||
}
|
||||
|
||||
// Send each of the sharded ImportValueRequests to the appropriate compute
|
||||
// node.
|
||||
for shard, req := range reqPerShard {
|
||||
addr, err := c.mds.IngestShard(ctx, qtid, dax.ShardNum(shard))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting ingest shard: %d", shard)
|
||||
}
|
||||
|
||||
importer, err := c.importer(addr)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "getting importer for address: %s", addr)
|
||||
}
|
||||
|
||||
importer.ImportValue(ctx, req,
|
||||
featurebase.OptImportOptionsClear(req.Clear),
|
||||
featurebase.OptImportOptionsIgnoreKeyCheck(true),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *qualifiedComputeAPI) Txf() *featurebase.TxFactory {
|
||||
return &featurebase.TxFactory{}
|
||||
}
|
||||
|
||||
func (c *qualifiedComputeAPI) indexToQualifiedTableKey(ctx context.Context, index string) (dax.TableKey, error) {
|
||||
qtid, err := c.mds.TableID(ctx, c.qual, dax.TableName(index))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "converting index to qualified table id")
|
||||
}
|
||||
return qtid.Key(), nil
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
package queryer
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
featurebase "github.com/molecula/featurebase/v3"
|
||||
"github.com/molecula/featurebase/v3/client"
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
)
|
||||
|
||||
// Ensure type implements interface.
|
||||
var _ Importer = &ComputeImporter{}
|
||||
|
||||
// ComputeImporter is an implementation of the Importer interface which uses a
|
||||
// featurebase client to communicate with the compute node.
|
||||
type ComputeImporter struct {
|
||||
addr dax.Address
|
||||
}
|
||||
|
||||
func NewComputeImporter(addr dax.Address) *ComputeImporter {
|
||||
return &ComputeImporter{
|
||||
addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
func (ci *ComputeImporter) CreateIndexKeys(ctx context.Context, index string, keys ...string) (map[string]uint64, error) {
|
||||
fbClient, err := fbClient(ci.addr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting fb client")
|
||||
}
|
||||
|
||||
idx := client.NewIndex(index)
|
||||
return fbClient.CreateIndexKeys(idx, keys...)
|
||||
}
|
||||
|
||||
func (ci *ComputeImporter) CreateFieldKeys(ctx context.Context, index, field string, keys ...string) (map[string]uint64, error) {
|
||||
fbClient, err := fbClient(ci.addr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getting fb client")
|
||||
}
|
||||
|
||||
idx := client.NewIndex(index)
|
||||
fld := idx.Field(field)
|
||||
return fbClient.CreateFieldKeys(fld, keys...)
|
||||
}
|
||||
|
||||
func (ci *ComputeImporter) Import(ctx context.Context, req *featurebase.ImportRequest, opts ...featurebase.ImportOption) error {
|
||||
fbClient, err := fbClient(ci.addr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting fb client")
|
||||
}
|
||||
|
||||
idx := client.NewIndex(req.Index)
|
||||
fld := idx.Field(req.Field)
|
||||
return fbClient.Import(fld, req.Shard, req.RowIDs, req.ColumnIDs, req.Clear)
|
||||
}
|
||||
|
||||
func (ci *ComputeImporter) ImportValue(ctx context.Context, req *featurebase.ImportValueRequest, opts ...featurebase.ImportOption) error {
|
||||
fbClient, err := fbClient(ci.addr)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting fb client")
|
||||
}
|
||||
|
||||
idx := client.NewIndex(req.Index)
|
||||
fld := idx.Field(req.Field)
|
||||
return fbClient.ImportValues(fld, req.Shard, req.Values, req.ColumnIDs, req.Clear)
|
||||
}
|
||||
|
|
@ -122,9 +122,6 @@ func (q *Queryer) QuerySQL(ctx context.Context, qual dax.TableQualifier, sql str
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
// ComputeAPI
|
||||
capi := NewQualifiedComputeAPI(qual, q.mds)
|
||||
|
||||
// SchemaAPI
|
||||
sapi := NewQualifiedSchemaAPI(qual, q.mds)
|
||||
|
||||
|
|
@ -141,7 +138,7 @@ func (q *Queryer) QuerySQL(ctx context.Context, qual dax.TableQualifier, sql str
|
|||
|
||||
systemLayer := systemlayer.NewSystemLayer()
|
||||
|
||||
pl := planner.NewExecutionPlanner(orch, sapi, sysapi, capi, systemLayer, imp, q.orchestrator.logger, sql)
|
||||
pl := planner.NewExecutionPlanner(orch, sapi, sysapi, systemLayer, imp, q.orchestrator.logger, sql)
|
||||
|
||||
planOp, err := pl.CompilePlan(ctx, st)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -16,10 +16,15 @@ ifErr() {
|
|||
fi
|
||||
}
|
||||
|
||||
echo "installing go"
|
||||
echo "installing wget"
|
||||
sudo yum install wget -y
|
||||
ifErr "installing wget"
|
||||
|
||||
echo "installing nss-tools"
|
||||
sudo yum install nss-tools -y
|
||||
ifErr "installing nss-tools"
|
||||
|
||||
echo "installing git"
|
||||
sudo yum install git -y
|
||||
ifErr "installing git"
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import (
|
|||
"github.com/molecula/featurebase/v3/boltdb"
|
||||
"github.com/molecula/featurebase/v3/dax"
|
||||
"github.com/molecula/featurebase/v3/dax/computer"
|
||||
"github.com/molecula/featurebase/v3/dax/computer/alpha"
|
||||
"github.com/molecula/featurebase/v3/disco"
|
||||
"github.com/molecula/featurebase/v3/encoding/proto"
|
||||
petcd "github.com/molecula/featurebase/v3/etcd"
|
||||
|
|
@ -77,9 +76,9 @@ type Command struct {
|
|||
logger loggerLogger
|
||||
queryLogger loggerLogger
|
||||
|
||||
Registrar computer.Registrar
|
||||
writeLogger computer.WriteLogger
|
||||
snapshotter computer.Snapshotter
|
||||
Registrar computer.Registrar
|
||||
writeLogService computer.WriteLogService
|
||||
snapshotService computer.SnapshotService
|
||||
|
||||
Handler pilosa.HandlerI
|
||||
httpHandler http.Handler
|
||||
|
|
@ -151,10 +150,10 @@ func OptCommandSetConfig(config *Config) CommandOption {
|
|||
func OptCommandInjections(inj Injections) CommandOption {
|
||||
return func(c *Command) error {
|
||||
if inj.WriteLogger != nil {
|
||||
c.writeLogger = inj.WriteLogger
|
||||
c.writeLogService = inj.WriteLogger
|
||||
}
|
||||
if inj.Snapshotter != nil {
|
||||
c.snapshotter = inj.Snapshotter
|
||||
c.snapshotService = inj.Snapshotter
|
||||
}
|
||||
c.isComputeNode = inj.IsComputeNode
|
||||
return nil
|
||||
|
|
@ -162,8 +161,8 @@ func OptCommandInjections(inj Injections) CommandOption {
|
|||
}
|
||||
|
||||
type Injections struct {
|
||||
WriteLogger computer.WriteLogger
|
||||
Snapshotter computer.Snapshotter
|
||||
WriteLogger computer.WriteLogService
|
||||
Snapshotter computer.SnapshotService
|
||||
IsComputeNode bool
|
||||
}
|
||||
|
||||
|
|
@ -555,16 +554,16 @@ func (m *Command) setupServer() error {
|
|||
// WriteLogger setup.
|
||||
var wlw computer.WriteLogWriter = computer.NewNopWriteLogWriter()
|
||||
var wlr computer.WriteLogReader = computer.NewNopWriteLogReader()
|
||||
if m.writeLogger != nil {
|
||||
alphaWriteLog := alpha.NewAlphaWriteLog(m.writeLogger)
|
||||
wlr = alphaWriteLog
|
||||
wlw = alphaWriteLog
|
||||
if m.writeLogService != nil {
|
||||
wlrw := computer.NewWriteLogReadWriter(m.writeLogService)
|
||||
wlr = wlrw
|
||||
wlw = wlrw
|
||||
}
|
||||
|
||||
// Snapshotter setup.
|
||||
var snap computer.SnapshotReadWriter = computer.NewNopSnapshotReadWriter()
|
||||
if m.snapshotter != nil {
|
||||
snap = alpha.NewAlphaSnapshot(m.snapshotter)
|
||||
if m.snapshotService != nil {
|
||||
snap = computer.NewSnapshotReadWriter(m.snapshotService)
|
||||
}
|
||||
|
||||
executionPlannerFn := func(e pilosa.Executor, api *pilosa.API, sql string) sql3.CompilePlanner {
|
||||
|
|
@ -572,7 +571,7 @@ func (m *Command) setupServer() error {
|
|||
fsapi := &pilosa.FeatureBaseSystemAPI{API: api}
|
||||
fimp := &batch.FeaturebaseImporter{API: api}
|
||||
|
||||
return planner.NewExecutionPlanner(e, fapi, fsapi, api, m.Server.SystemLayer, fimp, m.logger, sql)
|
||||
return planner.NewExecutionPlanner(e, fapi, fsapi, m.Server.SystemLayer, fimp, m.logger, sql)
|
||||
}
|
||||
|
||||
serverOptions := []pilosa.ServerOption{
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ type ExecutionPlanner struct {
|
|||
executor pilosa.Executor
|
||||
schemaAPI pilosa.SchemaAPI
|
||||
systemAPI pilosa.SystemAPI
|
||||
computeAPI pilosa.ComputeAPI
|
||||
systemLayerAPI pilosa.SystemLayerAPI
|
||||
importer batch.Importer
|
||||
logger logger.Logger
|
||||
|
|
@ -34,12 +33,11 @@ type ExecutionPlanner struct {
|
|||
scopeStack *scopeStack
|
||||
}
|
||||
|
||||
func NewExecutionPlanner(executor pilosa.Executor, schemaAPI pilosa.SchemaAPI, systemAPI pilosa.SystemAPI, computeAPI pilosa.ComputeAPI, systemLayerAPI pilosa.SystemLayerAPI, importer batch.Importer, logger logger.Logger, sql string) *ExecutionPlanner {
|
||||
func NewExecutionPlanner(executor pilosa.Executor, schemaAPI pilosa.SchemaAPI, systemAPI pilosa.SystemAPI, systemLayerAPI pilosa.SystemLayerAPI, importer batch.Importer, logger logger.Logger, sql string) *ExecutionPlanner {
|
||||
return &ExecutionPlanner{
|
||||
executor: executor,
|
||||
schemaAPI: newSystemTableDefintionsWrapper(schemaAPI),
|
||||
systemAPI: systemAPI,
|
||||
computeAPI: computeAPI,
|
||||
systemLayerAPI: systemLayerAPI,
|
||||
importer: importer,
|
||||
logger: logger,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue