mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44:59 +00:00
2919 lines
80 KiB
Go
2919 lines
80 KiB
Go
// Copyright 2017 Pilosa Corp.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pilosa
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bufio"
|
|
"bytes"
|
|
"container/heap"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"hash"
|
|
"io"
|
|
"io/ioutil"
|
|
"math"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/cespare/xxhash"
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/pilosa/pilosa/internal"
|
|
"github.com/pilosa/pilosa/logger"
|
|
"github.com/pilosa/pilosa/pql"
|
|
"github.com/pilosa/pilosa/roaring"
|
|
"github.com/pilosa/pilosa/shardwidth"
|
|
"github.com/pilosa/pilosa/stats"
|
|
"github.com/pilosa/pilosa/syswrap"
|
|
"github.com/pilosa/pilosa/tracing"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
// ShardWidth is the number of column IDs in a shard. It must be a power of 2 greater than or equal to 16.
|
|
// shardWidthExponent = 20 // set in shardwidthNN.go files
|
|
ShardWidth = 1 << shardwidth.Exponent
|
|
|
|
// shardVsContainerExponent is the power of 2 of ShardWith minus the power
|
|
// of two of roaring container width (which is 16).
|
|
// 2^shardVsContainerExponent is the number of containers in a shard row.
|
|
//
|
|
// It is represented in this rather awkward way because calculating the row
|
|
// which a given container is in means dividing by the number of rows per
|
|
// container which is performantly expressed as a right shift by this
|
|
// exponent.
|
|
shardVsContainerExponent = shardwidth.Exponent - 16
|
|
|
|
// width of roaring containers is 2^16
|
|
containerWidth = 1 << 16
|
|
|
|
// snapshotExt is the file extension used for an in-process snapshot.
|
|
snapshotExt = ".snapshotting"
|
|
|
|
// copyExt is the file extension used for the temp file used while copying.
|
|
copyExt = ".copying"
|
|
|
|
// cacheExt is the file extension for persisted cache ids.
|
|
cacheExt = ".cache"
|
|
|
|
// tempExt is the file extension for temporary files.
|
|
tempExt = ".temp"
|
|
|
|
// HashBlockSize is the number of rows in a merkle hash block.
|
|
HashBlockSize = 100
|
|
|
|
// defaultFragmentMaxOpN is the default value for Fragment.MaxOpN.
|
|
defaultFragmentMaxOpN = 10000
|
|
|
|
// Row ids used for boolean fields.
|
|
falseRowID = uint64(0)
|
|
trueRowID = uint64(1)
|
|
|
|
// BSI bits used to check existence & sign.
|
|
bsiExistsBit = 0
|
|
bsiSignBit = 1
|
|
bsiOffsetBit = 2
|
|
|
|
// Roaring bitmap flags.
|
|
roaringFlagBSIv2 = 0x01 // indicates version using low bit for existence
|
|
)
|
|
|
|
// fragment represents the intersection of a field and shard in an index.
|
|
type fragment struct {
|
|
mu sync.RWMutex
|
|
|
|
// Composite identifiers
|
|
index string
|
|
field string
|
|
view string
|
|
shard uint64
|
|
|
|
// File-backed storage
|
|
path string
|
|
flags byte // user-defined flags passed to roaring
|
|
file *os.File
|
|
storage *roaring.Bitmap
|
|
storageData []byte
|
|
opN int // number of ops since snapshot
|
|
|
|
// Cache for row counts.
|
|
CacheType string // passed in by field
|
|
cache cache
|
|
CacheSize uint32
|
|
|
|
// Stats reporting.
|
|
maxRowID uint64
|
|
|
|
// Cache containing full rows (not just counts).
|
|
rowCache bitmapCache
|
|
|
|
// Cached checksums for each block.
|
|
checksums map[int][]byte
|
|
|
|
// Number of operations performed before performing a snapshot.
|
|
// This limits the size of fragments on the heap and flushes them to disk
|
|
// so that they can be mmapped and heap utilization can be kept low.
|
|
MaxOpN int
|
|
|
|
// Logger used for out-of-band log entries.
|
|
Logger logger.Logger
|
|
|
|
// Row attribute storage.
|
|
// This is set by the parent field unless overridden for testing.
|
|
RowAttrStore AttrStore
|
|
|
|
// mutexVector is used for mutex field types. It's checked for an
|
|
// existing value (to clear) prior to setting a new value.
|
|
mutexVector vector
|
|
|
|
stats stats.StatsClient
|
|
}
|
|
|
|
// newFragment returns a new instance of Fragment.
|
|
func newFragment(path, index, field, view string, shard uint64, flags byte) *fragment {
|
|
return &fragment{
|
|
path: path,
|
|
index: index,
|
|
field: field,
|
|
view: view,
|
|
shard: shard,
|
|
flags: flags,
|
|
CacheType: DefaultCacheType,
|
|
CacheSize: DefaultCacheSize,
|
|
|
|
Logger: logger.NopLogger,
|
|
MaxOpN: defaultFragmentMaxOpN,
|
|
|
|
stats: stats.NopStatsClient,
|
|
}
|
|
}
|
|
|
|
// cachePath returns the path to the fragment's cache data.
|
|
func (f *fragment) cachePath() string { return f.path + cacheExt }
|
|
|
|
// Open opens the underlying storage.
|
|
func (f *fragment) Open() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
if err := func() error {
|
|
// Initialize storage in a function so we can close if anything goes wrong.
|
|
f.Logger.Debugf("open storage for index/field/view/fragment: %s/%s/%s/%d", f.index, f.field, f.view, f.shard)
|
|
if err := f.openStorage(); err != nil {
|
|
return errors.Wrap(err, "opening storage")
|
|
}
|
|
|
|
// Fill cache with rows persisted to disk.
|
|
f.Logger.Debugf("open cache for index/field/view/fragment: %s/%s/%s/%d", f.index, f.field, f.view, f.shard)
|
|
if err := f.openCache(); err != nil {
|
|
return errors.Wrap(err, "opening cache")
|
|
}
|
|
|
|
// Clear checksums.
|
|
f.checksums = make(map[int][]byte)
|
|
|
|
// Read last bit to determine max row.
|
|
pos := f.storage.Max()
|
|
f.maxRowID = pos / ShardWidth
|
|
f.stats.Gauge("rows", float64(f.maxRowID), 1.0)
|
|
return nil
|
|
}(); err != nil {
|
|
f.close()
|
|
return err
|
|
}
|
|
|
|
f.Logger.Debugf("successfully opened index/field/view/fragment: %s/%s/%s/%d", f.index, f.field, f.view, f.shard)
|
|
return nil
|
|
}
|
|
|
|
func (f *fragment) reopen() (mustClose bool, err error) {
|
|
if f.file == nil {
|
|
// Open the data file to be mmap'd and used as an ops log.
|
|
f.file, mustClose, err = syswrap.OpenFile(f.path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
|
if err != nil {
|
|
return mustClose, fmt.Errorf("open file: %s", err)
|
|
}
|
|
f.storage.OpWriter = f.file
|
|
}
|
|
return mustClose, nil
|
|
}
|
|
|
|
// openStorage opens the storage bitmap.
|
|
func (f *fragment) openStorage() error {
|
|
// Create a roaring bitmap to serve as storage for the shard.
|
|
if f.storage == nil {
|
|
f.storage = roaring.NewFileBitmap()
|
|
f.storage.Flags = f.flags
|
|
}
|
|
// Open the data file to be mmap'd and used as an ops log.
|
|
file, mustClose, err := syswrap.OpenFile(f.path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
|
if err != nil {
|
|
return fmt.Errorf("open file: %s", err)
|
|
}
|
|
f.file = file
|
|
if mustClose {
|
|
defer f.safeClose()
|
|
}
|
|
|
|
// Lock the underlying file.
|
|
if err := syscall.Flock(int(f.file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
|
|
return fmt.Errorf("flock: %s", err)
|
|
}
|
|
|
|
// If the file is empty then initialize it with an empty bitmap.
|
|
fi, err := f.file.Stat()
|
|
if err != nil {
|
|
return errors.Wrap(err, "statting file before")
|
|
} else if fi.Size() == 0 {
|
|
bi := bufio.NewWriter(f.file)
|
|
if _, err := f.storage.WriteTo(bi); err != nil {
|
|
return fmt.Errorf("init storage file: %s", err)
|
|
}
|
|
bi.Flush()
|
|
_, err = f.file.Stat()
|
|
if err != nil {
|
|
return errors.Wrap(err, "statting file after")
|
|
}
|
|
} else {
|
|
// Mmap the underlying file so it can be zero copied.
|
|
data, err := syswrap.Mmap(int(f.file.Fd()), 0, int(fi.Size()), syscall.PROT_READ, syscall.MAP_SHARED)
|
|
if err == syswrap.ErrMaxMapCountReached {
|
|
f.Logger.Debugf("maximum number of maps reached, reading file instead")
|
|
data, err = ioutil.ReadAll(file)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failure file readall")
|
|
}
|
|
} else if err != nil {
|
|
return errors.Wrap(err, "mmap failed")
|
|
} else {
|
|
f.storageData = data
|
|
// Advise the kernel that the mmap is accessed randomly.
|
|
if err := madvise(f.storageData, syscall.MADV_RANDOM); err != nil {
|
|
return fmt.Errorf("madvise: %s", err)
|
|
}
|
|
}
|
|
|
|
if err := f.storage.UnmarshalBinary(data); err != nil {
|
|
return fmt.Errorf("unmarshal storage: file=%s, err=%s", f.file.Name(), err)
|
|
}
|
|
|
|
}
|
|
|
|
f.opN = f.storage.Info().OpN
|
|
|
|
// Attach the file to the bitmap to act as a write-ahead log.
|
|
f.storage.OpWriter = f.file
|
|
f.rowCache = &simpleCache{make(map[uint64]*Row)}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// openCache initializes the cache from row ids persisted to disk.
|
|
func (f *fragment) openCache() error {
|
|
// Determine cache type from field name.
|
|
switch f.CacheType {
|
|
case CacheTypeRanked:
|
|
f.cache = NewRankCache(f.CacheSize)
|
|
case CacheTypeLRU:
|
|
f.cache = newLRUCache(f.CacheSize)
|
|
case CacheTypeNone:
|
|
f.cache = globalNopCache
|
|
return nil
|
|
default:
|
|
return ErrInvalidCacheType
|
|
}
|
|
|
|
// Read cache data from disk.
|
|
path := f.cachePath()
|
|
buf, err := ioutil.ReadFile(path)
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
} else if err != nil {
|
|
return fmt.Errorf("open cache: %s", err)
|
|
}
|
|
|
|
// Unmarshal cache data.
|
|
var pb internal.Cache
|
|
if err := proto.Unmarshal(buf, &pb); err != nil {
|
|
f.Logger.Printf("error unmarshaling cache data, skipping: path=%s, err=%s", path, err)
|
|
return nil
|
|
}
|
|
|
|
// Read in all rows by ID.
|
|
// This will cause them to be added to the cache.
|
|
for _, id := range pb.IDs {
|
|
n := f.storage.CountRange(id*ShardWidth, (id+1)*ShardWidth)
|
|
f.cache.BulkAdd(id, n)
|
|
}
|
|
f.cache.Invalidate()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close flushes the underlying storage, closes the file and unlocks it.
|
|
func (f *fragment) Close() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.close()
|
|
}
|
|
|
|
func (f *fragment) close() error {
|
|
// Flush cache if closing gracefully.
|
|
if err := f.flushCache(); err != nil {
|
|
f.Logger.Printf("fragment: error flushing cache on close: err=%s, path=%s", err, f.path)
|
|
return errors.Wrap(err, "flushing cache")
|
|
}
|
|
|
|
// Close underlying storage.
|
|
if err := f.closeStorage(); err != nil {
|
|
f.Logger.Printf("fragment: error closing storage: err=%s, path=%s", err, f.path)
|
|
return errors.Wrap(err, "closing storage")
|
|
}
|
|
|
|
// Remove checksums.
|
|
f.checksums = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
// safeClose is unprotected.
|
|
func (f *fragment) safeClose() error {
|
|
// Flush file, unlock & close.
|
|
if f.file != nil {
|
|
if err := f.file.Sync(); err != nil {
|
|
return fmt.Errorf("sync: %s", err)
|
|
}
|
|
if err := syscall.Flock(int(f.file.Fd()), syscall.LOCK_UN); err != nil {
|
|
return fmt.Errorf("unlock: %s", err)
|
|
}
|
|
if err := syswrap.CloseFile(f.file); err != nil {
|
|
return fmt.Errorf("close file: %s", err)
|
|
}
|
|
}
|
|
f.file = nil
|
|
f.storage.OpWriter = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *fragment) closeStorage() error {
|
|
// Clear the storage bitmap so it doesn't access the closed mmap.
|
|
|
|
//f.storage = roaring.NewBitmap()
|
|
|
|
// Unmap the file.
|
|
if f.storageData != nil {
|
|
if err := syswrap.Munmap(f.storageData); err != nil {
|
|
return fmt.Errorf("munmap: %s", err)
|
|
}
|
|
f.storageData = nil
|
|
}
|
|
|
|
if err := f.safeClose(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// opN is determined by how many bit set/clear operations are in the storage
|
|
// write log, so once the storage is closed it should be 0. Opening new
|
|
// storage will set opN appropriately.
|
|
f.opN = 0
|
|
|
|
return nil
|
|
}
|
|
|
|
// row returns a row by ID.
|
|
func (f *fragment) row(rowID uint64) *Row {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.unprotectedRow(rowID)
|
|
}
|
|
|
|
// unprotectedRow returns a row from the row cache if available or from storage
|
|
// (updating the cache).
|
|
func (f *fragment) unprotectedRow(rowID uint64) *Row {
|
|
r, ok := f.rowCache.Fetch(rowID)
|
|
if ok && r != nil {
|
|
return r
|
|
}
|
|
|
|
row := f.rowFromStorage(rowID)
|
|
f.rowCache.Add(rowID, row)
|
|
return row
|
|
}
|
|
|
|
// rowFromStorage clones a row data out of fragment storage and returns it as a
|
|
// Row object.
|
|
func (f *fragment) rowFromStorage(rowID uint64) *Row {
|
|
// Only use a subset of the containers.
|
|
// NOTE: The start & end ranges must be divisible by container width.
|
|
//
|
|
// Note that OffsetRange now returns a new bitmap which uses frozen
|
|
// containers which will use copy-on-write semantics. The actual bitmap
|
|
// and Containers object are new and not shared, but the containers are
|
|
// shared.
|
|
data := f.storage.OffsetRange(f.shard*ShardWidth, rowID*ShardWidth, (rowID+1)*ShardWidth)
|
|
|
|
row := &Row{
|
|
segments: []rowSegment{{
|
|
data: data,
|
|
shard: f.shard,
|
|
writable: true,
|
|
}},
|
|
}
|
|
row.invalidateCount()
|
|
|
|
return row
|
|
}
|
|
|
|
// setBit sets a bit for a given column & row within the fragment.
|
|
// This updates both the on-disk storage and the in-cache bitmap.
|
|
func (f *fragment) setBit(rowID, columnID uint64) (changed bool, err error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
mustClose, err := f.reopen()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "reopening")
|
|
}
|
|
if mustClose {
|
|
defer f.safeClose()
|
|
}
|
|
|
|
// handle mutux field type
|
|
if f.mutexVector != nil {
|
|
if err := f.handleMutex(rowID, columnID); err != nil {
|
|
return changed, errors.Wrap(err, "handling mutex")
|
|
}
|
|
}
|
|
|
|
return f.unprotectedSetBit(rowID, columnID)
|
|
}
|
|
|
|
// handleMutex will clear an existing row and store the new row
|
|
// in the vector.
|
|
func (f *fragment) handleMutex(rowID, columnID uint64) error {
|
|
if existingRowID, found, err := f.mutexVector.Get(columnID); err != nil {
|
|
return errors.Wrap(err, "getting mutex vector data")
|
|
} else if found && existingRowID != rowID {
|
|
if _, err := f.unprotectedClearBit(existingRowID, columnID); err != nil {
|
|
return errors.Wrap(err, "clearing mutex value")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// unprotectedSetBit TODO should be replaced by an invocation of importPositions with a single bit to set.
|
|
func (f *fragment) unprotectedSetBit(rowID, columnID uint64) (changed bool, err error) {
|
|
changed = false
|
|
// Determine the position of the bit in the storage.
|
|
pos, err := f.pos(rowID, columnID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "getting bit pos")
|
|
}
|
|
|
|
// Write to storage.
|
|
if changed, err = f.storage.Add(pos); err != nil {
|
|
return false, errors.Wrap(err, "writing")
|
|
}
|
|
|
|
// Don't update the cache if nothing changed.
|
|
if !changed {
|
|
return changed, nil
|
|
}
|
|
|
|
// Invalidate block checksum.
|
|
delete(f.checksums, int(rowID/HashBlockSize))
|
|
|
|
// Increment number of operations until snapshot is required.
|
|
if err := f.incrementOpN(); err != nil {
|
|
return false, errors.Wrap(err, "incrementing")
|
|
}
|
|
|
|
// If we're using a cache, update it. Otherwise skip the
|
|
// possibly-expensive count operation.
|
|
if f.CacheType != CacheTypeNone {
|
|
n := f.storage.CountRange(rowID*ShardWidth, (rowID+1)*ShardWidth)
|
|
f.cache.Add(rowID, n)
|
|
}
|
|
// Drop the rowCache entry; it's wrong, and we don't want to force
|
|
// a new copy if no one's reading it.
|
|
f.rowCache.Add(rowID, nil)
|
|
|
|
f.stats.Count("setBit", 1, 0.001)
|
|
|
|
// Update row count if they have increased.
|
|
if rowID > f.maxRowID {
|
|
f.maxRowID = rowID
|
|
f.stats.Gauge("rows", float64(f.maxRowID), 1.0)
|
|
}
|
|
|
|
return changed, nil
|
|
}
|
|
|
|
// clearBit clears a bit for a given column & row within the fragment.
|
|
// This updates both the on-disk storage and the in-cache bitmap.
|
|
func (f *fragment) clearBit(rowID, columnID uint64) (bool, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
mustClose, err := f.reopen()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "reopening")
|
|
}
|
|
if mustClose {
|
|
defer f.safeClose()
|
|
}
|
|
return f.unprotectedClearBit(rowID, columnID)
|
|
}
|
|
|
|
// unprotectedClearBit TODO should be replaced by an invocation of
|
|
// importPositions with a single bit to clear.
|
|
func (f *fragment) unprotectedClearBit(rowID, columnID uint64) (changed bool, err error) {
|
|
changed = false
|
|
// Determine the position of the bit in the storage.
|
|
pos, err := f.pos(rowID, columnID)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "getting bit pos")
|
|
}
|
|
|
|
// Write to storage.
|
|
if changed, err = f.storage.Remove(pos); err != nil {
|
|
return false, errors.Wrap(err, "writing")
|
|
}
|
|
|
|
// Don't update the cache if nothing changed.
|
|
if !changed {
|
|
return changed, nil
|
|
}
|
|
|
|
// Invalidate block checksum.
|
|
delete(f.checksums, int(rowID/HashBlockSize))
|
|
|
|
// Increment number of operations until snapshot is required.
|
|
if err := f.incrementOpN(); err != nil {
|
|
return false, errors.Wrap(err, "incrementing")
|
|
}
|
|
|
|
// If we're using a cache, update it. Otherwise skip the
|
|
// possibly-expensive count operation.
|
|
if f.CacheType != CacheTypeNone {
|
|
n := f.storage.CountRange(rowID*ShardWidth, (rowID+1)*ShardWidth)
|
|
f.cache.Add(rowID, n)
|
|
}
|
|
// Drop the rowCache entry; it's wrong, and we don't want to force
|
|
// a new copy if no one's reading it.
|
|
f.rowCache.Add(rowID, nil)
|
|
|
|
f.stats.Count("clearBit", 1, 1.0)
|
|
|
|
return changed, nil
|
|
}
|
|
|
|
// setRow replaces an existing row (specified by rowID) with the given
|
|
// Row. This updates both the on-disk storage and the in-cache bitmap.
|
|
func (f *fragment) setRow(row *Row, rowID uint64) (bool, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
mustClose, err := f.reopen()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "reopening")
|
|
}
|
|
if mustClose {
|
|
defer f.safeClose()
|
|
}
|
|
return f.unprotectedSetRow(row, rowID)
|
|
}
|
|
|
|
func (f *fragment) unprotectedSetRow(row *Row, rowID uint64) (changed bool, err error) {
|
|
// TODO: In order to return `changed`, we need to first compare
|
|
// the existing row with the given row. Determine if the overhead
|
|
// of this is worth having `changed`.
|
|
// For now we will assume changed is always true.
|
|
changed = true
|
|
|
|
// First container of the row in storage.
|
|
headContainerKey := rowID << shardVsContainerExponent
|
|
|
|
// Remove every existing container in the row.
|
|
for i := uint64(0); i < (1 << shardVsContainerExponent); i++ {
|
|
f.storage.Containers.Remove(headContainerKey + i)
|
|
}
|
|
|
|
// From the given row, get the rowSegment for this shard.
|
|
seg := row.segment(f.shard)
|
|
if seg == nil {
|
|
return changed, nil
|
|
}
|
|
|
|
// Put each container from rowSegment to fragment storage.
|
|
citer, _ := seg.data.Containers.Iterator(f.shard << shardVsContainerExponent)
|
|
for citer.Next() {
|
|
k, c := citer.Value()
|
|
f.storage.Containers.Put(headContainerKey+(k%(1<<shardVsContainerExponent)), c)
|
|
}
|
|
|
|
// Update the row in cache.
|
|
n := f.storage.CountRange(rowID*ShardWidth, (rowID+1)*ShardWidth)
|
|
f.cache.BulkAdd(rowID, n)
|
|
|
|
// Snapshot storage.
|
|
if err := f.snapshot(); err != nil {
|
|
return false, errors.Wrap(err, "snapshotting")
|
|
}
|
|
|
|
f.stats.Count("setRow", 1, 1.0)
|
|
|
|
return changed, nil
|
|
}
|
|
|
|
// ClearRow clears a row for a given rowID within the fragment.
|
|
// This updates both the on-disk storage and the in-cache bitmap.
|
|
func (f *fragment) clearRow(rowID uint64) (bool, error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
mustClose, err := f.reopen()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "reopening")
|
|
}
|
|
if mustClose {
|
|
defer f.safeClose()
|
|
}
|
|
return f.unprotectedClearRow(rowID)
|
|
}
|
|
|
|
func (f *fragment) unprotectedClearRow(rowID uint64) (changed bool, err error) {
|
|
changed = false
|
|
|
|
// First container of the row in storage.
|
|
headContainerKey := rowID << shardVsContainerExponent
|
|
|
|
// Remove every container in the row.
|
|
for i := uint64(0); i < (1 << shardVsContainerExponent); i++ {
|
|
k := headContainerKey + i
|
|
// Technically we could bypass the Get() call and only
|
|
// call Remove(), but the Get() gives us the ability
|
|
// to return true if any existing data was removed.
|
|
if cont := f.storage.Containers.Get(k); cont != nil {
|
|
f.storage.Containers.Remove(k)
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
// Clear the row in cache.
|
|
f.cache.Add(rowID, 0)
|
|
|
|
// Snapshot storage.
|
|
if err := f.snapshot(); err != nil {
|
|
return false, errors.Wrap(err, "snapshotting")
|
|
}
|
|
|
|
f.stats.Count("clearRow", 1, 1.0)
|
|
|
|
return changed, nil
|
|
}
|
|
|
|
func (f *fragment) bit(rowID, columnID uint64) (bool, error) {
|
|
pos, err := f.pos(rowID, columnID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return f.storage.Contains(pos), nil
|
|
}
|
|
|
|
// value uses a column of bits to read a multi-bit value.
|
|
func (f *fragment) value(columnID uint64, bitDepth uint) (value int64, exists bool, err error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
// If existence bit is unset then ignore remaining bits.
|
|
if v, err := f.bit(bsiExistsBit, columnID); err != nil {
|
|
return 0, false, errors.Wrap(err, "getting existence bit")
|
|
} else if !v {
|
|
return 0, false, nil
|
|
}
|
|
|
|
// Compute other bits into a value.
|
|
for i := uint(0); i < bitDepth; i++ {
|
|
if v, err := f.bit(uint64(bsiOffsetBit+i), columnID); err != nil {
|
|
return 0, false, errors.Wrapf(err, "getting value bit %d", i)
|
|
} else if v {
|
|
value |= (1 << i)
|
|
}
|
|
}
|
|
|
|
// Negate if sign bit set.
|
|
if v, err := f.bit(bsiSignBit, columnID); err != nil {
|
|
return 0, false, errors.Wrap(err, "getting sign bit")
|
|
} else if v {
|
|
value = -value
|
|
}
|
|
|
|
return value, true, nil
|
|
}
|
|
|
|
// clearValue uses a column of bits to clear a multi-bit value.
|
|
func (f *fragment) clearValue(columnID uint64, bitDepth uint, value int64) (changed bool, err error) {
|
|
return f.setValueBase(columnID, bitDepth, value, true)
|
|
}
|
|
|
|
// setValue uses a column of bits to set a multi-bit value.
|
|
func (f *fragment) setValue(columnID uint64, bitDepth uint, value int64) (changed bool, err error) {
|
|
return f.setValueBase(columnID, bitDepth, value, false)
|
|
}
|
|
|
|
func (f *fragment) positionsForValue(columnID uint64, bitDepth uint, value int64, clear bool, toSet, toClear []uint64) ([]uint64, []uint64, error) {
|
|
// Convert value to an unsigned representation.
|
|
uvalue := uint64(value)
|
|
if value < 0 {
|
|
uvalue = uint64(-value)
|
|
}
|
|
|
|
// Mark value as set.
|
|
if bit, err := f.pos(bsiExistsBit, columnID); err != nil {
|
|
return toSet, toClear, errors.Wrap(err, "getting not-null pos")
|
|
} else if clear {
|
|
toClear = append(toClear, bit)
|
|
} else {
|
|
toSet = append(toSet, bit)
|
|
}
|
|
|
|
// Mark sign.
|
|
if bit, err := f.pos(bsiSignBit, columnID); err != nil {
|
|
return toSet, toClear, errors.Wrap(err, "getting sign pos")
|
|
} else if value >= 0 || clear {
|
|
toClear = append(toClear, bit)
|
|
} else {
|
|
toSet = append(toSet, bit)
|
|
}
|
|
|
|
for i := uint(0); i < bitDepth; i++ {
|
|
bit, err := f.pos(uint64(bsiOffsetBit+i), columnID)
|
|
if err != nil {
|
|
return toSet, toClear, errors.Wrap(err, "getting pos")
|
|
}
|
|
if uvalue&(1<<i) != 0 {
|
|
toSet = append(toSet, bit)
|
|
} else {
|
|
toClear = append(toClear, bit)
|
|
}
|
|
}
|
|
|
|
return toSet, toClear, nil
|
|
}
|
|
|
|
// TODO get rid of this and use positionsForValue to generate a single write op, and set that with importPositions.
|
|
func (f *fragment) setValueBase(columnID uint64, bitDepth uint, value int64, clear bool) (changed bool, err error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
mustClose, err := f.reopen()
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "reopening")
|
|
}
|
|
if mustClose {
|
|
defer f.safeClose()
|
|
}
|
|
|
|
// Convert value to an unsigned representation.
|
|
uvalue := uint64(value)
|
|
if value < 0 {
|
|
uvalue = uint64(-value)
|
|
}
|
|
|
|
for i := uint(0); i < bitDepth; i++ {
|
|
if uvalue&(1<<i) != 0 {
|
|
if c, err := f.unprotectedSetBit(uint64(bsiOffsetBit+i), columnID); err != nil {
|
|
return changed, err
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
} else {
|
|
if c, err := f.unprotectedClearBit(uint64(bsiOffsetBit+i), columnID); err != nil {
|
|
return changed, err
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mark value as set (or cleared).
|
|
if clear {
|
|
if c, err := f.unprotectedClearBit(uint64(bsiExistsBit), columnID); err != nil {
|
|
return changed, errors.Wrap(err, "clearing not-null")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
} else {
|
|
if c, err := f.unprotectedSetBit(uint64(bsiExistsBit), columnID); err != nil {
|
|
return changed, errors.Wrap(err, "marking not-null")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
// Mark sign bit (or clear).
|
|
if value >= 0 || clear {
|
|
if c, err := f.unprotectedClearBit(uint64(bsiSignBit), columnID); err != nil {
|
|
return changed, errors.Wrap(err, "clearing sign")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
} else {
|
|
if c, err := f.unprotectedSetBit(uint64(bsiSignBit), columnID); err != nil {
|
|
return changed, errors.Wrap(err, "marking sign")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
return changed, nil
|
|
}
|
|
|
|
// importSetValue is a more efficient SetValue just for imports.
|
|
func (f *fragment) importSetValue(columnID uint64, bitDepth uint, value int64, clear bool) (changed bool, err error) { // nolint: unparam
|
|
// Convert value to an unsigned representation.
|
|
uvalue := uint64(value)
|
|
if value < 0 {
|
|
uvalue = uint64(-value)
|
|
}
|
|
|
|
for i := uint(0); i < bitDepth; i++ {
|
|
bit, err := f.pos(uint64(bsiOffsetBit+i), columnID)
|
|
if err != nil {
|
|
return changed, errors.Wrap(err, "getting pos")
|
|
}
|
|
|
|
if uvalue&(1<<i) != 0 {
|
|
if c, err := f.storage.Add(bit); err != nil {
|
|
return changed, errors.Wrap(err, "adding")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
} else {
|
|
if c, err := f.storage.Remove(bit); err != nil {
|
|
return changed, errors.Wrap(err, "removing")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mark value as set.
|
|
if p, err := f.pos(uint64(bsiExistsBit), columnID); err != nil {
|
|
return changed, errors.Wrap(err, "getting not-null pos")
|
|
} else if clear {
|
|
if c, err := f.storage.Remove(p); err != nil {
|
|
return changed, errors.Wrap(err, "removing not-null from storage")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
} else {
|
|
if c, err := f.storage.Add(p); err != nil {
|
|
return changed, errors.Wrap(err, "adding not-null to storage")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
// Mark sign bit.
|
|
if p, err := f.pos(uint64(bsiSignBit), columnID); err != nil {
|
|
return changed, errors.Wrap(err, "getting sign pos")
|
|
} else if value >= 0 || clear {
|
|
if c, err := f.storage.Remove(p); err != nil {
|
|
return changed, errors.Wrap(err, "removing sign from storage")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
} else {
|
|
if c, err := f.storage.Add(p); err != nil {
|
|
return changed, errors.Wrap(err, "adding sign to storage")
|
|
} else if c {
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
return changed, nil
|
|
}
|
|
|
|
// sum returns the sum of a given bsiGroup as well as the number of columns involved.
|
|
// A bitmap can be passed in to optionally filter the computed columns.
|
|
func (f *fragment) sum(filter *Row, bitDepth uint) (sum int64, count uint64, err error) {
|
|
// Compute count based on the existence row.
|
|
consider := f.row(bsiExistsBit)
|
|
if filter != nil {
|
|
consider = consider.Intersect(filter)
|
|
}
|
|
count = consider.Count()
|
|
|
|
// Determine positive & negative sets.
|
|
nrow := f.row(bsiSignBit)
|
|
prow := consider.Difference(nrow)
|
|
|
|
// Compute the sum based on the bit count of each row multiplied by the
|
|
// place value of each row. For example, 10 bits in the 1's place plus
|
|
// 4 bits in the 2's place plus 3 bits in the 4's place equals a total
|
|
// sum of 30:
|
|
//
|
|
// 10*(2^0) + 4*(2^1) + 3*(2^2) = 30
|
|
//
|
|
// Execute once for positive numbers and once for negative. Subtract the
|
|
// negative sum from the positive sum.
|
|
for i := uint(0); i < bitDepth; i++ {
|
|
row := f.row(uint64(bsiOffsetBit + i))
|
|
|
|
psum := int64((1 << i) * row.intersectionCount(prow))
|
|
nsum := int64((1 << i) * row.intersectionCount(nrow))
|
|
|
|
// Squash to reduce the possibility of overflow.
|
|
sum += psum - nsum
|
|
}
|
|
|
|
return sum, count, nil
|
|
}
|
|
|
|
// min returns the min of a given bsiGroup as well as the number of columns involved.
|
|
// A bitmap can be passed in to optionally filter the computed columns.
|
|
func (f *fragment) min(filter *Row, bitDepth uint) (min int64, count uint64, err error) {
|
|
consider := f.row(bsiExistsBit)
|
|
if filter != nil {
|
|
consider = consider.Intersect(filter)
|
|
}
|
|
|
|
// If there are no columns to consider, return early.
|
|
if consider.Count() == 0 {
|
|
return 0, 0, nil
|
|
}
|
|
|
|
// If we have negative values, we should find the highest unsigned value
|
|
// from that set, then negate it, and return it. For example, if values
|
|
// (-1, -2) exist, they are stored unsigned (1,2) with a negative sign bit
|
|
// set. We take the highest of that set (2) and negate it and return it.
|
|
if row := f.row(bsiSignBit).Intersect(consider); row.Any() {
|
|
min, count := f.maxUnsigned(row, bitDepth)
|
|
return -min, count, nil
|
|
}
|
|
|
|
// Otherwise find lowest positive number.
|
|
min, count = f.minUnsigned(consider, bitDepth)
|
|
return min, count, nil
|
|
}
|
|
|
|
// minUnsigned the lowest value without considering the sign bit. Filter is required.
|
|
func (f *fragment) minUnsigned(filter *Row, bitDepth uint) (min int64, count uint64) {
|
|
for i := int(bitDepth - 1); i >= 0; i-- {
|
|
row := filter.Difference(f.row(uint64(bsiOffsetBit + i)))
|
|
count = row.Count()
|
|
if count > 0 {
|
|
filter = row
|
|
} else {
|
|
min += (1 << uint(i))
|
|
if i == 0 {
|
|
count = filter.Count()
|
|
}
|
|
}
|
|
}
|
|
return min, count
|
|
}
|
|
|
|
// max returns the max of a given bsiGroup as well as the number of columns involved.
|
|
// A bitmap can be passed in to optionally filter the computed columns.
|
|
func (f *fragment) max(filter *Row, bitDepth uint) (max int64, count uint64, err error) {
|
|
consider := f.row(bsiExistsBit)
|
|
if filter != nil {
|
|
consider = consider.Intersect(filter)
|
|
}
|
|
|
|
// If there are no columns to consider, return early.
|
|
if !consider.Any() {
|
|
return 0, 0, nil
|
|
}
|
|
|
|
// Find lowest negative number w/o sign and negate, if no positives are available.
|
|
pos := consider.Difference(f.row(bsiSignBit))
|
|
if !pos.Any() {
|
|
max, count = f.minUnsigned(consider, bitDepth)
|
|
return -max, count, nil
|
|
}
|
|
|
|
// Otherwise find highest positive number.
|
|
max, count = f.maxUnsigned(pos, bitDepth)
|
|
return max, count, nil
|
|
}
|
|
|
|
// maxUnsigned the highest value without considering the sign bit. Filter is required.
|
|
func (f *fragment) maxUnsigned(filter *Row, bitDepth uint) (max int64, count uint64) {
|
|
for i := int(bitDepth - 1); i >= 0; i-- {
|
|
row := f.row(uint64(bsiOffsetBit + i)).Intersect(filter)
|
|
count = row.Count()
|
|
if count > 0 {
|
|
max += (1 << uint(i))
|
|
filter = row
|
|
} else if i == 0 {
|
|
count = filter.Count()
|
|
}
|
|
}
|
|
return max, count
|
|
}
|
|
|
|
// rangeOp returns bitmaps with a bsiGroup value encoding matching the predicate.
|
|
func (f *fragment) rangeOp(op pql.Token, bitDepth uint, predicate int64) (*Row, error) {
|
|
switch op {
|
|
case pql.EQ:
|
|
return f.rangeEQ(bitDepth, predicate)
|
|
case pql.NEQ:
|
|
return f.rangeNEQ(bitDepth, predicate)
|
|
case pql.LT, pql.LTE:
|
|
return f.rangeLT(bitDepth, predicate, op == pql.LTE)
|
|
case pql.GT, pql.GTE:
|
|
return f.rangeGT(bitDepth, predicate, op == pql.GTE)
|
|
default:
|
|
return nil, ErrInvalidRangeOperation
|
|
}
|
|
}
|
|
|
|
func (f *fragment) rangeEQ(bitDepth uint, predicate int64) (*Row, error) {
|
|
// Start with set of columns with values set.
|
|
b := f.row(bsiExistsBit)
|
|
|
|
// Filter to only positive/negative numbers.
|
|
upredicate := uint64(predicate)
|
|
if predicate < 0 {
|
|
upredicate = uint64(-predicate)
|
|
b = b.Intersect(f.row(bsiSignBit)) // only negatives
|
|
} else {
|
|
b = b.Difference(f.row(bsiSignBit)) // only positives
|
|
}
|
|
|
|
// Filter any bits that don't match the current bit value.
|
|
for i := int(bitDepth - 1); i >= 0; i-- {
|
|
row := f.row(uint64(bsiOffsetBit + i))
|
|
bit := (upredicate >> uint(i)) & 1
|
|
|
|
if bit == 1 {
|
|
b = b.Intersect(row)
|
|
} else {
|
|
b = b.Difference(row)
|
|
}
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (f *fragment) rangeNEQ(bitDepth uint, predicate int64) (*Row, error) {
|
|
// Start with set of columns with values set.
|
|
b := f.row(bsiExistsBit)
|
|
|
|
// Get the equal bitmap.
|
|
eq, err := f.rangeEQ(bitDepth, predicate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Not-null minus the equal bitmap.
|
|
b = b.Difference(eq)
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (f *fragment) rangeLT(bitDepth uint, predicate int64, allowEquality bool) (*Row, error) {
|
|
// Start with set of columns with values set.
|
|
b := f.row(bsiExistsBit)
|
|
|
|
// Create predicate without sign bit.
|
|
upredicate := uint64(predicate)
|
|
if predicate < 0 {
|
|
upredicate = uint64(-predicate)
|
|
}
|
|
|
|
// If predicate is positive, return all positives less than predicate and all negatives.
|
|
if (predicate >= 0 && allowEquality) || (predicate >= -1 && !allowEquality) {
|
|
pos, err := f.rangeLTUnsigned(b.Difference(f.row(bsiSignBit)), bitDepth, upredicate, allowEquality)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
neg := f.row(bsiSignBit)
|
|
return neg.Union(pos), nil
|
|
}
|
|
|
|
// Otherwise if predicate is negative, return all negatives greater than upredicate.
|
|
return f.rangeGTUnsigned(b.Intersect(f.row(bsiSignBit)), bitDepth, upredicate, allowEquality)
|
|
}
|
|
|
|
// rangeLTUnsigned returns all bits LT/LTE the predicate without considering the sign bit.
|
|
func (f *fragment) rangeLTUnsigned(filter *Row, bitDepth uint, predicate uint64, allowEquality bool) (*Row, error) {
|
|
keep := NewRow()
|
|
|
|
// Filter any bits that don't match the current bit value.
|
|
leadingZeros := true
|
|
for i := int(bitDepth - 1); i >= 0; i-- {
|
|
row := f.row(uint64(bsiOffsetBit + i))
|
|
bit := (predicate >> uint(i)) & 1
|
|
|
|
// Remove any columns with higher bits set.
|
|
if leadingZeros {
|
|
if bit == 0 {
|
|
filter = filter.Difference(row)
|
|
continue
|
|
} else {
|
|
leadingZeros = false
|
|
}
|
|
}
|
|
|
|
// Handle last bit differently.
|
|
// If bit is zero then return only already kept columns.
|
|
// If bit is one then remove any one columns.
|
|
if i == 0 && !allowEquality {
|
|
if bit == 0 {
|
|
return keep, nil
|
|
}
|
|
return filter.Difference(row.Difference(keep)), nil
|
|
}
|
|
|
|
// If bit is zero then remove all set columns not in excluded bitmap.
|
|
if bit == 0 {
|
|
filter = filter.Difference(row.Difference(keep))
|
|
continue
|
|
}
|
|
|
|
// If bit is set then add columns for set bits to exclude.
|
|
// Don't bother to compute this on the final iteration.
|
|
if i > 0 {
|
|
keep = keep.Union(filter.Difference(row))
|
|
}
|
|
}
|
|
|
|
return filter, nil
|
|
}
|
|
|
|
func (f *fragment) rangeGT(bitDepth uint, predicate int64, allowEquality bool) (*Row, error) {
|
|
b := f.row(bsiExistsBit)
|
|
|
|
// Create predicate without sign bit.
|
|
upredicate := uint64(predicate)
|
|
if predicate < 0 {
|
|
upredicate = uint64(-predicate)
|
|
}
|
|
|
|
// If predicate is positive, return all positives greater than predicate.
|
|
if (predicate >= 0 && allowEquality) || (predicate >= -1 && !allowEquality) {
|
|
return f.rangeGTUnsigned(b.Difference(f.row(bsiSignBit)), bitDepth, upredicate, allowEquality)
|
|
}
|
|
|
|
// If predicate is negative, return all negatives less than than upredicate and all positives.
|
|
neg, err := f.rangeLTUnsigned(b.Intersect(f.row(bsiSignBit)), bitDepth, upredicate, allowEquality)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pos := b.Difference(f.row(bsiSignBit))
|
|
return pos.Union(neg), nil
|
|
}
|
|
|
|
func (f *fragment) rangeGTUnsigned(filter *Row, bitDepth uint, predicate uint64, allowEquality bool) (*Row, error) {
|
|
keep := NewRow()
|
|
|
|
// Filter any bits that don't match the current bit value.
|
|
for i := int(bitDepth - 1); i >= 0; i-- {
|
|
row := f.row(uint64(bsiOffsetBit + i))
|
|
bit := (predicate >> uint(i)) & 1
|
|
|
|
// Handle last bit differently.
|
|
// If bit is one then return only already kept columns.
|
|
// If bit is zero then remove any unset columns.
|
|
if i == 0 && !allowEquality {
|
|
if bit == 1 {
|
|
return keep, nil
|
|
}
|
|
return filter.Difference(filter.Difference(row).Difference(keep)), nil
|
|
}
|
|
|
|
// If bit is set then remove all unset columns not already kept.
|
|
if bit == 1 {
|
|
filter = filter.Difference(filter.Difference(row).Difference(keep))
|
|
continue
|
|
}
|
|
|
|
// If bit is unset then add columns with set bit to keep.
|
|
// Don't bother to compute this on the final iteration.
|
|
if i > 0 {
|
|
keep = keep.Union(filter.Intersect(row))
|
|
}
|
|
}
|
|
|
|
return filter, nil
|
|
}
|
|
|
|
// notNull returns the exists row.
|
|
func (f *fragment) notNull() (*Row, error) {
|
|
return f.row(bsiExistsBit), nil
|
|
}
|
|
|
|
// rangeBetween returns bitmaps with a bsiGroup value encoding matching any value between predicateMin and predicateMax.
|
|
func (f *fragment) rangeBetween(bitDepth uint, predicateMin, predicateMax int64) (*Row, error) {
|
|
b := f.row(bsiExistsBit)
|
|
|
|
// Convert predicates to unsigned values.
|
|
upredicateMin, upredicateMax := uint64(predicateMin), uint64(predicateMax)
|
|
if predicateMin < 0 {
|
|
upredicateMin = uint64(-predicateMin)
|
|
}
|
|
if predicateMax < 0 {
|
|
upredicateMax = uint64(-predicateMax)
|
|
}
|
|
|
|
// Handle positive-only values.
|
|
if predicateMin >= 0 {
|
|
return f.rangeBetweenUnsigned(b.Difference(f.row(bsiSignBit)), bitDepth, upredicateMin, upredicateMax)
|
|
}
|
|
|
|
// Handle negative-only values. Swap unsigned min/max predicates.
|
|
if predicateMax < 0 {
|
|
return f.rangeBetweenUnsigned(b.Intersect(f.row(bsiSignBit)), bitDepth, upredicateMax, upredicateMin)
|
|
}
|
|
|
|
// If predicate crosses positive/negative boundary then handle separately and union.
|
|
pos, err := f.rangeLTUnsigned(b.Difference(f.row(bsiSignBit)), bitDepth, upredicateMax, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
neg, err := f.rangeLTUnsigned(b.Intersect(f.row(bsiSignBit)), bitDepth, upredicateMin, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pos.Union(neg), nil
|
|
}
|
|
|
|
// rangeBetweenUnsigned returns BSI columns for a range of values. Disregards the sign bit.
|
|
func (f *fragment) rangeBetweenUnsigned(filter *Row, bitDepth uint, predicateMin, predicateMax uint64) (*Row, error) {
|
|
keep1 := NewRow() // GTE
|
|
keep2 := NewRow() // LTE
|
|
|
|
// Filter any bits that don't match the current bit value.
|
|
for i := int(bitDepth - 1); i >= 0; i-- {
|
|
row := f.row(uint64(bsiOffsetBit + i))
|
|
bit1 := (predicateMin >> uint(i)) & 1
|
|
bit2 := (predicateMax >> uint(i)) & 1
|
|
|
|
// GTE predicateMin
|
|
// If bit is set then remove all unset columns not already kept.
|
|
if bit1 == 1 {
|
|
filter = filter.Difference(filter.Difference(row).Difference(keep1))
|
|
} else {
|
|
// If bit is unset then add columns with set bit to keep.
|
|
// Don't bother to compute this on the final iteration.
|
|
if i > 0 {
|
|
keep1 = keep1.Union(filter.Intersect(row))
|
|
}
|
|
}
|
|
|
|
// LTE predicateMax
|
|
// If bit is zero then remove all set bits not in excluded bitmap.
|
|
if bit2 == 0 {
|
|
filter = filter.Difference(row.Difference(keep2))
|
|
} else {
|
|
// If bit is set then add columns for set bits to exclude.
|
|
// Don't bother to compute this on the final iteration.
|
|
if i > 0 {
|
|
keep2 = keep2.Union(filter.Difference(row))
|
|
}
|
|
}
|
|
}
|
|
|
|
return filter, nil
|
|
}
|
|
|
|
// pos translates the row ID and column ID into a position in the storage bitmap.
|
|
func (f *fragment) pos(rowID, columnID uint64) (uint64, error) {
|
|
// Return an error if the column ID is out of the range of the fragment's shard.
|
|
minColumnID := f.shard * ShardWidth
|
|
if columnID < minColumnID || columnID >= minColumnID+ShardWidth {
|
|
return 0, errors.Errorf("column:%d out of bounds", columnID)
|
|
}
|
|
return pos(rowID, columnID), nil
|
|
}
|
|
|
|
// forEachBit executes fn for every bit set in the fragment.
|
|
// Errors returned from fn are passed through.
|
|
func (f *fragment) forEachBit(fn func(rowID, columnID uint64) error) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
var err error
|
|
f.storage.ForEach(func(i uint64) {
|
|
// Skip if an error has already occurred.
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Invoke caller's function.
|
|
err = fn(i/ShardWidth, (f.shard*ShardWidth)+(i%ShardWidth))
|
|
})
|
|
return err
|
|
}
|
|
|
|
// top returns the top rows from the fragment.
|
|
// If opt.Src is specified then only rows which intersect src are returned.
|
|
// If opt.FilterValues exist then the row attribute specified by field is matched.
|
|
func (f *fragment) top(opt topOptions) ([]Pair, error) {
|
|
// Retrieve pairs. If no row ids specified then return from cache.
|
|
pairs := f.topBitmapPairs(opt.RowIDs)
|
|
|
|
// If row ids are provided, we don't want to truncate the result set
|
|
if len(opt.RowIDs) > 0 {
|
|
opt.N = 0
|
|
}
|
|
|
|
// Create a fast lookup of filter values.
|
|
var filters map[interface{}]struct{}
|
|
if opt.FilterName != "" && len(opt.FilterValues) > 0 {
|
|
filters = make(map[interface{}]struct{})
|
|
for _, v := range opt.FilterValues {
|
|
filters[v] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// Use `tanimotoThreshold > 0` to indicate whether or not we are considering Tanimoto.
|
|
var tanimotoThreshold uint64
|
|
var minTanimoto, maxTanimoto float64
|
|
var srcCount uint64
|
|
if opt.TanimotoThreshold > 0 && opt.Src != nil {
|
|
tanimotoThreshold = opt.TanimotoThreshold
|
|
srcCount = opt.Src.Count()
|
|
minTanimoto = float64(srcCount*tanimotoThreshold) / 100
|
|
maxTanimoto = float64(srcCount*100) / float64(tanimotoThreshold)
|
|
}
|
|
|
|
// Iterate over rankings and add to results until we have enough.
|
|
results := &pairHeap{}
|
|
for _, pair := range pairs {
|
|
rowID, cnt := pair.ID, pair.Count
|
|
|
|
// Ignore empty rows.
|
|
if cnt == 0 {
|
|
continue
|
|
}
|
|
|
|
// Check against either Tanimoto threshold or minimum threshold.
|
|
if tanimotoThreshold > 0 {
|
|
// Ignore counts outside of the Tanimoto min/max values.
|
|
if float64(cnt) <= minTanimoto || float64(cnt) >= maxTanimoto {
|
|
continue
|
|
}
|
|
} else {
|
|
// Ignore counts less than MinThreshold.
|
|
if cnt < opt.MinThreshold {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Apply filter, if set.
|
|
if filters != nil {
|
|
attr, err := f.RowAttrStore.Attrs(rowID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getting attrs")
|
|
} else if attr == nil {
|
|
continue
|
|
} else if attrValue := attr[opt.FilterName]; attrValue == nil {
|
|
continue
|
|
} else if _, ok := filters[attrValue]; !ok {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// The initial n pairs should simply be added to the results.
|
|
if opt.N == 0 || results.Len() < opt.N {
|
|
// Calculate count and append.
|
|
count := cnt
|
|
if opt.Src != nil {
|
|
count = opt.Src.intersectionCount(f.row(rowID))
|
|
}
|
|
if count == 0 {
|
|
continue
|
|
}
|
|
|
|
// Check against either Tanimoto threshold or minimum threshold.
|
|
if tanimotoThreshold > 0 {
|
|
tanimoto := math.Ceil(float64(count*100) / float64(cnt+srcCount-count))
|
|
if tanimoto <= float64(tanimotoThreshold) {
|
|
continue
|
|
}
|
|
} else {
|
|
if count < opt.MinThreshold {
|
|
continue
|
|
}
|
|
}
|
|
|
|
heap.Push(results, Pair{ID: rowID, Count: count})
|
|
|
|
// If we reach the requested number of pairs and we are not computing
|
|
// intersections then simply exit. If we are intersecting then sort
|
|
// and then only keep pairs that are higher than the lowest count.
|
|
if opt.N > 0 && results.Len() == opt.N {
|
|
if opt.Src == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Retrieve the lowest count we have.
|
|
// If it's too low then don't try finding anymore pairs.
|
|
threshold := results.Pairs[0].Count
|
|
|
|
// If the row doesn't have enough columns set before the intersection
|
|
// then we can assume that any remaining rows also have a count too low.
|
|
if threshold < opt.MinThreshold || cnt < threshold {
|
|
break
|
|
}
|
|
|
|
// Calculate the intersecting column count and skip if it's below our
|
|
// last row in our current result set.
|
|
count := opt.Src.intersectionCount(f.row(rowID))
|
|
if count < threshold {
|
|
continue
|
|
}
|
|
|
|
heap.Push(results, Pair{ID: rowID, Count: count})
|
|
}
|
|
|
|
//Pop first opt.N elements out of heap
|
|
r := make(Pairs, results.Len())
|
|
x := results.Len()
|
|
i := 1
|
|
for results.Len() > 0 {
|
|
r[x-i] = heap.Pop(results).(Pair)
|
|
i++
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func (f *fragment) topBitmapPairs(rowIDs []uint64) []bitmapPair {
|
|
// Don't retrieve from storage if CacheTypeNone.
|
|
if f.CacheType == CacheTypeNone {
|
|
return f.cache.Top()
|
|
}
|
|
// If no specific rows are requested, retrieve top rows.
|
|
if len(rowIDs) == 0 {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.cache.Invalidate()
|
|
return f.cache.Top()
|
|
}
|
|
|
|
// Otherwise retrieve specific rows.
|
|
pairs := make([]bitmapPair, 0, len(rowIDs))
|
|
for _, rowID := range rowIDs {
|
|
// Look up cache first, if available.
|
|
if n := f.cache.Get(rowID); n > 0 {
|
|
pairs = append(pairs, bitmapPair{
|
|
ID: rowID,
|
|
Count: n,
|
|
})
|
|
continue
|
|
}
|
|
|
|
row := f.row(rowID)
|
|
if row.Count() > 0 {
|
|
// Otherwise load from storage.
|
|
pairs = append(pairs, bitmapPair{
|
|
ID: rowID,
|
|
Count: row.Count(),
|
|
})
|
|
}
|
|
}
|
|
sort.Sort(bitmapPairs(pairs))
|
|
return pairs
|
|
}
|
|
|
|
// topOptions represents options passed into the Top() function.
|
|
type topOptions struct {
|
|
// Number of rows to return.
|
|
N int
|
|
|
|
// Bitmap to intersect with.
|
|
Src *Row
|
|
|
|
// Specific rows to filter against.
|
|
RowIDs []uint64
|
|
MinThreshold uint64
|
|
|
|
// Filter field name & values.
|
|
FilterName string
|
|
FilterValues []interface{}
|
|
TanimotoThreshold uint64
|
|
}
|
|
|
|
// Checksum returns a checksum for the entire fragment.
|
|
// If two fragments have the same checksum then they have the same data.
|
|
func (f *fragment) Checksum() []byte {
|
|
h := xxhash.New()
|
|
for _, block := range f.Blocks() {
|
|
_, _ = h.Write(block.Checksum)
|
|
}
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
// InvalidateChecksums clears all cached block checksums.
|
|
func (f *fragment) InvalidateChecksums() {
|
|
f.mu.Lock()
|
|
f.checksums = make(map[int][]byte)
|
|
f.mu.Unlock()
|
|
}
|
|
|
|
// Blocks returns info for all blocks containing data.
|
|
func (f *fragment) Blocks() []FragmentBlock {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
var a []FragmentBlock
|
|
|
|
// Initialize the iterator.
|
|
itr := f.storage.Iterator()
|
|
itr.Seek(0)
|
|
|
|
// Initialize block hasher.
|
|
h := newBlockHasher()
|
|
|
|
// Iterate over each value in the fragment.
|
|
v, eof := itr.Next()
|
|
if eof {
|
|
return nil
|
|
}
|
|
blockID := int(v / (HashBlockSize * ShardWidth))
|
|
for {
|
|
// Check for multiple block checksums in a row.
|
|
if n := f.readContiguousChecksums(&a, blockID); n > 0 {
|
|
itr.Seek(uint64(blockID+n) * HashBlockSize * ShardWidth)
|
|
v, eof = itr.Next()
|
|
if eof {
|
|
break
|
|
}
|
|
blockID = int(v / (HashBlockSize * ShardWidth))
|
|
continue
|
|
}
|
|
|
|
// Reset hasher.
|
|
h.blockID = blockID
|
|
h.Reset()
|
|
|
|
// Read all values for the block.
|
|
for ; ; v, eof = itr.Next() {
|
|
// Once we hit the next block, save the value for the next iteration.
|
|
blockID = int(v / (HashBlockSize * ShardWidth))
|
|
if blockID != h.blockID || eof {
|
|
break
|
|
}
|
|
|
|
h.WriteValue(v)
|
|
}
|
|
|
|
// Cache checksum.
|
|
chksum := h.Sum()
|
|
f.checksums[h.blockID] = chksum
|
|
|
|
// Append block.
|
|
a = append(a, FragmentBlock{
|
|
ID: h.blockID,
|
|
Checksum: chksum,
|
|
})
|
|
|
|
// Exit if we're at the end.
|
|
if eof {
|
|
break
|
|
}
|
|
}
|
|
|
|
return a
|
|
}
|
|
|
|
// readContiguousChecksums appends multiple checksums in a row and returns the count added.
|
|
func (f *fragment) readContiguousChecksums(a *[]FragmentBlock, blockID int) (n int) {
|
|
for i := 0; ; i++ {
|
|
chksum := f.checksums[blockID+i]
|
|
if chksum == nil {
|
|
return i
|
|
}
|
|
|
|
*a = append(*a, FragmentBlock{
|
|
ID: blockID + i,
|
|
Checksum: chksum,
|
|
})
|
|
}
|
|
}
|
|
|
|
// blockData returns bits in a block as row & column ID pairs.
|
|
func (f *fragment) blockData(id int) (rowIDs, columnIDs []uint64) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.storage.ForEachRange(uint64(id)*HashBlockSize*ShardWidth, (uint64(id)+1)*HashBlockSize*ShardWidth, func(i uint64) {
|
|
rowIDs = append(rowIDs, i/ShardWidth)
|
|
columnIDs = append(columnIDs, i%ShardWidth)
|
|
})
|
|
return rowIDs, columnIDs
|
|
}
|
|
|
|
// mergeBlock compares the block's bits and computes a diff with another set of block bits.
|
|
// The state of a bit is determined by consensus from all blocks being considered.
|
|
//
|
|
// For example, if 3 blocks are compared and two have a set bit and one has a
|
|
// cleared bit then the bit is considered cleared. The function returns the
|
|
// diff per incoming block so that all can be in sync.
|
|
func (f *fragment) mergeBlock(id int, data []pairSet) (sets, clears []pairSet, err error) {
|
|
// Ensure that all pair sets are of equal length.
|
|
for i := range data {
|
|
if len(data[i].rowIDs) != len(data[i].columnIDs) {
|
|
return nil, nil, fmt.Errorf("pair set mismatch(idx=%d): %d != %d", i, len(data[i].rowIDs), len(data[i].columnIDs))
|
|
}
|
|
}
|
|
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
// Track sets and clears for all blocks (including local).
|
|
sets = make([]pairSet, len(data)+1)
|
|
clears = make([]pairSet, len(data)+1)
|
|
|
|
// Limit upper row/column pair.
|
|
maxRowID := uint64(id+1) * HashBlockSize
|
|
maxColumnID := uint64(ShardWidth)
|
|
|
|
// Create buffered iterator for local block.
|
|
itrs := make([]*bufIterator, 1, len(data)+1)
|
|
itrs[0] = newBufIterator(
|
|
newLimitIterator(
|
|
newRoaringIterator(f.storage.Iterator()), maxRowID, maxColumnID,
|
|
),
|
|
)
|
|
|
|
// Append buffered iterators for each incoming block.
|
|
for i := range data {
|
|
var itr iterator = newSliceIterator(data[i].rowIDs, data[i].columnIDs)
|
|
itr = newLimitIterator(itr, maxRowID, maxColumnID)
|
|
itrs = append(itrs, newBufIterator(itr))
|
|
}
|
|
|
|
// Seek to initial pair.
|
|
for _, itr := range itrs {
|
|
itr.Seek(uint64(id)*HashBlockSize, 0)
|
|
}
|
|
|
|
// Determine the number of blocks needed to meet consensus.
|
|
// If there is an even split then a set is used.
|
|
majorityN := (len(itrs) + 1) / 2
|
|
|
|
// Iterate over all values in all iterators to determine differences.
|
|
values := make([]bool, len(itrs))
|
|
for {
|
|
var min struct {
|
|
rowID uint64
|
|
columnID uint64
|
|
}
|
|
|
|
// Find the lowest pair.
|
|
var hasData bool
|
|
for _, itr := range itrs {
|
|
bid, pid, eof := itr.Peek()
|
|
if eof { // no more data
|
|
continue
|
|
} else if !hasData { // first pair
|
|
min.rowID, min.columnID, hasData = bid, pid, true
|
|
} else if bid < min.rowID || (bid == min.rowID && pid < min.columnID) { // lower pair
|
|
min.rowID, min.columnID = bid, pid
|
|
}
|
|
}
|
|
|
|
// If all iterators are EOF then exit.
|
|
if !hasData {
|
|
break
|
|
}
|
|
|
|
// Determine consensus of point.
|
|
var setN int
|
|
for i, itr := range itrs {
|
|
bid, pid, eof := itr.Next()
|
|
|
|
values[i] = !eof && bid == min.rowID && pid == min.columnID
|
|
if values[i] {
|
|
setN++ // set
|
|
} else {
|
|
itr.Unread() // clear
|
|
}
|
|
}
|
|
|
|
// Determine consensus value.
|
|
newValue := setN >= majorityN
|
|
|
|
// Add a diff for any node with a different value.
|
|
for i := range itrs {
|
|
// Value matches, ignore.
|
|
if values[i] == newValue {
|
|
continue
|
|
}
|
|
|
|
// Append to either the set or clear diff.
|
|
if newValue {
|
|
sets[i].rowIDs = append(sets[i].rowIDs, min.rowID)
|
|
sets[i].columnIDs = append(sets[i].columnIDs, min.columnID)
|
|
} else {
|
|
clears[i].rowIDs = append(sets[i].rowIDs, min.rowID)
|
|
clears[i].columnIDs = append(sets[i].columnIDs, min.columnID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set local bits.
|
|
for i := range sets[0].columnIDs {
|
|
if _, err := f.unprotectedSetBit(sets[0].rowIDs[i], (f.shard*ShardWidth)+sets[0].columnIDs[i]); err != nil {
|
|
return nil, nil, errors.Wrap(err, "setting")
|
|
}
|
|
}
|
|
|
|
// Clear local bits.
|
|
for i := range clears[0].columnIDs {
|
|
if _, err := f.unprotectedClearBit(clears[0].rowIDs[i], (f.shard*ShardWidth)+clears[0].columnIDs[i]); err != nil {
|
|
return nil, nil, errors.Wrap(err, "clearing")
|
|
}
|
|
}
|
|
|
|
return sets[1:], clears[1:], nil
|
|
}
|
|
|
|
// bulkImport bulk imports a set of bits and then snapshots the storage.
|
|
// The cache is updated to reflect the new data.
|
|
func (f *fragment) bulkImport(rowIDs, columnIDs []uint64, options *ImportOptions) error {
|
|
// Verify that there are an equal number of row ids and column ids.
|
|
if len(rowIDs) != len(columnIDs) {
|
|
return fmt.Errorf("mismatch of row/column len: %d != %d", len(rowIDs), len(columnIDs))
|
|
}
|
|
|
|
if f.mutexVector != nil && !options.Clear {
|
|
return f.bulkImportMutex(rowIDs, columnIDs)
|
|
}
|
|
return f.bulkImportStandard(rowIDs, columnIDs, options)
|
|
}
|
|
|
|
// bulkImportStandard performs a bulk import on a standard fragment. May mutate
|
|
// its rowIDs and columnIDs arguments.
|
|
func (f *fragment) bulkImportStandard(rowIDs, columnIDs []uint64, options *ImportOptions) (err error) {
|
|
// rowSet maintains the set of rowIDs present in this import. It allows the
|
|
// cache to be updated once per row, instead of once per bit. TODO: consider
|
|
// sorting by rowID/columnID first and avoiding the map allocation here. (we
|
|
// could reuse rowIDs to store the list of unique row IDs)
|
|
rowSet := make(map[uint64]struct{})
|
|
lastRowID := uint64(1 << 63)
|
|
|
|
// replace columnIDs with calculated positions to avoid allocation.
|
|
for i := 0; i < len(columnIDs); i++ {
|
|
rowID, columnID := rowIDs[i], columnIDs[i]
|
|
pos, err := f.pos(rowID, columnID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
columnIDs[i] = pos
|
|
|
|
// Add row to rowSet.
|
|
if rowID != lastRowID {
|
|
lastRowID = rowID
|
|
rowSet[rowID] = struct{}{}
|
|
}
|
|
}
|
|
positions := columnIDs
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
if options.Clear {
|
|
err = f.importPositions(nil, positions, rowSet)
|
|
} else {
|
|
err = f.importPositions(positions, nil, rowSet)
|
|
}
|
|
return errors.Wrap(err, "bulkImportStandard")
|
|
}
|
|
|
|
// importPositions takes slices of positions within the fragment to set and
|
|
// clear in storage. One must also pass in the set of unique rows which are
|
|
// affected by the set and clear operations. It is unprotected (f.mu must be
|
|
// locked when calling it). No position should appear in both set and clear.
|
|
//
|
|
// importPositions tries to intelligently decide whether or not to do a full
|
|
// snapshot of the fragment or just do in-memory updates while appending
|
|
// operations to the op log.
|
|
func (f *fragment) importPositions(set, clear []uint64, rowSet map[uint64]struct{}) error {
|
|
smallWrite := false
|
|
if len(set)+len(clear)+f.opN < f.MaxOpN {
|
|
smallWrite = true
|
|
mustClose, err := f.reopen()
|
|
if err != nil {
|
|
return errors.Wrap(err, "reopening")
|
|
}
|
|
if mustClose {
|
|
defer f.safeClose()
|
|
}
|
|
|
|
} else {
|
|
f.storage.OpWriter = nil
|
|
}
|
|
|
|
if len(set) > 0 {
|
|
f.stats.Count("ImportingN", int64(len(set)), 1)
|
|
changedN, err := f.storage.AddN(set...) // TODO benchmark Add/RemoveN behavior with sorted/unsorted positions
|
|
if err != nil {
|
|
return errors.Wrap(err, "adding positions")
|
|
}
|
|
f.stats.Count("ImportedN", int64(changedN), 1)
|
|
f.opN += changedN
|
|
}
|
|
|
|
if len(clear) > 0 {
|
|
f.stats.Count("ClearingN", int64(len(clear)), 1)
|
|
changedN, err := f.storage.RemoveN(clear...)
|
|
if err != nil {
|
|
return errors.Wrap(err, "clearing positions")
|
|
}
|
|
f.stats.Count("ClearedN", int64(changedN), 1)
|
|
f.opN += changedN
|
|
}
|
|
|
|
// Update cache counts for all affected rows.
|
|
for rowID := range rowSet {
|
|
// Invalidate block checksum.
|
|
delete(f.checksums, int(rowID/HashBlockSize))
|
|
|
|
n := f.storage.CountRange(rowID*ShardWidth, (rowID+1)*ShardWidth)
|
|
f.cache.BulkAdd(rowID, n)
|
|
|
|
if smallWrite {
|
|
f.rowCache.Add(rowID, nil)
|
|
}
|
|
}
|
|
|
|
f.cache.Recalculate()
|
|
|
|
if !smallWrite {
|
|
return f.snapshot()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// bulkImportMutex performs a bulk import on a fragment while ensuring
|
|
// mutex restrictions. Because the mutex requirements must be checked
|
|
// against storage, this method must acquire a write lock on the fragment
|
|
// during the entire process, and it handles every bit independently.
|
|
func (f *fragment) bulkImportMutex(rowIDs, columnIDs []uint64) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
rowSet := make(map[uint64]struct{})
|
|
// we have to maintain which columns are getting bits set as a map so that
|
|
// we don't end up setting multiple bits in the same column if a column is
|
|
// repeated within the import.
|
|
colSet := make(map[uint64]uint64)
|
|
|
|
// Since each imported bit will at most set one bit and clear one bit, we
|
|
// can reuse the rowIDs and columnIDs slices as the set and clear slice
|
|
// arguments to importPositions. The set positions we'll get from the
|
|
// colSet, but we maintain clearIdx as we loop through row and col ids so
|
|
// that we know how many bits we need to clear and how far through columnIDs
|
|
// we are.
|
|
clearIdx := 0
|
|
for i := range rowIDs {
|
|
rowID, columnID := rowIDs[i], columnIDs[i]
|
|
if existingRowID, found, err := f.mutexVector.Get(columnID); err != nil {
|
|
return errors.Wrap(err, "getting mutex vector data")
|
|
} else if found && existingRowID != rowID {
|
|
// Determine the position of the bit in the storage.
|
|
clearPos, err := f.pos(existingRowID, columnID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
columnIDs[clearIdx] = clearPos
|
|
clearIdx++
|
|
|
|
rowSet[existingRowID] = struct{}{}
|
|
} else if found && existingRowID == rowID {
|
|
continue
|
|
}
|
|
pos, err := f.pos(rowID, columnID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
colSet[columnID] = pos
|
|
rowSet[rowID] = struct{}{}
|
|
}
|
|
|
|
// re-use rowIDs by populating positions to set from colSet.
|
|
i := 0
|
|
for _, pos := range colSet {
|
|
rowIDs[i] = pos
|
|
i++
|
|
}
|
|
toSet := rowIDs[:i]
|
|
toClear := columnIDs[:clearIdx]
|
|
|
|
return errors.Wrap(f.importPositions(toSet, toClear, rowSet), "importing positions")
|
|
}
|
|
|
|
func (f *fragment) importValueSmallWrite(columnIDs []uint64, values []int64, bitDepth uint, clear bool) error {
|
|
// TODO figure out how to avoid re-allocating these each time. Probably
|
|
// possible to store them on the fragment with a capacity based on
|
|
// MaxOpN. For now, we know that the total number of bits to be
|
|
// set+cleared is len(values)*(bitDepth+1), so we make each slice
|
|
// slightly more than half of that to try to avoid reallocation.
|
|
toSet := make([]uint64, 0, len(columnIDs)*int(bitDepth+1)*(5/8))
|
|
toClear := make([]uint64, 0, len(columnIDs)*int(bitDepth+1)*(5/8))
|
|
colSet := make(map[uint64]struct{}, len(columnIDs))
|
|
|
|
if err := func() (err error) {
|
|
for i := len(columnIDs) - 1; i >= 0; i-- {
|
|
columnID, value := columnIDs[i], values[i]
|
|
if _, ok := colSet[columnID]; ok {
|
|
continue
|
|
}
|
|
|
|
colSet[columnID] = struct{}{}
|
|
toSet, toClear, err = f.positionsForValue(columnID, bitDepth, value, clear, toSet, toClear)
|
|
if err != nil {
|
|
return errors.Wrap(err, "getting positions for value")
|
|
}
|
|
}
|
|
return nil
|
|
}(); err != nil {
|
|
_ = f.closeStorage()
|
|
_ = f.openStorage()
|
|
return err
|
|
}
|
|
rowSet := make(map[uint64]struct{}, bitDepth+1)
|
|
for i := uint(0); i < bitDepth+1; i++ {
|
|
rowSet[uint64(i)] = struct{}{}
|
|
}
|
|
err := f.importPositions(toSet, toClear, rowSet)
|
|
return errors.Wrap(err, "importing positions")
|
|
}
|
|
|
|
// importValue bulk imports a set of range-encoded values.
|
|
func (f *fragment) importValue(columnIDs []uint64, values []int64, bitDepth uint, clear bool) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
// Verify that there are an equal number of column ids and values.
|
|
if len(columnIDs) != len(values) {
|
|
return fmt.Errorf("mismatch of column/value len: %d != %d", len(columnIDs), len(values))
|
|
}
|
|
|
|
if len(columnIDs)*int(bitDepth+1)+f.opN < f.MaxOpN {
|
|
return errors.Wrap(f.importValueSmallWrite(columnIDs, values, bitDepth, clear), "import small write")
|
|
}
|
|
|
|
f.storage.OpWriter = nil
|
|
|
|
// Process every value.
|
|
// If an error occurs then reopen the storage.
|
|
if err := func() (err error) {
|
|
for i := range columnIDs {
|
|
columnID, value := columnIDs[i], values[i]
|
|
if _, err := f.importSetValue(columnID, bitDepth, value, clear); err != nil {
|
|
return errors.Wrapf(err, "importSetValue")
|
|
}
|
|
}
|
|
return nil
|
|
}(); err != nil {
|
|
_ = f.closeStorage()
|
|
_ = f.openStorage()
|
|
return err
|
|
}
|
|
|
|
err := f.snapshot()
|
|
return errors.Wrap(err, "snapshotting")
|
|
}
|
|
|
|
// importRoaring imports from the official roaring data format defined at
|
|
// https://github.com/RoaringBitmap/RoaringFormatSpec or from pilosa's version
|
|
// of the roaring format. The cache is updated to reflect the new data.
|
|
func (f *fragment) importRoaring(ctx context.Context, data []byte, clear bool) error {
|
|
span, ctx := tracing.StartSpanFromContext(ctx, "fragment.importRoaring")
|
|
defer span.Finish()
|
|
span, ctx = tracing.StartSpanFromContext(ctx, "importRoaring.AcquireFragmentLock")
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
span.Finish()
|
|
bm := roaring.NewBTreeBitmap()
|
|
span, ctx = tracing.StartSpanFromContext(ctx, "importRoaring.UnmarshalBinary")
|
|
err := bm.UnmarshalBinary(data)
|
|
span.Finish()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// get a list of keys in order to update the cache
|
|
iter, _ := bm.Containers.Iterator(0)
|
|
rowSet := make(map[uint64]struct{})
|
|
var lastRow uint64 = math.MaxUint64
|
|
|
|
incomingCnt := uint64(0)
|
|
for iter.Next() {
|
|
key, c := iter.Value()
|
|
incomingCnt += uint64(c.N())
|
|
|
|
// virtual row for the current container
|
|
vRow := key >> shardVsContainerExponent
|
|
|
|
// skip dups
|
|
if vRow == lastRow {
|
|
continue
|
|
}
|
|
rowSet[vRow] = struct{}{}
|
|
lastRow = vRow
|
|
}
|
|
|
|
// take smallPath? TODO - ideally instead of checking f.storage.Any(), the
|
|
// test here would be if the storage size (in bytes) is significantly
|
|
// greater than the size of the incoming bits serialized as append
|
|
// operations. Getting the storage size might be a bit expensive though
|
|
// especially if the fragment isn't mapped.
|
|
if incomingCnt+uint64(f.opN) <= uint64(f.MaxOpN) && f.storage.Any() {
|
|
toSet, toClear := bm.Slice(), []uint64{}
|
|
if clear {
|
|
toSet, toClear = toClear, toSet
|
|
}
|
|
span, _ = tracing.StartSpanFromContext(ctx, "importRoaring.ImportPositions")
|
|
err := f.importPositions(toSet, toClear, rowSet)
|
|
span.Finish()
|
|
return err
|
|
}
|
|
|
|
if clear {
|
|
span, ctx = tracing.StartSpanFromContext(ctx, "importRoaringDifference")
|
|
bm = f.storage.Difference(bm)
|
|
span.Finish()
|
|
} else if f.storage.Containers.Size() >= bm.Containers.Size() {
|
|
span, ctx = tracing.StartSpanFromContext(ctx, "importRoaringStorageUIP")
|
|
f.storage.UnionInPlace(bm)
|
|
bm = f.storage
|
|
span.Finish()
|
|
} else {
|
|
span, ctx = tracing.StartSpanFromContext(ctx, "importRoaringBitmapUIP")
|
|
bm.UnionInPlace(f.storage)
|
|
span.Finish()
|
|
}
|
|
|
|
for rowID := range rowSet {
|
|
n := bm.CountRange(rowID*ShardWidth, (rowID+1)*ShardWidth)
|
|
f.cache.BulkAdd(rowID, n)
|
|
}
|
|
f.cache.Recalculate()
|
|
|
|
span, _ = tracing.StartSpanFromContext(ctx, "importRoaring.WriteToFragment")
|
|
n, err := unprotectedWriteToFragment(f, bm)
|
|
span.LogKV("bytesWritten", n)
|
|
span.Finish()
|
|
return err
|
|
}
|
|
|
|
// incrementOpN increase the operation count by one.
|
|
// If the count exceeds the maximum allowed then a snapshot is performed.
|
|
func (f *fragment) incrementOpN() error {
|
|
f.opN++
|
|
if f.opN <= f.MaxOpN {
|
|
return nil
|
|
}
|
|
|
|
if err := f.snapshot(); err != nil {
|
|
return fmt.Errorf("snapshot: %s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Snapshot writes the storage bitmap to disk and reopens it.
|
|
func (f *fragment) Snapshot() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.snapshot()
|
|
}
|
|
func track(start time.Time, message string, stats stats.StatsClient, logger logger.Logger) {
|
|
elapsed := time.Since(start)
|
|
logger.Printf("%s took %s", message, elapsed)
|
|
stats.Histogram("snapshot", elapsed.Seconds(), 1.0)
|
|
}
|
|
|
|
func (f *fragment) snapshot() error {
|
|
_, err := unprotectedWriteToFragment(f, f.storage)
|
|
return err
|
|
}
|
|
|
|
// unprotectedWriteToFragment writes the fragment f with bm as the data. It is unprotected, and
|
|
// f.mu must be locked when calling it.
|
|
func unprotectedWriteToFragment(f *fragment, bm *roaring.Bitmap) (n int64, err error) { // nolint: interfacer
|
|
|
|
completeMessage := fmt.Sprintf("fragment: snapshot complete %s/%s/%s/%d", f.index, f.field, f.view, f.shard)
|
|
start := time.Now()
|
|
defer track(start, completeMessage, f.stats, f.Logger)
|
|
|
|
// Create a temporary file to snapshot to.
|
|
snapshotPath := f.path + snapshotExt
|
|
file, err := os.Create(snapshotPath)
|
|
if err != nil {
|
|
return n, fmt.Errorf("create snapshot file: %s", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
// Write storage to snapshot.
|
|
bw := bufio.NewWriter(file)
|
|
if n, err = bm.WriteTo(bw); err != nil {
|
|
return n, fmt.Errorf("snapshot write to: %s", err)
|
|
}
|
|
|
|
if err := bw.Flush(); err != nil {
|
|
return n, fmt.Errorf("flush: %s", err)
|
|
}
|
|
|
|
// Close current storage.
|
|
if err := f.closeStorage(); err != nil {
|
|
return n, fmt.Errorf("close storage: %s", err)
|
|
}
|
|
|
|
// Move snapshot to data file location.
|
|
if err := os.Rename(snapshotPath, f.path); err != nil {
|
|
return n, fmt.Errorf("rename snapshot: %s", err)
|
|
}
|
|
|
|
// Reopen storage.
|
|
if err := f.openStorage(); err != nil {
|
|
return n, fmt.Errorf("open storage: %s", err)
|
|
}
|
|
|
|
// Reset operation count.
|
|
f.opN = 0
|
|
|
|
return n, nil
|
|
}
|
|
|
|
// RecalculateCache rebuilds the cache regardless of invalidate time delay.
|
|
func (f *fragment) RecalculateCache() {
|
|
f.mu.Lock()
|
|
f.cache.Recalculate()
|
|
f.mu.Unlock()
|
|
}
|
|
|
|
// FlushCache writes the cache data to disk.
|
|
func (f *fragment) FlushCache() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.flushCache()
|
|
}
|
|
|
|
func (f *fragment) flushCache() error {
|
|
if f.cache == nil {
|
|
return nil
|
|
}
|
|
|
|
if f.CacheType == CacheTypeNone {
|
|
return nil
|
|
}
|
|
|
|
// Retrieve a list of row ids from the cache.
|
|
ids := f.cache.IDs()
|
|
|
|
// Marshal cache data to bytes.
|
|
buf, err := proto.Marshal(&internal.Cache{IDs: ids})
|
|
if err != nil {
|
|
return errors.Wrap(err, "marshalling")
|
|
}
|
|
|
|
// Write to disk.
|
|
if err := ioutil.WriteFile(f.cachePath(), buf, 0666); err != nil {
|
|
return errors.Wrap(err, "writing")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteTo writes the fragment's data to w.
|
|
func (f *fragment) WriteTo(w io.Writer) (n int64, err error) {
|
|
// Force cache flush.
|
|
if err := f.FlushCache(); err != nil {
|
|
return 0, errors.Wrap(err, "flushing cache")
|
|
}
|
|
|
|
// Write out data and cache to a tar archive.
|
|
tw := tar.NewWriter(w)
|
|
if err := f.writeStorageToArchive(tw); err != nil {
|
|
return 0, fmt.Errorf("write storage: %s", err)
|
|
}
|
|
if err := f.writeCacheToArchive(tw); err != nil {
|
|
return 0, fmt.Errorf("write cache: %s", err)
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
func (f *fragment) writeStorageToArchive(tw *tar.Writer) error {
|
|
// Open separate file descriptor to read from.
|
|
file, err := os.Open(f.path)
|
|
if err != nil {
|
|
return errors.Wrap(err, "opening file")
|
|
}
|
|
defer file.Close()
|
|
|
|
// Retrieve the current file size under lock so we don't read
|
|
// while an operation is appending to the end.
|
|
var sz int64
|
|
if err := func() error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
fi, err := file.Stat()
|
|
if err != nil {
|
|
return errors.Wrap(err, "statting")
|
|
}
|
|
sz = fi.Size()
|
|
|
|
return nil
|
|
}(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write archive header.
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: "data",
|
|
Mode: 0600,
|
|
Size: sz,
|
|
ModTime: time.Now(),
|
|
}); err != nil {
|
|
return errors.Wrap(err, "writing header")
|
|
}
|
|
|
|
// Copy the file up to the last known size.
|
|
// This is done outside the lock because the storage format is append-only.
|
|
if _, err := io.CopyN(tw, file, sz); err != nil {
|
|
return errors.Wrap(err, "copying")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (f *fragment) writeCacheToArchive(tw *tar.Writer) error {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
// Read cache into buffer.
|
|
buf, err := ioutil.ReadFile(f.cachePath())
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
} else if err != nil {
|
|
return errors.Wrap(err, "reading cache")
|
|
}
|
|
|
|
// Write archive header.
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
Name: "cache",
|
|
Mode: 0600,
|
|
Size: int64(len(buf)),
|
|
ModTime: time.Now(),
|
|
}); err != nil {
|
|
return errors.Wrap(err, "writing header")
|
|
}
|
|
|
|
// Write data to archive.
|
|
if _, err := tw.Write(buf); err != nil {
|
|
return errors.Wrap(err, "writing")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadFrom reads a data file from r and loads it into the fragment.
|
|
func (f *fragment) ReadFrom(r io.Reader) (n int64, err error) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
tr := tar.NewReader(r)
|
|
for {
|
|
// Read next tar header.
|
|
hdr, err := tr.Next()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return 0, errors.Wrap(err, "opening")
|
|
}
|
|
|
|
// Process file based on file name.
|
|
switch hdr.Name {
|
|
case "data":
|
|
if err := f.readStorageFromArchive(tr); err != nil {
|
|
return 0, errors.Wrap(err, "reading storage")
|
|
}
|
|
case "cache":
|
|
if err := f.readCacheFromArchive(tr); err != nil {
|
|
return 0, errors.Wrap(err, "reading cache")
|
|
}
|
|
default:
|
|
return 0, fmt.Errorf("invalid fragment archive file: %s", hdr.Name)
|
|
}
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
func (f *fragment) readStorageFromArchive(r io.Reader) error {
|
|
// Create a temporary file to copy into.
|
|
path := f.path + copyExt
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
return errors.Wrap(err, "creating directory")
|
|
}
|
|
defer file.Close()
|
|
|
|
// Copy reader into temporary path.
|
|
if _, err = io.Copy(file, r); err != nil {
|
|
return errors.Wrap(err, "copying")
|
|
}
|
|
|
|
// Close current storage.
|
|
if err := f.closeStorage(); err != nil {
|
|
return errors.Wrap(err, "closing")
|
|
}
|
|
|
|
// Move snapshot to data file location.
|
|
if err := os.Rename(path, f.path); err != nil {
|
|
return errors.Wrap(err, "renaming")
|
|
}
|
|
|
|
// Reopen storage.
|
|
if err := f.openStorage(); err != nil {
|
|
return errors.Wrap(err, "opening")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *fragment) readCacheFromArchive(r io.Reader) error {
|
|
// Slurp data from reader and write to disk.
|
|
buf, err := ioutil.ReadAll(r)
|
|
if err != nil {
|
|
return errors.Wrap(err, "reading")
|
|
} else if err := ioutil.WriteFile(f.cachePath(), buf, 0666); err != nil {
|
|
return errors.Wrap(err, "writing")
|
|
}
|
|
|
|
// Re-open cache.
|
|
if err := f.openCache(); err != nil {
|
|
return errors.Wrap(err, "opening")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// rowFilter is a function signature for controlling iteration over containers
|
|
// in a fragment. It will be invoked on each container found and returns two
|
|
// booleans. The first is whether the row this container is in should be
|
|
// included or skipped, and the second is whether to stop processing or
|
|
// continue.
|
|
type rowFilter func(rowID, key uint64, c *roaring.Container) (include, done bool)
|
|
|
|
// filterWithLimit returns a filter which will only allow a limited number of
|
|
// rows to be returned. It should be applied last so that it is only called (and
|
|
// therefore only updates its internal state) if the row is being included by
|
|
// every other filter.
|
|
func filterWithLimit(limit uint64) rowFilter {
|
|
return func(rowID, key uint64, c *roaring.Container) (include, done bool) {
|
|
if limit > 0 {
|
|
limit--
|
|
return true, false
|
|
}
|
|
return false, true
|
|
}
|
|
}
|
|
|
|
func filterColumn(col uint64) rowFilter {
|
|
return func(rowID, key uint64, c *roaring.Container) (include, done bool) {
|
|
colID := col % ShardWidth
|
|
colKey := ((rowID * ShardWidth) + colID) >> 16
|
|
colVal := uint16(colID & 0xFFFF) // columnID within the container
|
|
return colKey == key && c.Contains(colVal), false
|
|
}
|
|
}
|
|
|
|
// TODO: this works, but it would be more performant if the fragment could seek
|
|
// to the next row in the rows list rather than asking the filter for each
|
|
// container serially. The container iterator would need to expose a seek
|
|
// method, and the rowFilter would need some way of communicating to
|
|
// fragment.rows what the next rowID to seek to is.
|
|
func filterWithRows(rows []uint64) rowFilter {
|
|
loc := 0
|
|
return func(rowID, key uint64, c *roaring.Container) (include, done bool) {
|
|
if loc >= len(rows) {
|
|
return false, true
|
|
}
|
|
i := sort.Search(len(rows[loc:]), func(i int) bool {
|
|
return rows[loc+i] >= rowID
|
|
})
|
|
loc += i
|
|
if loc >= len(rows) {
|
|
return false, true
|
|
}
|
|
if rows[loc] == rowID {
|
|
if loc == len(rows)-1 {
|
|
done = true
|
|
}
|
|
return true, done
|
|
}
|
|
return false, false
|
|
}
|
|
}
|
|
|
|
// rows returns all rows starting from 'start'. Filters will be applied in
|
|
// order. All filters must return true to include the row. Once a row is
|
|
// included, further containers in that row will be skipped. So, for a row to be
|
|
// included, there must be one container in that row where all filters return
|
|
// true. For a row to be skipped, at least one filter must return false for each
|
|
// container in that row (it need not be the same filter for each). Any filter
|
|
// returning done == true will cause processing to stop after all filters for
|
|
// this container have been processed. The rows accumulated up to this point
|
|
// (including this row if all filters passed) will be returned.
|
|
func (f *fragment) rows(start uint64, filters ...rowFilter) []uint64 {
|
|
startKey := rowToKey(start)
|
|
i, _ := f.storage.Containers.Iterator(startKey)
|
|
rows := make([]uint64, 0)
|
|
var lastRow uint64 = math.MaxUint64
|
|
|
|
// Loop over the existing containers.
|
|
for i.Next() {
|
|
key, c := i.Value()
|
|
|
|
// virtual row for the current container
|
|
vRow := key >> shardVsContainerExponent
|
|
|
|
// skip dups
|
|
if vRow == lastRow {
|
|
continue
|
|
}
|
|
|
|
// apply filters
|
|
addRow, done := true, false
|
|
for _, filter := range filters {
|
|
var d bool
|
|
addRow, d = filter(vRow, key, c)
|
|
done = done || d
|
|
if !addRow {
|
|
break
|
|
}
|
|
}
|
|
if addRow {
|
|
lastRow = vRow
|
|
rows = append(rows, vRow)
|
|
}
|
|
if done {
|
|
return rows
|
|
}
|
|
}
|
|
return rows
|
|
}
|
|
|
|
// upgradeRoaringBSIv2 upgrades a fragment that contains old BSI formatting
|
|
// to a new BSI format (v2). The new format moves the "exists" bit to the
|
|
// beginning & adds a negative sign bit.
|
|
func upgradeRoaringBSIv2(f *fragment, bitDepth uint) (string, error) {
|
|
// If flag set, already upgraded. Exit.
|
|
if f.storage.Flags&roaringFlagBSIv2 == 1 {
|
|
return "", nil
|
|
}
|
|
|
|
other := roaring.NewBitmap()
|
|
other.Flags = roaringFlagBSIv2
|
|
func() {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
|
|
f.storage.ForEach(func(i uint64) {
|
|
rowID, columnID := i/ShardWidth, (f.shard*ShardWidth)+(i%ShardWidth)
|
|
if rowID == uint64(bitDepth) {
|
|
_, _ = other.Add(pos(bsiExistsBit, columnID)) // move exists bit to beginning
|
|
} else {
|
|
_, _ = other.Add(pos(rowID+bsiOffsetBit, columnID)) // move other bits up
|
|
}
|
|
})
|
|
}()
|
|
|
|
// Create temporary file next to existing file.
|
|
newPath := f.path + ".tmp"
|
|
file, err := os.OpenFile(newPath, os.O_WRONLY|os.O_CREATE, 0666)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
// Write & flush to temporary file.
|
|
if _, err := other.WriteTo(file); err != nil {
|
|
return "", err
|
|
} else if err := file.Sync(); err != nil {
|
|
return "", err
|
|
} else if err := file.Close(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return newPath, nil
|
|
}
|
|
|
|
type rowIterator struct {
|
|
f *fragment
|
|
rowIDs []uint64
|
|
cur int
|
|
wrap bool
|
|
}
|
|
|
|
func (f *fragment) rowIterator(wrap bool, filters ...rowFilter) *rowIterator {
|
|
return &rowIterator{
|
|
f: f,
|
|
rowIDs: f.rows(0, filters...), // TODO: this may be memory intensive in high cardinality cases
|
|
wrap: wrap,
|
|
}
|
|
}
|
|
|
|
func (ri *rowIterator) Seek(rowID uint64) {
|
|
idx := sort.Search(len(ri.rowIDs), func(i int) bool {
|
|
return ri.rowIDs[i] >= rowID
|
|
})
|
|
ri.cur = idx
|
|
}
|
|
|
|
func (ri *rowIterator) Next() (r *Row, rowID uint64, wrapped bool) {
|
|
if ri.cur >= len(ri.rowIDs) {
|
|
if !ri.wrap || len(ri.rowIDs) == 0 {
|
|
return nil, 0, true
|
|
}
|
|
ri.Seek(0)
|
|
wrapped = true
|
|
}
|
|
rowID = ri.rowIDs[ri.cur]
|
|
r = ri.f.row(rowID)
|
|
ri.cur++
|
|
return r, rowID, wrapped
|
|
}
|
|
|
|
// FragmentBlock represents info about a subsection of the rows in a block.
|
|
// This is used for comparing data in remote blocks for active anti-entropy.
|
|
type FragmentBlock struct {
|
|
ID int `json:"id"`
|
|
Checksum []byte `json:"checksum"`
|
|
}
|
|
|
|
type blockHasher struct {
|
|
blockID int
|
|
buf [8]byte
|
|
hash hash.Hash
|
|
}
|
|
|
|
func newBlockHasher() blockHasher {
|
|
return blockHasher{
|
|
blockID: -1,
|
|
hash: xxhash.New(),
|
|
}
|
|
}
|
|
func (h *blockHasher) Reset() {
|
|
h.hash.Reset()
|
|
}
|
|
|
|
func (h *blockHasher) Sum() []byte {
|
|
return h.hash.Sum(nil)[:]
|
|
}
|
|
|
|
func (h *blockHasher) WriteValue(v uint64) {
|
|
binary.BigEndian.PutUint64(h.buf[:], v)
|
|
_, _ = h.hash.Write(h.buf[:])
|
|
}
|
|
|
|
// fragmentSyncer syncs a local fragment to one on a remote host.
|
|
type fragmentSyncer struct {
|
|
Fragment *fragment
|
|
|
|
Node *Node
|
|
Cluster *cluster
|
|
|
|
Closing <-chan struct{}
|
|
}
|
|
|
|
// isClosing returns true if the closing channel is closed.
|
|
func (s *fragmentSyncer) isClosing() bool {
|
|
select {
|
|
case <-s.Closing:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// syncFragment compares checksums for the local and remote fragments and
|
|
// then merges any blocks which have differences.
|
|
func (s *fragmentSyncer) syncFragment() error {
|
|
span, ctx := tracing.StartSpanFromContext(context.Background(), "FragmentSyncer.syncFragment")
|
|
defer span.Finish()
|
|
|
|
// Determine replica set.
|
|
nodes := s.Cluster.shardNodes(s.Fragment.index, s.Fragment.shard)
|
|
if len(nodes) == 1 {
|
|
return nil
|
|
}
|
|
|
|
// Create a set of blocks.
|
|
blockSets := make([][]FragmentBlock, 0, len(nodes))
|
|
for _, node := range nodes {
|
|
// Read local blocks.
|
|
if node.ID == s.Node.ID {
|
|
b := s.Fragment.Blocks()
|
|
blockSets = append(blockSets, b)
|
|
continue
|
|
}
|
|
|
|
// Retrieve remote blocks.
|
|
blocks, err := s.Cluster.InternalClient.FragmentBlocks(ctx, &node.URI, s.Fragment.index, s.Fragment.field, s.Fragment.view, s.Fragment.shard)
|
|
if err != nil && err != ErrFragmentNotFound {
|
|
return errors.Wrap(err, "getting blocks")
|
|
}
|
|
blockSets = append(blockSets, blocks)
|
|
|
|
// Verify sync is not prematurely closing.
|
|
if s.isClosing() {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Iterate over all blocks and find differences.
|
|
checksums := make([][]byte, len(nodes))
|
|
for {
|
|
// Find min block id.
|
|
blockID := -1
|
|
for _, blocks := range blockSets {
|
|
if len(blocks) == 0 {
|
|
continue
|
|
} else if blockID == -1 || blocks[0].ID < blockID {
|
|
blockID = blocks[0].ID
|
|
}
|
|
}
|
|
|
|
// Exit loop if no blocks are left.
|
|
if blockID == -1 {
|
|
break
|
|
}
|
|
|
|
// Read the checksum for the current block.
|
|
for i, blocks := range blockSets {
|
|
// Clear checksum if the next block for the node doesn't match current ID.
|
|
if len(blocks) == 0 || blocks[0].ID != blockID {
|
|
checksums[i] = nil
|
|
continue
|
|
}
|
|
|
|
// Otherwise set checksum and move forward.
|
|
checksums[i] = blocks[0].Checksum
|
|
blockSets[i] = blockSets[i][1:]
|
|
}
|
|
|
|
// Ignore if all the blocks on each node match.
|
|
if byteSlicesEqual(checksums) {
|
|
continue
|
|
}
|
|
// Synchronize block.
|
|
if err := s.syncBlock(blockID); err != nil {
|
|
return fmt.Errorf("sync block: id=%d, err=%s", blockID, err)
|
|
}
|
|
s.Fragment.stats.Count("BlockRepair", 1, 1.0)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// syncBlock sends and receives all rows for a given block.
|
|
// Returns an error if any remote hosts are unreachable.
|
|
func (s *fragmentSyncer) syncBlock(id int) error {
|
|
span, ctx := tracing.StartSpanFromContext(context.Background(), "FragmentSyncer.syncBlock")
|
|
defer span.Finish()
|
|
|
|
f := s.Fragment
|
|
|
|
// Read pairs from each remote block.
|
|
var uris []*URI
|
|
var pairSets []pairSet
|
|
for _, node := range s.Cluster.shardNodes(f.index, f.shard) {
|
|
if s.Node.ID == node.ID {
|
|
continue
|
|
}
|
|
|
|
// Verify sync is not prematurely closing.
|
|
if s.isClosing() {
|
|
return nil
|
|
}
|
|
|
|
uri := &node.URI
|
|
uris = append(uris, uri)
|
|
|
|
// Only sync the standard block.
|
|
rowIDs, columnIDs, err := s.Cluster.InternalClient.BlockData(ctx, &node.URI, f.index, f.field, f.view, f.shard, id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "getting block")
|
|
}
|
|
|
|
pairSets = append(pairSets, pairSet{
|
|
columnIDs: columnIDs,
|
|
rowIDs: rowIDs,
|
|
})
|
|
}
|
|
|
|
// Verify sync is not prematurely closing.
|
|
if s.isClosing() {
|
|
return nil
|
|
}
|
|
|
|
// Merge blocks together.
|
|
sets, clears, err := f.mergeBlock(id, pairSets)
|
|
if err != nil {
|
|
return errors.Wrap(err, "merging")
|
|
}
|
|
|
|
// Write updates to remote blocks.
|
|
for i := 0; i < len(uris); i++ {
|
|
set, clear := sets[i], clears[i]
|
|
|
|
// Handle Sets.
|
|
if len(set.columnIDs) > 0 {
|
|
setData, err := bitsToRoaringData(set)
|
|
if err != nil {
|
|
return errors.Wrap(err, "converting bits to roaring data (set)")
|
|
}
|
|
|
|
setReq := &ImportRoaringRequest{
|
|
Clear: false,
|
|
Views: map[string][]byte{cleanViewName(f.view): setData},
|
|
}
|
|
|
|
if err := s.Cluster.InternalClient.ImportRoaring(ctx, uris[i], f.index, f.field, f.shard, true, setReq); err != nil {
|
|
return errors.Wrap(err, "sending roaring data (set)")
|
|
}
|
|
}
|
|
|
|
// Handle Clears.
|
|
if len(clear.columnIDs) > 0 {
|
|
clearData, err := bitsToRoaringData(clear)
|
|
if err != nil {
|
|
return errors.Wrap(err, "converting bits to roaring data (clear)")
|
|
}
|
|
|
|
clearReq := &ImportRoaringRequest{
|
|
Clear: true,
|
|
Views: map[string][]byte{"": clearData},
|
|
}
|
|
|
|
if err := s.Cluster.InternalClient.ImportRoaring(ctx, uris[i], f.index, f.field, f.shard, true, clearReq); err != nil {
|
|
return errors.Wrap(err, "sending roaring data (clear)")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// cleanViewName converts a viewname into the equivalent
|
|
// string required by the external api. Because views are
|
|
// not exposed externally, the conversion looks like this:
|
|
// "standard" -> ""
|
|
// "standard_YYYYMMDD" -> "YYYYMMDD"
|
|
// "other" -> "other" (there is currently not a use for this)
|
|
func cleanViewName(v string) string {
|
|
viewPrefix := viewStandard + "_"
|
|
if strings.HasPrefix(v, viewPrefix) {
|
|
return v[len(viewPrefix):]
|
|
} else if v == viewStandard {
|
|
return ""
|
|
}
|
|
return v
|
|
}
|
|
|
|
// bitsToRoaringData converts a pairSet into a roaring.Bitmap
|
|
// which represents the data within a single shard.
|
|
func bitsToRoaringData(ps pairSet) ([]byte, error) {
|
|
bmp := roaring.NewBitmap()
|
|
for j := 0; j < len(ps.columnIDs); j++ {
|
|
bmp.DirectAdd(ps.rowIDs[j]*ShardWidth + (ps.columnIDs[j] % ShardWidth))
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
_, err := bmp.WriteTo(&buf)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "writing to buffer")
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func madvise(b []byte, advice int) error { // nolint: unparam
|
|
_, _, err := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), uintptr(advice))
|
|
if err != 0 {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pairSet is a list of equal length row and column id lists.
|
|
type pairSet struct {
|
|
rowIDs []uint64
|
|
columnIDs []uint64
|
|
}
|
|
|
|
// byteSlicesEqual returns true if all slices are equal.
|
|
func byteSlicesEqual(a [][]byte) bool {
|
|
if len(a) == 0 {
|
|
return true
|
|
}
|
|
|
|
for _, v := range a[1:] {
|
|
if !bytes.Equal(a[0], v) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// pos returns the row position of a row/column pair.
|
|
func pos(rowID, columnID uint64) uint64 {
|
|
return (rowID * ShardWidth) + (columnID % ShardWidth)
|
|
}
|
|
|
|
// vector stores the mapping of colID to rowID.
|
|
// It's used for a mutex field type.
|
|
type vector interface {
|
|
Get(colID uint64) (uint64, bool, error)
|
|
}
|
|
|
|
// rowsVector implements the vector interface by looking
|
|
// at row data as needed.
|
|
type rowsVector struct {
|
|
f *fragment
|
|
}
|
|
|
|
// newRowsVector returns a rowsVector for a given fragment.
|
|
func newRowsVector(f *fragment) *rowsVector {
|
|
return &rowsVector{
|
|
f: f,
|
|
}
|
|
}
|
|
|
|
// Get returns the rowID associated to the given colID.
|
|
// Additionally, it returns true if a value was found,
|
|
// otherwise it returns false.
|
|
func (v *rowsVector) Get(colID uint64) (uint64, bool, error) {
|
|
rows := v.f.rows(0, filterColumn(colID))
|
|
if len(rows) > 1 {
|
|
return 0, false, errors.New("found multiple row values for column")
|
|
} else if len(rows) == 1 {
|
|
return rows[0], true, nil
|
|
}
|
|
return 0, false, nil
|
|
}
|
|
|
|
// rowToKey converts a Pilosa row ID to the key of the container which starts
|
|
// that row in the bitmap which represents this entire fragment. A fragment is
|
|
// all the rows within a shard within a field concatenated together.
|
|
func rowToKey(rowID uint64) (key uint64) {
|
|
return rowID * (ShardWidth / containerWidth)
|
|
}
|
|
|
|
// boolVector implements the vector interface by looking
|
|
// at data in rows 0 and 1.
|
|
type boolVector struct {
|
|
f *fragment
|
|
}
|
|
|
|
// newBoolVector returns a boolVector for a given fragment.
|
|
func newBoolVector(f *fragment) *boolVector {
|
|
return &boolVector{
|
|
f: f,
|
|
}
|
|
}
|
|
|
|
// Get returns the rowID associated to the given colID.
|
|
// Additionally, it returns true if a value was found,
|
|
// otherwise it returns false.
|
|
func (v *boolVector) Get(colID uint64) (uint64, bool, error) {
|
|
rows := v.f.rows(0, filterColumn(colID))
|
|
if len(rows) > 1 {
|
|
return 0, false, errors.New("found multiple row values for column")
|
|
} else if len(rows) == 1 {
|
|
switch rows[0] {
|
|
case falseRowID, trueRowID:
|
|
return rows[0], true, nil
|
|
default:
|
|
return 0, false, errors.New("found non-boolean value")
|
|
}
|
|
}
|
|
return 0, false, nil
|
|
}
|