mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-07 17:15:56 +00:00
Merge pull request #621 from molecula/bluegreen_atg
blueGreenTx roaring vs badger is all tests green (atg)
This commit is contained in:
commit
d2586210a2
18 changed files with 745 additions and 223 deletions
60
Makefile
60
Makefile
|
|
@ -186,41 +186,43 @@ topt-race:
|
|||
|
||||
# blue-green checks. These run two different storage engines (rbf, roaring, or badger)
|
||||
# and compare each transaction for a result.
|
||||
bg-br:
|
||||
mv log.bg.bg_roar log.bg.bg_roar.prev || true
|
||||
PILOSA_TXSRC=badger_roaring go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.bg.bg_roar
|
||||
@echo " log.bg.bg_roar green: \c"; cat log.bg.bg_roar | grep PASS |wc -l
|
||||
@echo " log.bg.bg_roar red: \c"; cat log.bg.bg_roar | grep '\-\-\- FAIL' |wc -l
|
||||
|
||||
bg-rb:
|
||||
bg-rr: # shorthand for bluegreen test with A:badger; B:roaring
|
||||
mv log.bg-rr log.bg-rr.prev || true
|
||||
set -o pipefail; PILOSA_TXSRC=badger_roaring go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.bg-rr
|
||||
@echo " log.bg-rr green: \c"; cat log.bg-rr | grep PASS |wc -l
|
||||
@echo " log.bg-rr red: \c"; cat log.bg-rr | grep '\-\-\- FAIL' |wc -l
|
||||
|
||||
rr-bg: # bluegreen with A:roaring; B:badger (B's values are returned).
|
||||
mv log.bg.roar_bg log.bg.roar_bg.prev || true
|
||||
PILOSA_TXSRC=roaring_badger go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.bg.roar_bg
|
||||
@echo " log.bg.roar_bg green: \c"; cat log.bg.roar_bg | grep PASS |wc -l
|
||||
@echo " log.bg.roar_bg red: \c"; cat log.bg.roar_bg | grep '\-\-\- FAIL' |wc -l
|
||||
set -o pipefail; PILOSA_TXSRC=roaring_badger go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.rr-bg
|
||||
##PILOSA_TXSRC=roaring_badger go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.rr-bg
|
||||
@echo " log.rr-bg green: \c"; cat log.rr-bg | grep PASS |wc -l
|
||||
@echo " log.rr-bg red: \c"; cat log.rr-bg | grep '\-\-\- FAIL' |wc -l
|
||||
|
||||
bg-fr:
|
||||
mv log.bg.rbf_roar log.bg.rbf_roar.prev || true
|
||||
PILOSA_TXSRC=rbf_roaring go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.bg.rbf_roar
|
||||
@echo " log.bg.rbf_roar green: \c"; cat log.bg.rbf_roar | grep PASS |wc -l
|
||||
@echo " log.bg.rbf_roar red: \c"; cat log.bg.rbf_roar | grep '\-\-\- FAIL' |wc -l
|
||||
rbf-rr:
|
||||
mv log.rbf-rr log.rbf-rr.prev || true
|
||||
set -o pipefail; PILOSA_TXSRC=rbf_roaring go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.rbf-rr
|
||||
@echo " log.rbf-rr green: \c"; cat log.rbf-rr | grep PASS |wc -l
|
||||
@echo " log.rbf-rr red: \c"; cat log.rbf-rr | grep '\-\-\- FAIL' |wc -l
|
||||
|
||||
bg-rf:
|
||||
mv log.bg.roar_rbf log.bg.roar_rbf.prev || true
|
||||
PILOSA_TXSRC=roaring_rbf go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.bg.roar_rbf
|
||||
@echo " log.bg.roar_rbf green: \c"; cat log.bg.roar_rbf | grep PASS |wc -l
|
||||
@echo " log.bg.roar_rbf red: \c"; cat log.bg.roar_rbf | grep '\-\-\- FAIL' |wc -l
|
||||
rr-rbf:
|
||||
mv log.rr-rbf log.rr-rbf.prev || true
|
||||
set -o pipefail; PILOSA_TXSRC=roaring_rbf go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.rr-rbf
|
||||
@echo " log.rr-rbf green: \c"; cat log.rr-rbf | grep PASS |wc -l
|
||||
@echo " log.rr-rbf red: \c"; cat log.rr-rbf | grep '\-\-\- FAIL' |wc -l
|
||||
|
||||
bg-fb:
|
||||
mv log.bg.rbf_badger log.bg.rbf_badger.prev || true
|
||||
PILOSA_TXSRC=rbf_badger go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.bg.rbf_badger
|
||||
@echo " log.bg.rbf_badger green: \c"; cat log.bg.rbf_badger | grep PASS |wc -l
|
||||
@echo " log.bg.rbf_badger red: \c"; cat log.bg.rbf_badger | grep '\-\-\- FAIL' |wc -l
|
||||
rbf-bg:
|
||||
mv log.rbf-bg log.rbf-bg.prev || true
|
||||
set -o pipefail; PILOSA_TXSRC=rbf_badger go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.rbf-bg
|
||||
@echo " log.rbf-bg green: \c"; cat log.rbf-bg | grep PASS |wc -l
|
||||
@echo " log.rbf-bg red: \c"; cat log.rbf-bg | grep '\-\-\- FAIL' |wc -l
|
||||
|
||||
bg-bf:
|
||||
mv log.bg.badger_rbf log.bg.badger_rbf.prev || true
|
||||
PILOSA_TXSRC=badger_rbf go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.bg.badger_rbf
|
||||
@echo " log.bg.badger_rbf green: \c"; cat log.bg.badger_rbf | grep PASS |wc -l
|
||||
@echo " log.bg.badger_rbf red: \c"; cat log.bg.badger_rbf | grep '\-\-\- FAIL' |wc -l
|
||||
bg-rbf:
|
||||
mv log.bg-rbf log.bg-rbf.prev || true
|
||||
set -o pipefail; PILOSA_TXSRC=badger_rbf go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.bg-rbf
|
||||
@echo " log.bg-rbf green: \c"; cat log.bg-rbf | grep PASS |wc -l
|
||||
@echo " log.bg-rbf red: \c"; cat log.bg-rbf | grep '\-\-\- FAIL' |wc -l
|
||||
|
||||
|
||||
# Run golangci-lint
|
||||
|
|
|
|||
78
badger.go
78
badger.go
|
|
@ -432,6 +432,8 @@ type BadgerDBWrapper struct {
|
|||
// stack() from our creation point, to track tests
|
||||
// that haven't closed us.
|
||||
startStack string
|
||||
|
||||
DeleteEmptyContainer bool
|
||||
}
|
||||
|
||||
// unprotectedListOpenTxAsString is a debugging helper.
|
||||
|
|
@ -477,12 +479,13 @@ func (w *BadgerDBWrapper) NewBadgerTx(write bool, initialIndexName string) (tx *
|
|||
defer w.muDb.Unlock()
|
||||
|
||||
tx = &BadgerTx{
|
||||
write: write,
|
||||
tx: w.db.NewTransaction(write),
|
||||
Db: w,
|
||||
initloc: stack(),
|
||||
doAllocZero: w.doAllocZero,
|
||||
initialIndexName: initialIndexName,
|
||||
write: write,
|
||||
tx: w.db.NewTransaction(write),
|
||||
Db: w,
|
||||
initloc: stack(),
|
||||
doAllocZero: w.doAllocZero,
|
||||
initialIndexName: initialIndexName,
|
||||
DeleteEmptyContainer: w.DeleteEmptyContainer,
|
||||
}
|
||||
|
||||
if w.openTx == nil {
|
||||
|
|
@ -541,6 +544,8 @@ type BadgerTx struct {
|
|||
ourContainers []*roaring.Container
|
||||
|
||||
initialIndexName string
|
||||
|
||||
DeleteEmptyContainer bool
|
||||
}
|
||||
|
||||
func (tx *BadgerTx) Type() string {
|
||||
|
|
@ -771,6 +776,11 @@ func badgerIndexOnlyPrefix(indexName string) []byte {
|
|||
return []byte(fmt.Sprintf("idx:'%v';", indexName))
|
||||
}
|
||||
|
||||
// same for deleting a whole field.
|
||||
func badgerFieldPrefix(index, field string) []byte {
|
||||
return []byte(fmt.Sprintf("idx:'%v';fld:'%v';", index, field))
|
||||
}
|
||||
|
||||
// Container returns the requested roaring.Container, selected by fragment and ckey
|
||||
func (tx *BadgerTx) Container(index, field, view string, shard uint64, ckey uint64) (c *roaring.Container, err error) {
|
||||
|
||||
|
|
@ -1012,7 +1022,10 @@ func (tx *BadgerTx) ContainerIterator(index, field, view string, shard uint64, f
|
|||
if !bi.it.ValidForPrefix(prefix) {
|
||||
return bi, false, nil
|
||||
}
|
||||
return bi, true, nil
|
||||
item := bi.it.Item()
|
||||
// have to compare b/c badger might give us valid iterator
|
||||
// that is past our needle if needle isn't present.
|
||||
return bi, bytes.Equal(item.Key(), needle), nil
|
||||
}
|
||||
|
||||
// BadgerIterator is the iterator returned from a BadgerTx.ContainerIterator() call.
|
||||
|
|
@ -1301,18 +1314,18 @@ func (tx *BadgerTx) UnionInPlace(index, field, view string, shard uint64, others
|
|||
}
|
||||
|
||||
// CountRange returns the count of hot bits in the start, end range on the fragment.
|
||||
// roaring.countRange counts the number of bits set between [start, end).
|
||||
func (tx *BadgerTx) CountRange(index, field, view string, shard uint64, start, end uint64) (n uint64, err error) {
|
||||
|
||||
skey := highbits(start)
|
||||
ekey := highbits(end)
|
||||
|
||||
citer, found, err := tx.ContainerIterator(index, field, view, shard, skey)
|
||||
_ = found
|
||||
panicOn(err)
|
||||
|
||||
defer citer.Close() // doesn't seem to be getting called.
|
||||
if !found {
|
||||
return 0, nil
|
||||
}
|
||||
defer citer.Close()
|
||||
|
||||
// If range is entirely in one container then just count that range.
|
||||
if skey == ekey {
|
||||
citer.Next()
|
||||
|
|
@ -1485,7 +1498,7 @@ func (tx *BadgerTx) ImportRoaringBits(index, field, view string, shard uint64, i
|
|||
changed += changes
|
||||
rowSet[currRow] -= changes
|
||||
|
||||
if newC.N() == 0 {
|
||||
if tx.DeleteEmptyContainer && newC.N() == 0 {
|
||||
err = tx.RemoveContainer(index, field, view, shard, itrKey)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -1565,6 +1578,10 @@ const (
|
|||
|
||||
func (tx *BadgerTx) toContainer(typ byte, v []byte) (r *roaring.Container) {
|
||||
|
||||
if len(v) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// For safety we copy v, since it lives in BadgerDB's memory-mapped vlog-file,
|
||||
// and Badger will recycle it after tx ends with rollback or commit.
|
||||
// We copy into Go runtime GC managed memory. Technically we don't need
|
||||
|
|
@ -1609,30 +1626,28 @@ func (tx *BadgerTx) toContainer(typ byte, v []byte) (r *roaring.Container) {
|
|||
|
||||
// fromArray16 converts to an 8KB page
|
||||
func fromArray16(a []uint16) []byte {
|
||||
if len(a) == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
return (*[8192]byte)(unsafe.Pointer(&a[0]))[: len(a)*2 : len(a)*2]
|
||||
}
|
||||
|
||||
// fromArray64 converts to an 8KB page
|
||||
func fromArray64(a []uint64) []byte {
|
||||
if len(a) == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
return (*[8192]byte)(unsafe.Pointer(&a[0]))[:8192:8192]
|
||||
}
|
||||
|
||||
// fromInterval16 converts to 8KB page
|
||||
func fromInterval16(a []roaring.Interval16) []byte {
|
||||
if len(a) == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
return (*[8192]byte)(unsafe.Pointer(&a[0]))[: len(a)*4 : len(a)*4]
|
||||
}
|
||||
|
||||
// badgerKey method on fragment creates a query key in the
|
||||
// standard format by invoking the top level badgerKey with
|
||||
// the container key being highbits(rowID * ShardWidth).
|
||||
//
|
||||
// Commented out for now only to keep the golangci-lint happy,
|
||||
// as it has no users at the moment.
|
||||
//func (f *fragment) badgerKey(rowID uint64) []byte {
|
||||
// hi0 := highbits(rowID * ShardWidth)
|
||||
// return badgerKey(f.index, f.field, f.view, f.shard, hi0)
|
||||
//}
|
||||
|
||||
// StringifiedBadgerKeys returns a string with all the container
|
||||
// keys available in badger.
|
||||
func (w *BadgerDBWrapper) StringifiedBadgerKeys(optionalUseThisTx Tx) (r string) {
|
||||
|
|
@ -1674,6 +1689,10 @@ func (tx *BadgerTx) countBitsSet(bkey []byte) (n int) {
|
|||
return
|
||||
}
|
||||
|
||||
func (tx *BadgerTx) Dump() {
|
||||
fmt.Printf("%v\n", stringifiedBadgerKeysTx(tx))
|
||||
}
|
||||
|
||||
// stringifiedBadgerKeysTx reports all the badger keys and a
|
||||
// corresponding blake3 hash viewable by txn within the entire
|
||||
// badger database.
|
||||
|
|
@ -1826,6 +1845,19 @@ func dirAsString(path string) (r string) {
|
|||
|
||||
var _ = dirAsString // happy linter
|
||||
|
||||
func (w *BadgerDBWrapper) DeleteField(index, field, fieldPath string) error {
|
||||
|
||||
// under blue-green roaring_badger, the directory will not be found, b/c roaring will have
|
||||
// already done the os.RemoveAll(). BUT, RemoveAll returns nil error in this case. Docs:
|
||||
// "If the path does not exist, RemoveAll returns nil (no error)"
|
||||
err := os.RemoveAll(fieldPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "removing directory")
|
||||
}
|
||||
prefix := badgerFieldPrefix(index, field)
|
||||
return w.DeletePrefix(prefix)
|
||||
}
|
||||
|
||||
func (w *BadgerDBWrapper) DeleteFragment(index, field, view string, shard uint64, frag interface{}) error {
|
||||
prefix := badgerPrefix(index, field, view, shard)
|
||||
return w.DeletePrefix(prefix)
|
||||
|
|
|
|||
|
|
@ -1052,6 +1052,7 @@ func TestBadger_ImportRoaringBits(t *testing.T) {
|
|||
index, field, view, shard := "i", "f", "v", uint64(0)
|
||||
tx := dbwrap.NewBadgerTx(writable, index)
|
||||
defer tx.Rollback()
|
||||
tx.DeleteEmptyContainer = true // traditional badger Tx behavior, but not Roaring.
|
||||
|
||||
//bitvalue := uint64(42)
|
||||
|
||||
|
|
@ -1483,7 +1484,6 @@ func TestBadger_DeleteFragment(t *testing.T) {
|
|||
}
|
||||
err := tx.Commit()
|
||||
panicOn(err)
|
||||
//vv("Dump: %v", dbwrap.StringifiedBadgerKeys(nil))
|
||||
|
||||
// end of setup
|
||||
|
||||
|
|
|
|||
280
bluegreentx.go
280
bluegreentx.go
|
|
@ -15,27 +15,43 @@
|
|||
package pilosa
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/pilosa/pilosa/v2/roaring"
|
||||
)
|
||||
|
||||
// blueGreenTx runs two Tx together and notices differences in their output.
|
||||
// By convention, the 'b' Tx is the output that is returned to caller.
|
||||
//
|
||||
// Warning: DATA RACES are expected if RoaringTx is one side of the Tx pair.
|
||||
// The checkDatabase() call will do reads of the fragments at Commit/Rollback,
|
||||
// while the snapshotqueue may be doing writes.
|
||||
//
|
||||
// Do not run with go test -race and expect it to be race free.
|
||||
//
|
||||
type blueGreenTx struct {
|
||||
a Tx
|
||||
b Tx // b's output is returned
|
||||
|
||||
as string
|
||||
bs string
|
||||
|
||||
idx *Index
|
||||
|
||||
checker blueGreenChecker
|
||||
checker blueGreenChecker
|
||||
mu sync.Mutex
|
||||
rollbackOrCommitDone bool
|
||||
}
|
||||
|
||||
func newBlueGreenTx(a, b Tx, idx *Index) *blueGreenTx {
|
||||
return &blueGreenTx{a: a, b: b, idx: idx}
|
||||
as := a.Type()
|
||||
bs := b.Type()
|
||||
return &blueGreenTx{a: a, b: b, idx: idx, as: as, bs: bs}
|
||||
}
|
||||
|
||||
var _ = newBlueGreenTx // keep linter happy
|
||||
|
|
@ -46,6 +62,18 @@ func (c *blueGreenTx) Type() string {
|
|||
return c.a.Type() + "_" + c.b.Type()
|
||||
}
|
||||
|
||||
var blueGreenTxDumpMut sync.Mutex
|
||||
|
||||
func (c *blueGreenTx) Dump() {
|
||||
blueGreenTxDumpMut.Lock()
|
||||
defer blueGreenTxDumpMut.Unlock()
|
||||
fmt.Printf("%v blueGreenTx.Dump ============== \n", FileLine(2))
|
||||
fmt.Printf("A(%v) Dump:\n", c.as)
|
||||
c.a.Dump()
|
||||
fmt.Printf("B(%v) Dump:\n", c.bs)
|
||||
c.b.Dump()
|
||||
}
|
||||
|
||||
func (c *blueGreenTx) Readonly() bool {
|
||||
a := c.a.Readonly()
|
||||
b := c.b.Readonly()
|
||||
|
|
@ -57,6 +85,7 @@ func (c *blueGreenTx) Readonly() bool {
|
|||
|
||||
func (c *blueGreenTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator {
|
||||
c.checker.see(index, field, view, shard)
|
||||
// TODO(jea): does this need to be different, to handle c.a iteration at the same time?
|
||||
return c.b.NewTxIterator(index, field, view, shard)
|
||||
}
|
||||
|
||||
|
|
@ -70,53 +99,70 @@ func (c *blueGreenTx) IncrementOpN(index, field, view string, shard uint64, chan
|
|||
c.b.IncrementOpN(index, field, view, shard, changedN)
|
||||
}
|
||||
|
||||
// compareTxState is called for the first Commit or Rollback a blueGreenTx sees.
|
||||
func (c *blueGreenTx) compareTxState(index, field, view string, shard uint64) {
|
||||
here := fmt.Sprintf("%v/%v/%v/%v", index, field, view, shard)
|
||||
aIter, aFound, aErr := c.a.ContainerIterator(index, field, view, shard, 0)
|
||||
bIter, bFound, bErr := c.b.ContainerIterator(index, field, view, shard, 0)
|
||||
|
||||
if aFound != bFound {
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A ContainerIterator had aFound=%v, but B had bFound=%v; at '%v'", here, aFound, bFound, stack()))
|
||||
}
|
||||
|
||||
if aErr == nil {
|
||||
if aErr == nil || aIter != nil {
|
||||
defer aIter.Close()
|
||||
}
|
||||
if bErr == nil {
|
||||
if bErr == nil || bIter != nil {
|
||||
defer bIter.Close()
|
||||
}
|
||||
|
||||
if aFound != bFound {
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A(%v) ContainerIterator had aFound=%v, but B(%v) had bFound=%v; at '%v'", here, c.as, aFound, c.bs, bFound, stack()))
|
||||
}
|
||||
|
||||
if aErr != nil || bErr != nil {
|
||||
if aErr != nil && bErr != nil {
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A reported err '%v'; B reported err '%v' at %v", here, aErr, bErr, stack()))
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A(%v) reported err '%v'; B(%v) reported err '%v' at %v", here, c.as, aErr, c.bs, bErr, stack()))
|
||||
}
|
||||
if aErr != nil {
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A reported err %v at %v; but B did not", here, aErr, stack()))
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A(%v) reported err %v at %v; but B(%v) did not", here, c.as, aErr, c.bs, stack()))
|
||||
}
|
||||
if bErr != nil {
|
||||
panic(fmt.Sprintf("compareTxState[%v]: B reported err %v at %v; but A did not", here, bErr, stack()))
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: B(%v) reported err %v at %v; but A(%v) did not", here, c.bs, bErr, c.as, stack()))
|
||||
}
|
||||
}
|
||||
|
||||
for aIter.Next() {
|
||||
aKey, aValue := aIter.Value()
|
||||
|
||||
if !bIter.Next() {
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A found key %v, B didn't, at %v", here, aKey, stack()))
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A(%v) found key %v, B(%v) didn't, at %v", here, c.as, aKey, c.bs, stack()))
|
||||
}
|
||||
bKey, bValue := bIter.Value()
|
||||
if bKey != aKey {
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A found key %v, B found %v, at %v", here, aKey, bKey, stack()))
|
||||
AlwaysPrintf("problem in caller %v", Caller(2))
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: A(%v) found key %v, B(%v) found %v, at %v", here, c.as, aKey, c.bs, bKey, stack())) // crashing here on TestBSIGroup_importValue
|
||||
}
|
||||
if err := aValue.BitwiseCompare(bValue); err != nil {
|
||||
panic(fmt.Sprintf("compareTxState[%v]: key %v differs: %v at %v", here, aKey, err, stack()))
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: key %v differs: %v; A=%v; B=%v; at stack=%v", here, aKey, err, c.as, c.bs, stack()))
|
||||
}
|
||||
}
|
||||
// end checking everything in A, but does B have more?
|
||||
if bIter.Next() {
|
||||
bKey, _ := bIter.Value()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: B found key %v, A didn't, at %v", here, bKey, stack()))
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("compareTxState[%v]: B(%v) found key %v, A(%v) didn't, at %v", here, c.bs, bKey, c.as, stack()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *blueGreenTx) checkDatabase() {
|
||||
c.checker.mu.Lock()
|
||||
defer c.checker.mu.Unlock()
|
||||
|
||||
// seen() returns nil on 2nd or any further call,
|
||||
// so only the first Commit() or Rollback() does this.
|
||||
for index, fields := range c.checker.seen() {
|
||||
for field, views := range fields {
|
||||
for view, shards := range views {
|
||||
|
|
@ -129,6 +175,13 @@ func (c *blueGreenTx) checkDatabase() {
|
|||
}
|
||||
|
||||
func (c *blueGreenTx) Rollback() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.rollbackOrCommitDone {
|
||||
return
|
||||
}
|
||||
c.rollbackOrCommitDone = true
|
||||
|
||||
c.checkDatabase()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
|
@ -141,6 +194,12 @@ func (c *blueGreenTx) Rollback() {
|
|||
}
|
||||
|
||||
func (c *blueGreenTx) Commit() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.rollbackOrCommitDone {
|
||||
return nil
|
||||
}
|
||||
c.rollbackOrCommitDone = true
|
||||
c.checkDatabase()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
|
@ -168,6 +227,13 @@ func (c *blueGreenTx) RoaringBitmap(index, field, view string, shard uint64) (*r
|
|||
_, _ = a, errA
|
||||
b, errB := c.b.RoaringBitmap(index, field, view, shard)
|
||||
compareErrors(errA, errB)
|
||||
|
||||
slcA := a.Slice()
|
||||
slcB := b.Slice()
|
||||
if !reflect.DeepEqual(slcA, slcB) {
|
||||
panic("blueGreenTx.RoaringBitmap() returning different roaring.Bitmaps!")
|
||||
}
|
||||
|
||||
return b, errB
|
||||
}
|
||||
|
||||
|
|
@ -205,6 +271,14 @@ func (c *blueGreenTx) PutContainer(index, field, view string, shard uint64, key
|
|||
|
||||
func (c *blueGreenTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
|
||||
c.checker.see(index, field, view, shard)
|
||||
|
||||
// these are the first port of call for debugging, so we leave them in.
|
||||
// ================== begin save comments.
|
||||
//c.checkDatabase()
|
||||
//vv("got past database check at TOP of ImportRoaringBits")
|
||||
//c.Dump()
|
||||
//vv("done with top dump; clear=%v", clear)
|
||||
// ================== end save comments.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see ImportRoaringBits() panic '%v' at '%v'", r, stack())
|
||||
|
|
@ -214,9 +288,9 @@ func (c *blueGreenTx) ImportRoaringBits(index, field, view string, shard uint64,
|
|||
|
||||
// remember where the iterator started, so we can replay it a second time.
|
||||
rit2 := rit.Clone()
|
||||
panicOn(err)
|
||||
|
||||
changedA, rowSetA, errA := c.a.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize, data)
|
||||
|
||||
changedB, rowSetB, errB := c.b.ImportRoaringBits(index, field, view, shard, rit2, clear, log, rowSize, data)
|
||||
|
||||
if len(data) == 0 {
|
||||
|
|
@ -240,7 +314,7 @@ func (c *blueGreenTx) ImportRoaringBits(index, field, view string, shard uint64,
|
|||
}
|
||||
}
|
||||
compareErrors(errA, errB)
|
||||
|
||||
c.checkDatabase()
|
||||
return changedB, rowSetB, errB
|
||||
}
|
||||
|
||||
|
|
@ -344,50 +418,101 @@ func (c *blueGreenTx) ContainerIterator(index, field, view string, shard uint64,
|
|||
panic(r)
|
||||
}
|
||||
}()
|
||||
// TODO: need to return a blueGreenIterator too, that does close/next operations on both A and B.
|
||||
|
||||
ait, afound, errA := c.a.ContainerIterator(index, field, view, shard, firstRoaringContainerKey)
|
||||
_, _, _ = ait, afound, errA
|
||||
|
||||
bit, bfound, errB := c.b.ContainerIterator(index, field, view, shard, firstRoaringContainerKey)
|
||||
|
||||
compareErrors(errA, errB)
|
||||
if errA != nil {
|
||||
ait.Close() // don't leak it.
|
||||
// INVAR: errA == errB, so only need to check one.
|
||||
if errB != nil {
|
||||
// RoaringTx can return an iterator and an error, so be sure Close it we have it.
|
||||
if ait != nil {
|
||||
ait.Close()
|
||||
}
|
||||
if bit != nil {
|
||||
bit.Close()
|
||||
}
|
||||
return nil, bfound, errB
|
||||
}
|
||||
return bit, bfound, errB
|
||||
// INVAR: errA == errB == nil
|
||||
bgi := NewBlueGreenIterator(c, ait, bit)
|
||||
return bgi, bfound, errB
|
||||
}
|
||||
|
||||
func NewBlueGreenIterator(tx *blueGreenTx, ait, bit roaring.ContainerIterator) *blueGreenIterator {
|
||||
return &blueGreenIterator{
|
||||
tx: tx,
|
||||
as: tx.as,
|
||||
bs: tx.bs,
|
||||
ait: ait,
|
||||
bit: bit,
|
||||
}
|
||||
}
|
||||
|
||||
type blueGreenIterator struct {
|
||||
tx *blueGreenTx
|
||||
as string
|
||||
bs string
|
||||
|
||||
ait roaring.ContainerIterator
|
||||
bit roaring.ContainerIterator
|
||||
}
|
||||
|
||||
func (bgi *blueGreenIterator) Next() bool {
|
||||
na := bgi.ait.Next()
|
||||
nb := bgi.bit.Next()
|
||||
if na != nb {
|
||||
panic(fmt.Sprintf("na=%v(%v) != nb(%v)=%v", na, bgi.as, bgi.bs, nb))
|
||||
}
|
||||
return nb
|
||||
}
|
||||
|
||||
func (bgi *blueGreenIterator) Value() (uint64, *roaring.Container) {
|
||||
ka, ca := bgi.ait.Value()
|
||||
kb, cb := bgi.bit.Value()
|
||||
if ka != kb {
|
||||
panic(fmt.Sprintf("ka=%v != kb=%v", ka, kb))
|
||||
}
|
||||
err := ca.BitwiseCompare(cb)
|
||||
panicOn(err)
|
||||
return kb, cb
|
||||
}
|
||||
func (bgi *blueGreenIterator) Close() {
|
||||
bgi.ait.Close()
|
||||
bgi.bit.Close()
|
||||
}
|
||||
|
||||
// ForEach is read-only on the database, and so we only pass through to B.
|
||||
// Avoids the side-effects of calling fn too many times.
|
||||
func (c *blueGreenTx) ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error {
|
||||
c.checker.see(index, field, view, shard)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see ForEach() panic '%v' at '%v'", r, stack())
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
errA := c.a.ForEach(index, field, view, shard, fn)
|
||||
_ = errA
|
||||
errB := c.b.ForEach(index, field, view, shard, fn)
|
||||
_ = errB
|
||||
return c.b.ForEach(index, field, view, shard, fn)
|
||||
|
||||
compareErrors(errA, errB)
|
||||
return errB
|
||||
}
|
||||
|
||||
// ForEachRange cannot change the database, and we also can't control
|
||||
// the side effects of the fn() calls. So we only pass through to B, not A.
|
||||
// No checker.see() is needed as well, because we are read-only.
|
||||
func (c *blueGreenTx) ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error {
|
||||
c.checker.see(index, field, view, shard)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see ForEachRange() panic '%v' at '%v'", r, stack())
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
errA := c.a.ForEachRange(index, field, view, shard, start, end, fn)
|
||||
_ = errA
|
||||
errB := c.b.ForEachRange(index, field, view, shard, start, end, fn)
|
||||
_ = errB
|
||||
|
||||
compareErrors(errA, errB)
|
||||
return errB
|
||||
// calling fn will have side effects; can only call it the right number of times.
|
||||
// so can't do this.
|
||||
// errA := c.a.ForEachRange(index, field, view, shard, start, end, fn)
|
||||
return c.b.ForEachRange(index, field, view, shard, start, end, fn)
|
||||
}
|
||||
|
||||
func (c *blueGreenTx) Count(index, field, view string, shard uint64) (uint64, error) {
|
||||
|
|
@ -459,6 +584,7 @@ func (c *blueGreenTx) CountRange(index, field, view string, shard uint64, start,
|
|||
c.checker.see(index, field, view, shard)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.Dump()
|
||||
AlwaysPrintf("see CountRange() panic '%v' at '%v'", r, stack())
|
||||
panic(r)
|
||||
}
|
||||
|
|
@ -467,7 +593,7 @@ func (c *blueGreenTx) CountRange(index, field, view string, shard uint64, start,
|
|||
b, errB := c.b.CountRange(index, field, view, shard, start, end)
|
||||
|
||||
if a != b {
|
||||
panic(fmt.Sprintf("a = %v, but b = %v", a, b))
|
||||
panic(fmt.Sprintf("a(%v) = %v, but b(%v) = %v", c.as, a, c.bs, b))
|
||||
}
|
||||
|
||||
compareErrors(errA, errB)
|
||||
|
|
@ -495,18 +621,35 @@ func (c *blueGreenTx) RoaringBitmapReader(index, field, view string, shard uint6
|
|||
c.checker.see(index, field, view, shard)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see OffsetRange() panic '%v' at '%v'", r, stack())
|
||||
c.Dump()
|
||||
AlwaysPrintf("see RoaringBitmapReader() panic '%v' at '%v'", r, stack())
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
rcA, szA, errA := c.a.RoaringBitmapReader(index, field, view, shard, fragmentPathForRoaring)
|
||||
rcB, szB, errB := c.b.RoaringBitmapReader(index, field, view, shard, fragmentPathForRoaring)
|
||||
if szA != szB {
|
||||
panic(fmt.Sprintf("szA = %v, but szB = %v", szA, szB))
|
||||
}
|
||||
|
||||
compareErrors(errA, errB)
|
||||
return &MultiReaderB{a: rcA, b: rcB}, szB, errB
|
||||
|
||||
// We are seeing Roaring vs Badger size differences on
|
||||
// server/ test TestClusterResize_AddNode/ContinuousShards,
|
||||
// so turn off the szA vs szB checks and MutliReaderB use. But keep them if we want to
|
||||
// check RBF vs Badger for byte-for-byte compatiblity (we
|
||||
// suspect the ops log or optimized bitmaps are accounting for the difference).
|
||||
sizeMustMatch := false
|
||||
if sizeMustMatch {
|
||||
if szA != szB {
|
||||
panic(fmt.Sprintf("szA(%v) = %v, but szB(%v) = %v; fragmentPathForRoaring='%v'", c.as, szA, c.bs, szB, fragmentPathForRoaring))
|
||||
}
|
||||
return &MultiReaderB{a: rcA, b: rcB}, szB, errB
|
||||
} else {
|
||||
// one db won't get data if we do
|
||||
//return &MultiReaderB{a: rcA, b: rcB, allowSizeVariation: true}, szB, errB
|
||||
_, _ = szA, errA
|
||||
rcA.Close()
|
||||
return rcB, szB, errB
|
||||
}
|
||||
}
|
||||
|
||||
func (c *blueGreenTx) SliceOfShards(index, field, view, optionalViewPath string) (sliceOfShards []uint64, err error) {
|
||||
|
|
@ -514,6 +657,7 @@ func (c *blueGreenTx) SliceOfShards(index, field, view, optionalViewPath string)
|
|||
//c.checker.see(index, field, view, shard) // don't have shard.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
c.Dump()
|
||||
AlwaysPrintf("see SliceOfShards() panic '%v' at '%v'", r, stack())
|
||||
panic(r)
|
||||
}
|
||||
|
|
@ -536,30 +680,38 @@ func (c *blueGreenTx) SliceOfShards(index, field, view, optionalViewPath string)
|
|||
}
|
||||
for _, kb := range slcB {
|
||||
if !ma[kb] {
|
||||
panic(fmt.Sprintf("blueGreenTx SliceOfShards diference! B had %v, but A did not; in the SliceOfShards returned slice.", kb))
|
||||
c.Dump()
|
||||
panic(fmt.Sprintf("blueGreenTx SliceOfShards diference! B(%v) had %v, but A(%v) did not. cpa='%#v'; cpb='%#v'; in the SliceOfShards returned slice.", c.bs, kb, c.as, cpa, cpb))
|
||||
}
|
||||
delete(ma, kb)
|
||||
}
|
||||
if len(ma) != 0 {
|
||||
for _, firstDifference := range ma {
|
||||
panic(fmt.Sprintf("blueGreenTx SliceOfShards diference! A had %v, but B did not; in the SliceOfShards returned slice.", firstDifference))
|
||||
for firstDifference := range ma {
|
||||
panic(fmt.Sprintf("blueGreenTx SliceOfShards diference! A(%v) had %v, but B(%v) did not. cpa='%#v'; cpb='%#v'; in the SliceOfShards returned slice.", c.as, firstDifference, c.bs, cpa, cpb))
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("blueGreenTx SliceOfShards diference \n slcA='%#v';\n slcB='%#v';\n", cpa, cpb))
|
||||
panic(fmt.Sprintf("blueGreenTx SliceOfShards diference \n slcA(%v)='%#v';\n slcB(%v)='%#v';\n", c.as, cpa, c.bs, cpb))
|
||||
}
|
||||
return slcB, errB
|
||||
}
|
||||
|
||||
// MultiReaderB is returned by RoaringBitmapReader. It verifies
|
||||
// that identical byte streams are read from its two members.
|
||||
type MultiReaderB struct {
|
||||
a io.ReadCloser
|
||||
b io.ReadCloser
|
||||
|
||||
allowSizeVariation bool
|
||||
}
|
||||
|
||||
// TODO(jea): test this for accuracy/correctness.
|
||||
// Read implements the standard io.Reader method. It panics
|
||||
// if "a" and "b" have even one byte different in their reads.
|
||||
func (m *MultiReaderB) Read(p []byte) (nB int, errB error) {
|
||||
nB, errB = m.b.Read(p)
|
||||
p2 := make([]byte, nB)
|
||||
// discard the exact same amount from A
|
||||
|
||||
// read (and discard after comparing for equality) the exact same amount from A.
|
||||
|
||||
// ReadAtLeast reads from r into buf until it has read at least
|
||||
// min bytes. It returns the number of bytes copied and an error
|
||||
// if fewer bytes were read. The error is EOF only if no bytes
|
||||
|
|
@ -569,11 +721,18 @@ func (m *MultiReaderB) Read(p []byte) (nB int, errB error) {
|
|||
// return, n >= min if and only if err == nil. If r returns
|
||||
// an error having read at least min bytes, the error is dropped.
|
||||
nA, errA := io.ReadAtLeast(m.a, p2, nB)
|
||||
if errA == io.ErrUnexpectedEOF {
|
||||
panic(fmt.Sprintf("MultiReaderB got ErrUnexpectedEOF: read %v bytes from B, but could only read %v bytes for A", nB, nA))
|
||||
}
|
||||
if nA != nB {
|
||||
panic(fmt.Sprintf("MultiReaderB read %v bytes from B, but could only read %v bytes for A", nB, nA))
|
||||
|
||||
if !m.allowSizeVariation {
|
||||
if errA == io.ErrUnexpectedEOF {
|
||||
panic(fmt.Sprintf("MultiReaderB got ErrUnexpectedEOF: read %v bytes from B, but could only read %v bytes for A", nB, nA))
|
||||
}
|
||||
if nA != nB {
|
||||
panic(fmt.Sprintf("MultiReaderB read %v bytes from B, but could only read %v bytes for A", nB, nA))
|
||||
}
|
||||
cmp := bytes.Compare(p[:nB], p2[:nB])
|
||||
if cmp != 0 {
|
||||
panic(fmt.Sprintf("MultiReaderB reads p and p2 (cmp= %v) differed.", cmp)) // \np ='%v'; \np2 ='%v'", cmp, string(p[:nB]), string(p2[:nA])))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -586,11 +745,20 @@ func (m *MultiReaderB) Close() error {
|
|||
// blueGreenChecker is used
|
||||
type blueGreenChecker struct {
|
||||
visited map[string]map[string]map[string]map[uint64]struct{}
|
||||
done bool
|
||||
|
||||
// lock mu when using visited.
|
||||
// otherwise concurrent map writes on TestAPI_Import/RowIDColumnKey
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// see would mark a thing as seen.
|
||||
func (b *blueGreenChecker) see(index, field, view string, shard uint64) {
|
||||
// keep this next Printf. Useful to see the sequence of Tx operations.
|
||||
//fmt.Printf("blueGreenTx.%v\n", Caller(1))
|
||||
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if b.visited == nil {
|
||||
b.visited = make(map[string]map[string]map[string]map[uint64]struct{})
|
||||
}
|
||||
|
|
@ -617,9 +785,5 @@ func (b *blueGreenChecker) see(index, field, view string, shard uint64) {
|
|||
// that Rollback can be called after Commit without repeating
|
||||
// the check.
|
||||
func (b *blueGreenChecker) seen() map[string]map[string]map[string]map[uint64]struct{} {
|
||||
if b.done {
|
||||
return nil
|
||||
}
|
||||
b.done = true
|
||||
return b.visited
|
||||
}
|
||||
|
|
|
|||
91
bluegreentx_test.go
Normal file
91
bluegreentx_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2020 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 (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
cryrand "crypto/rand"
|
||||
)
|
||||
|
||||
func TestMultiReaderB(t *testing.T) {
|
||||
// MultiReaderB should read identical chunks of bytes from both its "a" and "b"
|
||||
// member io.Readers, else it should panic. This should hold for
|
||||
// varying sizes of inputs.
|
||||
|
||||
for n := 1 << 5; n < (1 << 18); n = n*2 - 13 {
|
||||
src := io.LimitReader(cryrand.Reader, int64(n))
|
||||
|
||||
a := make([]byte, n)
|
||||
nr := 0
|
||||
for nr < n {
|
||||
na, err := src.Read(a)
|
||||
panicOn(err)
|
||||
nr += na
|
||||
}
|
||||
if nr != n {
|
||||
panic("short read")
|
||||
}
|
||||
|
||||
b := make([]byte, n)
|
||||
copy(b, a)
|
||||
if !bytes.Equal(a, b) {
|
||||
panic("test prep failed")
|
||||
}
|
||||
|
||||
m := &MultiReaderB{
|
||||
a: ioutil.NopCloser(bytes.NewBuffer(a)),
|
||||
b: ioutil.NopCloser(bytes.NewBuffer(b)),
|
||||
}
|
||||
|
||||
// should not trigger the internal panic of MultiReadB
|
||||
ncp, err := io.Copy(ioutil.Discard, m)
|
||||
panicOn(err)
|
||||
if ncp != int64(n) {
|
||||
panic("short copy")
|
||||
}
|
||||
|
||||
for victim := 0; victim < n; victim += 7 {
|
||||
|
||||
copy(b, a)
|
||||
if victim%2 == 0 {
|
||||
// corrupt b
|
||||
b[victim] = (b[victim] + 1) % 255
|
||||
} else {
|
||||
// corrupt a
|
||||
a[victim] = (a[victim] + 1) % 255
|
||||
}
|
||||
m = &MultiReaderB{
|
||||
a: ioutil.NopCloser(bytes.NewBuffer(a)),
|
||||
b: ioutil.NopCloser(bytes.NewBuffer(b)),
|
||||
}
|
||||
helperShouldPanicOnCopy(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func helperShouldPanicOnCopy(m *MultiReaderB) {
|
||||
// differences in bytes read should be noticed
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
panic("expected panic on byte difference but didn't see it")
|
||||
}
|
||||
}()
|
||||
_, _ = io.Copy(ioutil.Discard, m)
|
||||
}
|
||||
|
|
@ -62,6 +62,10 @@ func (c *catcherTx) ImportRoaringBits(index, field, view string, shard uint64, r
|
|||
return c.b.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize, data)
|
||||
}
|
||||
|
||||
func (c *catcherTx) Dump() {
|
||||
c.b.Dump()
|
||||
}
|
||||
|
||||
func (c *catcherTx) Readonly() bool {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
|
|
|
|||
|
|
@ -854,7 +854,6 @@ func TestCluster_ResizeStates(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
} else if !bytes.Equal(chksum, node0Checksum) {
|
||||
t.Fatalf("expected standard view checksum to match: %x - %x", chksum, node0Checksum)
|
||||
// badger red: TestCluster_ResizeStates/Multiple_nodes,_with_data: cluster_internal_test.go:841: expected standard view checksum to match: ef46db3751d8e999 - fad4de25ee696ca0
|
||||
}
|
||||
|
||||
// Close TestCluster.
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"io/ioutil"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -513,6 +514,12 @@ func TestExecutor_Execute_Count(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func roaringOnlyTest(t *testing.T) {
|
||||
if os.Getenv("PILOSA_TXSRC") != "roaring" {
|
||||
t.Skip("skip for everything but roaring")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure a set query can be executed.
|
||||
func TestExecutor_Execute_Set(t *testing.T) {
|
||||
t.Run("RowIDColumnID", func(t *testing.T) {
|
||||
|
|
@ -521,7 +528,7 @@ func TestExecutor_Execute_Set(t *testing.T) {
|
|||
cmd := cluster[0]
|
||||
holder := cmd.Server.Holder()
|
||||
hldr := test.Holder{Holder: holder}
|
||||
hldr.SetBit("i", "f", 1, 0)
|
||||
hldr.SetBit("i", "f", 1, 0) // creates and commits a Tx internally.
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
hldr.ClearBit("i", "f", 11, 1)
|
||||
|
|
@ -582,10 +589,10 @@ func TestExecutor_Execute_Set(t *testing.T) {
|
|||
cmd := cluster[0]
|
||||
holder := cmd.Server.Holder()
|
||||
hldr := test.Holder{Holder: holder}
|
||||
index := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{Keys: true})
|
||||
idx := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{Keys: true})
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
hldr.SetBit("i", "f", 1, 0)
|
||||
hldr.SetBit("i", "f", 1, 0) // creates and Commits a Tx internally.
|
||||
if n := hldr.Row("i", "f", 11).Count(); n != 0 {
|
||||
t.Fatalf("unexpected row count: %d", n)
|
||||
}
|
||||
|
|
@ -619,14 +626,16 @@ func TestExecutor_Execute_Set(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("ErrInvalidColValueType", func(t *testing.T) {
|
||||
if err := index.DeleteField("f"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := index.CreateField("f", pilosa.OptFieldTypeDefault()); err != nil {
|
||||
|
||||
if err := idx.DeleteField("f"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := cmd.API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Set(2.1, f=1)`}); err == nil || strings.Contains(err.Error(), `column value must be a string or non-negative integer`) {
|
||||
if _, err := idx.CreateField("f", pilosa.OptFieldTypeDefault()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := cmd.API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Set(2.1, f=1)`}); err == nil || !strings.Contains(err.Error(), "parse error") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
@ -637,9 +646,9 @@ func TestExecutor_Execute_Set(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("ErrInvalidRowValueType", func(t *testing.T) {
|
||||
index := hldr.MustCreateIndexIfNotExists("inokey", pilosa.IndexOptions{})
|
||||
if _, err := index.CreateField("f", pilosa.OptFieldTypeDefault(), pilosa.OptFieldKeys()); err != nil {
|
||||
t.Run("ErrInvalidRowValueType", func(t *testing.T) { // // failing under badger_roaring
|
||||
idx := hldr.MustCreateIndexIfNotExists("inokey", pilosa.IndexOptions{})
|
||||
if _, err := idx.CreateField("f", pilosa.OptFieldTypeDefault(), pilosa.OptFieldKeys()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := cmd.API.Query(context.Background(), &pilosa.QueryRequest{Index: "inokey", Query: `Set(2, f=1.2)`}); err == nil || !strings.Contains(err.Error(), "row value must be a string or non-negative integer") {
|
||||
|
|
|
|||
|
|
@ -611,22 +611,25 @@ func TestBSIGroup_importValue(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
tx := f.idx.Txf.NewTx(Txo{Write: writable, Index: f.idx, Field: f.Field})
|
||||
defer tx.Rollback()
|
||||
// can't do this, we are in a loop, not a function:
|
||||
// defer tx.Rollback()
|
||||
|
||||
if err := f.importValue(tx, tt.columnIDs, tt.values, options); err != nil {
|
||||
t.Fatalf("test %d, importing values: %s", i, err.Error())
|
||||
}
|
||||
|
||||
panicOn(tx.Commit())
|
||||
|
||||
tx = f.idx.Txf.NewTx(Txo{Write: !writable, Index: f.idx, Field: f.Field})
|
||||
defer tx.Rollback()
|
||||
// no, same reason as above: defer tx.Rollback()
|
||||
|
||||
if row, err := f.Range(tx, f.name, pql.EQ, tt.checkVal); err != nil {
|
||||
t.Fatalf("test %d, getting range: %s", i, err.Error())
|
||||
} else if !reflect.DeepEqual(row.Columns(), tt.expCols) {
|
||||
t.Fatalf("test %d, expected columns: %v, but got: %v", i, tt.expCols, row.Columns())
|
||||
}
|
||||
}
|
||||
tx.Rollback()
|
||||
} // loop
|
||||
}
|
||||
|
||||
func TestIntField_MinMaxForShard(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -1771,6 +1771,8 @@ func TestFragment_LRUCache_Persistence(t *testing.T) {
|
|||
t.Fatalf("unexpected cache len: %d", cache.Len())
|
||||
}
|
||||
|
||||
panicOn(tx.Commit())
|
||||
|
||||
// Reopen the fragment.
|
||||
if err := f.Reopen(); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -1847,16 +1849,27 @@ func TestFragment_RankCache_Persistence(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func roaringOnlyTest(t *testing.T) {
|
||||
if os.Getenv("PILOSA_TXSRC") != "roaring" {
|
||||
t.Skip("skip for everything but roaring")
|
||||
}
|
||||
}
|
||||
|
||||
func roaringOnlyBenchmark(b *testing.B) {
|
||||
if os.Getenv("PILOSA_TXSRC") != "roaring" {
|
||||
b.Skip("skip for everything but roaring")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure a fragment can be copied to another fragment.
|
||||
func TestFragment_WriteTo_ReadFrom(t *testing.T) {
|
||||
skipForRBF(t)
|
||||
roaringOnlyTest(t)
|
||||
|
||||
f0, idx := mustOpenFragment("i", "f", viewStandard, 0, "")
|
||||
_ = idx
|
||||
defer f0.Clean(t)
|
||||
|
||||
// Obtain transaction.
|
||||
tx := idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f0})
|
||||
tx := f0.txTestingOnly
|
||||
defer tx.Rollback()
|
||||
|
||||
// Set and then clear bits on the fragment.
|
||||
|
|
@ -2032,6 +2045,8 @@ func TestFragment_Zero_Tanimoto(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFragment_Snapshot_Run(t *testing.T) {
|
||||
roaringOnlyTest(t)
|
||||
|
||||
f, idx := mustOpenFragment("i", "f", viewStandard, 0, "")
|
||||
_ = idx
|
||||
defer f.Clean(t)
|
||||
|
|
@ -3094,10 +3109,7 @@ func BenchmarkImportRoaringConcurrent(b *testing.B) {
|
|||
}
|
||||
}
|
||||
func BenchmarkImportRoaringUpdateConcurrent(b *testing.B) {
|
||||
skipForBadger := os.Getenv("PILOSA_TXSRC") == "badger"
|
||||
if skipForBadger {
|
||||
b.Skip("skip for badger")
|
||||
}
|
||||
roaringOnlyBenchmark(b)
|
||||
if testing.Short() {
|
||||
b.SkipNow()
|
||||
}
|
||||
|
|
@ -3385,7 +3397,7 @@ func BenchmarkImportRoaringIntoLargeFragment(b *testing.B) {
|
|||
//nf, idx := mustOpenFragmentFlags(index, field, view string, shard uint64, cacheType string, flags byte)
|
||||
|
||||
th := newTestHolder()
|
||||
idx := fragTestMustOpenIndex("i", th, IndexOptions{})
|
||||
idx := fragTestMustOpenIndex(filepath.Dir(fi.Name()), "i", th, IndexOptions{})
|
||||
if th.NeedsSnapshot() {
|
||||
th.SnapshotQueue = newSnapshotQueue(1, 1, nil)
|
||||
}
|
||||
|
|
@ -3663,12 +3675,8 @@ func newTestHolder() *Holder {
|
|||
}
|
||||
|
||||
// fragTestMustOpenIndex returns a new, opened index at a temporary path. Panic on error.
|
||||
func fragTestMustOpenIndex(index string, holder *Holder, opt IndexOptions) *Index {
|
||||
path, err := ioutil.TempDir(*TempDir, "pilosa-index-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
holder.Path = path
|
||||
func fragTestMustOpenIndex(holderDir, index string, holder *Holder, opt IndexOptions) *Index {
|
||||
holder.Path = holderDir
|
||||
holder.mu.Lock()
|
||||
idx, err := holder.createIndex(index, opt)
|
||||
holder.mu.Unlock()
|
||||
|
|
@ -3685,23 +3693,24 @@ func fragTestMustOpenIndex(index string, holder *Holder, opt IndexOptions) *Inde
|
|||
|
||||
// mustOpenFragment returns a new instance of Fragment with a temporary path.
|
||||
func mustOpenFragmentFlags(index, field, view string, shard uint64, cacheType string, flags byte) (*fragment, *Index) {
|
||||
file, err := ioutil.TempFile(*TempDir, "pilosa-fragment-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
holderDir, err := ioutil.TempDir(*TempDir, "holder-dir")
|
||||
panicOn(err)
|
||||
|
||||
if cacheType == "" {
|
||||
cacheType = DefaultCacheType
|
||||
}
|
||||
|
||||
// new:
|
||||
th := newTestHolder()
|
||||
idx := fragTestMustOpenIndex(index, th, IndexOptions{})
|
||||
idx := fragTestMustOpenIndex(holderDir, index, th, IndexOptions{})
|
||||
if th.NeedsSnapshot() {
|
||||
th.SnapshotQueue = newSnapshotQueue(1, 1, nil)
|
||||
}
|
||||
f := newFragment(th, file.Name(), index, field, view, shard, flags)
|
||||
|
||||
fragDir := fmt.Sprintf("%v/%v/views/%v/fragments/", idx.path, field, view)
|
||||
panicOn(os.MkdirAll(fragDir, 0777))
|
||||
fragPath := fragDir + fmt.Sprintf("%v", shard)
|
||||
f := newFragment(th, fragPath, index, field, view, shard, flags)
|
||||
|
||||
tx := idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f})
|
||||
f.txTestingOnly = tx
|
||||
|
|
@ -3811,6 +3820,10 @@ func TestFragment_RowsIteration(t *testing.T) {
|
|||
} else if _, err := f.setBit(tx, 2, 166000); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
panicOn(tx.Commit())
|
||||
|
||||
tx = idx.Txf.NewTx(Txo{Write: !writable, Index: idx, Fragment: f})
|
||||
defer tx.Rollback()
|
||||
|
||||
ids, err := f.rows(context.Background(), tx, 0)
|
||||
if err != nil {
|
||||
|
|
@ -4421,11 +4434,7 @@ func TestFragmentRowIterator_WithTxCommit(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUnionInPlaceMapped(t *testing.T) {
|
||||
|
||||
skipForBadger := os.Getenv("PILOSA_TXSRC") == "badger"
|
||||
if skipForBadger {
|
||||
t.Skip("skip for badger")
|
||||
}
|
||||
roaringOnlyTest(t)
|
||||
|
||||
f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeNone)
|
||||
// note: clean has to be deferred first, because it has to run with
|
||||
|
|
@ -5112,6 +5121,8 @@ func TestFragmentBSISigned(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestImportClearRestart(t *testing.T) {
|
||||
roaringOnlyTest(t)
|
||||
|
||||
tests := []struct {
|
||||
rows []uint64
|
||||
cols []uint64
|
||||
|
|
@ -5272,7 +5283,6 @@ func TestImportClearRestart(t *testing.T) {
|
|||
|
||||
err = f3.Open()
|
||||
if err != nil {
|
||||
// TODO(jea): might be a flaky test? when run from make test
|
||||
t.Fatalf("opening f3: %v", err)
|
||||
}
|
||||
defer f3.Clean(t)
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ import (
|
|||
)
|
||||
|
||||
func TestHolder_Open(t *testing.T) {
|
||||
skipForBadger := os.Getenv("PILOSA_TXSRC") == "badger"
|
||||
skipForRBF := os.Getenv("PILOSA_TXSRC") == "rbf"
|
||||
|
||||
t.Run("ErrIndexName", func(t *testing.T) {
|
||||
h := test.MustOpenHolder()
|
||||
|
|
@ -168,11 +166,8 @@ func TestHolder_Open(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("ErrFragmentStoragePermission", func(t *testing.T) {
|
||||
if skipForBadger {
|
||||
t.Skip("skipping for badger")
|
||||
} else if skipForRBF {
|
||||
t.Skip("skipping for rbf")
|
||||
}
|
||||
roaringOnlyTest(t)
|
||||
|
||||
if os.Geteuid() == 0 {
|
||||
t.Skip("Skipping permissions test since user is root.")
|
||||
}
|
||||
|
|
@ -209,11 +204,7 @@ func TestHolder_Open(t *testing.T) {
|
|||
}
|
||||
})
|
||||
t.Run("ErrFragmentStorageCorrupt", func(t *testing.T) {
|
||||
if skipForBadger {
|
||||
t.Skip("skipping for badger")
|
||||
} else if skipForRBF {
|
||||
t.Skip("skipping for rbf")
|
||||
}
|
||||
roaringOnlyTest(t)
|
||||
|
||||
h := test.MustOpenHolder()
|
||||
defer h.Close()
|
||||
|
|
@ -247,11 +238,7 @@ func TestHolder_Open(t *testing.T) {
|
|||
}
|
||||
})
|
||||
t.Run("ErrFragmentStorageRecoverable", func(t *testing.T) {
|
||||
if skipForBadger {
|
||||
t.Skip("skipping for badger")
|
||||
} else if skipForRBF {
|
||||
t.Skip("skipping for rbf")
|
||||
}
|
||||
roaringOnlyTest(t)
|
||||
|
||||
h := test.MustOpenHolder()
|
||||
defer h.Close()
|
||||
|
|
@ -594,7 +581,6 @@ func TestHolderSyncer_BlockIteratorLimits(t *testing.T) {
|
|||
|
||||
// Leave the third replica empty to force a block merge.
|
||||
//
|
||||
|
||||
err = c[0].Server.SyncData()
|
||||
if err != nil {
|
||||
t.Fatalf("syncing node 0: %v", err)
|
||||
|
|
|
|||
15
index.go
15
index.go
|
|
@ -621,9 +621,8 @@ func (i *Index) DeleteField(name string) error {
|
|||
return errors.Wrap(err, "closing")
|
||||
}
|
||||
|
||||
// Delete field directory.
|
||||
if err := os.RemoveAll(i.fieldPath(name)); err != nil {
|
||||
return errors.Wrap(err, "removing directory")
|
||||
if err := i.Txf.DeleteFieldFromStore(i.name, name, i.fieldPath(name)); err != nil {
|
||||
return errors.Wrap(err, "Txf.DeleteFieldFromStore")
|
||||
}
|
||||
|
||||
// If the field being deleted is the existence field,
|
||||
|
|
@ -700,3 +699,13 @@ type importValueData struct {
|
|||
func FormatQualifiedIndexName(index string) string {
|
||||
return fmt.Sprintf("%s\x00", index)
|
||||
}
|
||||
|
||||
// Dump prints to stdout the contents of the roaring Containers
|
||||
// stored in idx. Mostly for debugging.
|
||||
func (idx *Index) Dump(label string) {
|
||||
fileline := FileLine(2)
|
||||
tx := idx.Txf.NewTx(Txo{Write: !writable, Index: idx})
|
||||
defer tx.Rollback()
|
||||
fmt.Printf("\n%v Index.Dump('%v') for index '%v':\n", fileline, label, idx.name)
|
||||
tx.Dump()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4404,3 +4404,40 @@ func TestUnionRunRunInPlaceBitwiseCompare(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneRoaringIterator(t *testing.T) {
|
||||
|
||||
ca := NewContainerArray([]uint16{1, 10, 100, 1000})
|
||||
ba := NewFileBitmap()
|
||||
ba.Containers.Put(0, ca)
|
||||
ba.Containers.Put(10, ca)
|
||||
ba.Containers.Put(101, ca)
|
||||
ba.Containers.Put(10001, ca)
|
||||
var buf bytes.Buffer
|
||||
_, err := ba.WriteTo(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("error writing: %v", err)
|
||||
}
|
||||
|
||||
itr, err := NewRoaringIterator(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("error NewRoaringIterator(buf.Bytes()): %v", err)
|
||||
}
|
||||
|
||||
itr2 := itr.Clone()
|
||||
|
||||
var keys []uint64
|
||||
for itrKey, synthC := itr.NextContainer(); synthC != nil; itrKey, synthC = itr.NextContainer() {
|
||||
keys = append(keys, itrKey)
|
||||
_ = synthC
|
||||
}
|
||||
|
||||
var keys2 []uint64
|
||||
for itrKey, synthC := itr2.NextContainer(); synthC != nil; itrKey, synthC = itr2.NextContainer() {
|
||||
keys2 = append(keys2, itrKey)
|
||||
_ = synthC
|
||||
}
|
||||
if !reflect.DeepEqual(keys, keys2) {
|
||||
t.Fatalf("keys != keys2. keys='%#v'; keys2='%#v'", keys, keys2)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
tx.go
44
tx.go
|
|
@ -198,9 +198,12 @@ type Tx interface {
|
|||
|
||||
// SliceOfShards returns all of the shards for the specified index, field, view triple.
|
||||
// Use within pilosa supposes a new read-only transaction was created just
|
||||
// for the SliceOfShards() call. The original Roaring version is the only
|
||||
// for the SliceOfShards() call. The legacy RoaringTx version is the only
|
||||
// one that needs optionalViewPath; any other Tx implementation can ignore that.
|
||||
SliceOfShards(index, field, view, optionalViewPath string) (sliceOfShards []uint64, err error)
|
||||
|
||||
// Dump is for debugging, what does this Tx see as its database?
|
||||
Dump()
|
||||
}
|
||||
|
||||
// TxStore has operations that will create and commit multiple
|
||||
|
|
@ -222,6 +225,8 @@ type TxStore interface {
|
|||
//
|
||||
DeleteFragment(index, field, view string, shard uint64, frag interface{}) error
|
||||
|
||||
DeleteField(index, field string) error
|
||||
|
||||
// Close shuts down the database.
|
||||
Close() error
|
||||
}
|
||||
|
|
@ -269,6 +274,19 @@ func (mtx *MultiTx) Type() string {
|
|||
return RoaringTxn
|
||||
}
|
||||
|
||||
// debugging, what does this Tx see as its database?
|
||||
func (mtx *MultiTx) Dump() {
|
||||
mtx.mu.Lock()
|
||||
defer mtx.mu.Unlock()
|
||||
if len(mtx.txs) == 0 {
|
||||
return
|
||||
}
|
||||
for _, tx := range mtx.txs {
|
||||
tx.Dump()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (mtx *MultiTx) SliceOfShards(index, field, view, optionalViewPath string) (sliceOfShards []uint64, err error) {
|
||||
tx, err := mtx.txNoShard(index)
|
||||
panicOn(err)
|
||||
|
|
@ -516,10 +534,14 @@ type RoaringTx struct {
|
|||
fragment *fragment
|
||||
}
|
||||
|
||||
func (mtx *RoaringTx) Type() string {
|
||||
func (tx *RoaringTx) Type() string {
|
||||
return RoaringTxn
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) Dump() {
|
||||
fmt.Printf("%v\n", tx.Index.StringifiedRoaringKeys())
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) UseRowCache() bool {
|
||||
return true
|
||||
}
|
||||
|
|
@ -548,6 +570,7 @@ func (tx *RoaringTx) SliceOfShards(index, field, view, optionalViewPath string)
|
|||
// Parse filename into integer.
|
||||
shard, err := strconv.ParseUint(filepath.Base(fi.Name()), 10, 64)
|
||||
if err != nil {
|
||||
//AlwaysPrintf("WARNING: couldn't use non-integer file as shard in index/field/view %s/%s/%s: %s", index, field, view, fi.Name())
|
||||
//v.holder.Logger.Debugf("WARNING: couldn't use non-integer file as shard in index/field/view %s/%s/%s: %s", v.index, v.field, v.name, fi.Name())
|
||||
continue
|
||||
}
|
||||
|
|
@ -572,7 +595,6 @@ func (tx *RoaringTx) NewTxIterator(index, field, view string, shard uint64) *roa
|
|||
// the data []byte is supplied. This mimics the traditional roaring-per-file
|
||||
// and should be faster.
|
||||
func (tx *RoaringTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
|
||||
|
||||
f, err := tx.getFragment(index, field, view, shard)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
|
|
@ -836,6 +858,18 @@ func (db *RoaringStore) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (db *RoaringStore) DeleteField(index, field, fieldPath string) error {
|
||||
|
||||
// under blue-green badger_roaring, the directory will not be found, b/c badger will have
|
||||
// already done the os.RemoveAll(). BUT, RemoveAll returns nil error in this case. Docs:
|
||||
// "If the path does not exist, RemoveAll returns nil (no error)"
|
||||
err := os.RemoveAll(fieldPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "removing directory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// frag should be passed by any RoaringTx user, but for RBF/Badger it can be nil.
|
||||
func (db *RoaringStore) DeleteFragment(index, field, view string, shard uint64, frag interface{}) error {
|
||||
|
||||
|
|
@ -1000,6 +1034,10 @@ func (tx *RBFTx) Pointer() string {
|
|||
return fmt.Sprintf("%p", tx)
|
||||
}
|
||||
|
||||
func (tx *RBFTx) Dump() {
|
||||
// todo
|
||||
}
|
||||
|
||||
// Readonly is true if the transaction is not read-and-write, but only doing reads.
|
||||
func (tx *RBFTx) Readonly() bool {
|
||||
return !tx.tx.Writable()
|
||||
|
|
|
|||
196
txfactory.go
196
txfactory.go
|
|
@ -21,6 +21,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/pilosa/pilosa/v2/rbf"
|
||||
"github.com/pilosa/pilosa/v2/roaring"
|
||||
|
|
@ -67,25 +68,6 @@ type TxFactory struct {
|
|||
idx *Index
|
||||
}
|
||||
|
||||
/* want glue-green to multiplex, so don't do this directly
|
||||
// but rather f.CloseStore()
|
||||
func (f *TxFactory) Store() TxStore {
|
||||
switch f.typeOfTx {
|
||||
case roaringFragmentFilesTxn:
|
||||
return &RoaringStore{}
|
||||
case badgerTxn:
|
||||
return f.badgerDB
|
||||
case rbfTxn:
|
||||
return f.rbfDB
|
||||
// case blueGreenBadgerRoaring:
|
||||
// case blueGreenRoaringBadger:
|
||||
}
|
||||
panic(fmt.Sprintf("unknown f.typeOfTx type: '%v'", f.typeOfTx))
|
||||
=======
|
||||
>>>>>>> Implement pilosa.Tx for RBF
|
||||
}
|
||||
*/
|
||||
|
||||
// integer types for fast switch{}
|
||||
type txtype int
|
||||
|
||||
|
|
@ -235,6 +217,25 @@ func (f *TxFactory) DeleteIndex(name string) error {
|
|||
panic(fmt.Sprintf("unknown f.typeOfTx type: '%v'", f.typeOfTx))
|
||||
}
|
||||
|
||||
func (f *TxFactory) DeleteFieldFromStore(index, field, fieldPath string) error {
|
||||
switch f.typeOfTx {
|
||||
case roaringFragmentFilesTxn:
|
||||
return f.roaringDB.DeleteField(index, field, fieldPath)
|
||||
case badgerTxn:
|
||||
return f.badgerDB.DeleteField(index, field, fieldPath)
|
||||
case rbfTxn:
|
||||
//return f.rbfDB.DeleteField(index, field, fieldPath)
|
||||
return nil
|
||||
case blueGreenBadgerRoaring:
|
||||
_ = f.badgerDB.DeleteField(index, field, fieldPath)
|
||||
return f.roaringDB.DeleteField(index, field, fieldPath)
|
||||
case blueGreenRoaringBadger:
|
||||
_ = f.roaringDB.DeleteField(index, field, fieldPath)
|
||||
return f.badgerDB.DeleteField(index, field, fieldPath)
|
||||
}
|
||||
panic(fmt.Sprintf("unknown f.typeOfTx type: '%v'", f.typeOfTx))
|
||||
}
|
||||
|
||||
func (f *TxFactory) DeleteFragmentFromStore(index, field, view string, shard uint64, frag *fragment) error {
|
||||
switch f.typeOfTx {
|
||||
case roaringFragmentFilesTxn:
|
||||
|
|
@ -416,23 +417,33 @@ func (idx *Index) StringifiedRoaringKeys() (r string) {
|
|||
index := idx.name
|
||||
|
||||
r = "allkeys:[\n"
|
||||
n := 0
|
||||
for _, relpath := range paths {
|
||||
field, view, shard, err := fragmentSpecFromRoaringPath(relpath)
|
||||
if err != nil {
|
||||
continue // ignore .meta paths
|
||||
}
|
||||
abspath := idx.path + sep + relpath
|
||||
s, err := stringifiedRawRoaringFragment(abspath, index, field, view, shard)
|
||||
const showOps = false
|
||||
s, err := stringifiedRawRoaringFragment(abspath, index, field, view, shard, showOps)
|
||||
panicOn(err)
|
||||
//r += fmt.Sprintf("path:'%v' fragment contains:\n") + s
|
||||
if s == "" {
|
||||
s = "<empty bitmap>"
|
||||
}
|
||||
r += s
|
||||
n++
|
||||
}
|
||||
if n == 0 {
|
||||
return "" // new convention that empty database => empty string returned.
|
||||
}
|
||||
// note that we can have a bitmap present, but it can be empty
|
||||
r += "]\n all-in-blake3:" + blake3sum16([]byte(r)) + "\n"
|
||||
|
||||
return "roaring-" + r
|
||||
}
|
||||
|
||||
func stringifiedRawRoaringFragment(path string, index, field, view string, shard uint64) (r string, err error) {
|
||||
func stringifiedRawRoaringFragment(path string, index, field, view string, shard uint64, showOps bool) (r string, err error) {
|
||||
|
||||
var info roaring.BitmapInfo
|
||||
_ = info
|
||||
|
|
@ -471,6 +482,21 @@ func stringifiedRawRoaringFragment(path string, index, field, view string, shard
|
|||
return
|
||||
}
|
||||
|
||||
//cmd.DisplayInfo(info)
|
||||
// inlined
|
||||
if showOps {
|
||||
pC := pointerContext{
|
||||
from: info.From,
|
||||
to: info.To,
|
||||
}
|
||||
if info.ContainerCount > 0 {
|
||||
printContainers(info, pC)
|
||||
}
|
||||
if info.Ops > 0 {
|
||||
printOps(info)
|
||||
}
|
||||
}
|
||||
|
||||
citer, found := rbm.Containers.Iterator(0)
|
||||
_ = found // probably gonna use just the Ops log instead, so don't panic if !found.
|
||||
|
||||
|
|
@ -551,28 +577,6 @@ func fileSize(name string) (int64, error) {
|
|||
|
||||
var _ = fileSize // happy linter
|
||||
|
||||
// Dump prints to stdout the contents of the roaring Containers
|
||||
// stored in idx. Its format may vary depending of the type of
|
||||
// idx.Txf transaction factory that is in use.
|
||||
// Mostly for debugging.
|
||||
func (idx *Index) Dump(label string) {
|
||||
ty := idx.Txf.TxType()
|
||||
fileline := FileLine(2)
|
||||
switch ty {
|
||||
case badgerTxn:
|
||||
fmt.Printf("%v Index.Dump('%v') for index '%v':\n%v\n", fileline, label, idx.name, idx.StringifiedBadgerKeys(nil))
|
||||
return
|
||||
case blueGreenRoaringBadger, blueGreenBadgerRoaring:
|
||||
fmt.Printf("%v Index.Dump('%v') for index '%v', RoaringTx:\n%v\n", fileline, label, idx.name, idx.StringifiedRoaringKeys())
|
||||
fmt.Printf("%v Index.Dump('%v') for index '%v', BadgerTx :\n%v\n", fileline, label, idx.name, idx.StringifiedBadgerKeys(nil))
|
||||
return
|
||||
case roaringFragmentFilesTxn:
|
||||
fmt.Printf("%v Index.Dump('%v') for index '%v', BadgerTx :\n%v\n", fileline, label, idx.name, idx.StringifiedRoaringKeys())
|
||||
return
|
||||
}
|
||||
panic(fmt.Errorf("%v Index.Dump('%v') for index '%v': no implementation for txtype '%v'\n", fileline, label, idx.name, ty))
|
||||
}
|
||||
|
||||
func containerToBytes(ct *roaring.Container) []byte {
|
||||
ty := roaring.ContainerType(ct)
|
||||
switch ty {
|
||||
|
|
@ -587,3 +591,109 @@ func containerToBytes(ct *roaring.Container) []byte {
|
|||
}
|
||||
panic(fmt.Sprintf("unknown container type '%v'", int(ty)))
|
||||
}
|
||||
|
||||
type pointerContext struct {
|
||||
from, to uintptr
|
||||
}
|
||||
|
||||
func printOps(info roaring.BitmapInfo) {
|
||||
fmt.Fprintln(os.Stdout, " Ops:")
|
||||
tw := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
|
||||
fmt.Fprintf(tw, " \t%s\t%s\t%s\t\n", "TYPE", "OpN", "SIZE")
|
||||
printed := 0
|
||||
for _, op := range info.OpDetails {
|
||||
fmt.Fprintf(tw, "\t%s\t%d\t%d\t\n", op.Type, op.OpN, op.Size)
|
||||
printed++
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
func (p *pointerContext) pretty(c roaring.ContainerInfo) string {
|
||||
var pointer string
|
||||
if c.Mapped {
|
||||
if c.Pointer >= p.from && c.Pointer < p.to {
|
||||
pointer = fmt.Sprintf("@+0x%x", c.Pointer-p.from)
|
||||
} else {
|
||||
pointer = fmt.Sprintf("!0x%x!", c.Pointer)
|
||||
}
|
||||
} else {
|
||||
pointer = fmt.Sprintf("0x%x", c.Pointer)
|
||||
}
|
||||
return fmt.Sprintf("%s \t%d \t%d \t%s ", c.Type, c.N, c.Alloc, pointer)
|
||||
}
|
||||
|
||||
// stolen from ctl/inspect.go
|
||||
func printContainers(info roaring.BitmapInfo, pC pointerContext) {
|
||||
fmt.Fprintln(os.Stdout, " Containers:")
|
||||
tw := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
|
||||
fmt.Fprintf(tw, " \t\tRoaring\t\t\t\tOps\t\t\t\tFlags\t\n")
|
||||
fmt.Fprintf(tw, "\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "KEY", "TYPE", "N", "ALLOC", "OFFSET", "TYPE", "N", "ALLOC", "OFFSET", "FLAGS")
|
||||
c1s := info.Containers
|
||||
c2s := info.OpContainers
|
||||
l1 := len(c1s)
|
||||
l2 := len(c2s)
|
||||
i1 := 0
|
||||
i2 := 0
|
||||
var c1, c2 roaring.ContainerInfo
|
||||
c1.Key = ^uint64(0)
|
||||
c2.Key = ^uint64(0)
|
||||
c1e := false
|
||||
c2e := false
|
||||
if i1 < l1 {
|
||||
c1 = c1s[i1]
|
||||
i1++
|
||||
c1e = true
|
||||
}
|
||||
if i2 < l2 {
|
||||
c2 = c2s[i2]
|
||||
i2++
|
||||
c2e = true
|
||||
}
|
||||
printed := 0
|
||||
for c1e || c2e {
|
||||
c1used := false
|
||||
c2used := false
|
||||
var key uint64
|
||||
c1fmt := "-\t\t\t"
|
||||
c2fmt := "-\t\t\t"
|
||||
// If c2 exists, we'll always prefer its flags,
|
||||
// if it doesn't, this gets overwritten.
|
||||
flags := c2.Flags
|
||||
if !c2e || (c1e && c1.Key < c2.Key) {
|
||||
c1fmt = pC.pretty(c1)
|
||||
key = c1.Key
|
||||
c1used = true
|
||||
flags = c1.Flags
|
||||
} else if !c1e || (c2e && c2.Key < c1.Key) {
|
||||
c2fmt = pC.pretty(c2)
|
||||
key = c2.Key
|
||||
c2used = true
|
||||
} else {
|
||||
// c1e and c2e both set, and neither key is < the other.
|
||||
c1fmt = pC.pretty(c1)
|
||||
c2fmt = pC.pretty(c2)
|
||||
key = c1.Key
|
||||
c1used = true
|
||||
c2used = true
|
||||
}
|
||||
if c1used {
|
||||
if i1 < l1 {
|
||||
c1 = c1s[i1]
|
||||
i1++
|
||||
} else {
|
||||
c1e = false
|
||||
}
|
||||
}
|
||||
if c2used {
|
||||
if i2 < l2 {
|
||||
c2 = c2s[i2]
|
||||
i2++
|
||||
} else {
|
||||
c2e = false
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(tw, "\t%d\t%s\t%s\t%s\t\n", key, c1fmt, c2fmt, flags)
|
||||
printed++
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -458,6 +458,7 @@ func (t *ClusterCluster) FollowResizeInstruction(instr *ResizeInstruction) error
|
|||
srctx := srcIdx.Txf.NewTx(Txo{Write: !writable, Index: srcIdx, Fragment: srcFragment})
|
||||
|
||||
destIdx := destCluster.holder.Index(src.Index)
|
||||
|
||||
desttx := destIdx.Txf.NewTx(Txo{Write: writable, Index: destIdx, Fragment: destFragment})
|
||||
|
||||
citer, _, err := srctx.ContainerIterator(src.Index, src.Field, src.View, src.Shard, 0)
|
||||
|
|
|
|||
7
view.go
7
view.go
|
|
@ -137,9 +137,12 @@ func (v *view) open() error {
|
|||
if err := func() error {
|
||||
// Ensure the view's path exists.
|
||||
v.holder.Logger.Debugf("ensure view path exists: %s", v.path)
|
||||
if err := os.MkdirAll(v.path, 0777); err != nil {
|
||||
err := os.MkdirAll(v.path, 0777)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating view directory")
|
||||
} else if err := os.MkdirAll(filepath.Join(v.path, "fragments"), 0777); err != nil {
|
||||
}
|
||||
err = os.MkdirAll(filepath.Join(v.path, "fragments"), 0777)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating fragments directory")
|
||||
}
|
||||
|
||||
|
|
|
|||
24
vprint.go
24
vprint.go
|
|
@ -141,3 +141,27 @@ func FileSize(name string) (int64, error) {
|
|||
}
|
||||
return fi.Size(), nil
|
||||
}
|
||||
|
||||
// Caller returns the name of the calling function.
|
||||
func Caller(upStack int) string {
|
||||
// elide ourself and runtime.Callers
|
||||
target := upStack + 2
|
||||
|
||||
pc := make([]uintptr, target+2)
|
||||
n := runtime.Callers(0, pc)
|
||||
|
||||
f := runtime.Frame{Function: "unknown"}
|
||||
if n > 0 {
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
for i := 0; i <= target; i++ {
|
||||
contender, more := frames.Next()
|
||||
if i == target {
|
||||
f = contender
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return f.Function
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue