diff --git a/Makefile b/Makefile index 511a5900d..b7a74062e 100644 --- a/Makefile +++ b/Makefile @@ -149,6 +149,28 @@ docker-build: docker-test: docker run --rm -v $(PWD):/go/src/$(CLONE_URL) -w /go/src/$(CLONE_URL) golang:$(GO_VERSION) go test -tags='$(BUILD_TAGS)' $(TESTFLAGS) ./... +# run top tests, not subdirs. print summary red/green after. +# The \-\-\- FAIL avoids counting the extra two FAIL strings at then bottom of log.topt. +topt: + go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.topt.roar + @echo " log.topt green: \c"; cat log.topt.roar | grep PASS |wc -l + @echo " log.topt red: \c"; cat log.topt.roar | grep '\-\-\- FAIL' |wc -l + +topt-badger: + PILOSA_TXSRC=badger go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.topt.badger + @echo " log.topt green: \c"; cat log.topt.badger | grep PASS |wc -l + @echo " log.topt red: \c"; cat log.topt.badger | grep '\-\-\- FAIL' |wc -l + +topt-rbf: + PILOSA_TXSRC=rbf go test -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.topt.rbf + @echo " log.topt green: \c"; cat log.topt.rbf | grep PASS |wc -l + @echo " log.topt red: \c"; cat log.topt.rbf | grep '\-\-\- FAIL' |wc -l + +topt-race: + go test -race -v -tags='$(BUILD_TAGS)' $(TESTFLAGS) $(NOCHECKPTR) 2>&1 | tee log.topt.race + @echo " log.topt green: \c"; cat log.topt.race | grep PASS |wc -l + @echo " log.topt red: \c"; cat log.topt.race | grep '\-\-\- FAIL' |wc -l + # Run golangci-lint golangci-lint: require-golangci-lint golangci-lint run --skip-files '.*\.peg\.go' diff --git a/api.go b/api.go index 2d52676c7..b5bc89422 100644 --- a/api.go +++ b/api.go @@ -330,6 +330,7 @@ func setUpImportOptions(opts ...ImportOption) (*ImportOptions, error) { type importJob struct { ctx context.Context + tx Tx req *ImportRoaringRequest shard uint64 field *Field @@ -370,17 +371,23 @@ func importWorker(importWork chan importJob) { var doClear bool switch doAction { case RequestActionOverwrite: - tx := &RoaringTx{Field: j.field} + // TODO(jea): the question here is, why are we commiting this separately from j.tx? + // why doesn't j.tx suffice? It doesn't but why/which is correct? + tx := j.field.holder.indexes[j.field.index].Txf.NewTx(Txo{Write: true, Field: j.field}) + defer tx.Rollback() if err := j.field.importRoaringOverwrite(j.ctx, tx, viewData, j.shard, viewName, j.req.Block); err != nil { return errors.Wrap(err, "importing roaring as overwrite") } + if err := tx.Commit(); err != nil { + return errors.Wrap(err, "commit of importing roaring as overwrite") + } case RequestActionClear: doClear = true fallthrough case RequestActionSet: fileMagic := uint32(binary.LittleEndian.Uint16(viewData[0:2])) if fileMagic == roaring.MagicNumber { // if pilosa roaring format - if err := j.field.importRoaring(j.ctx, viewData, j.shard, viewName, doClear); err != nil { + if err := j.field.importRoaring(j.ctx, j.tx, viewData, j.shard, viewName, doClear); err != nil { return errors.Wrap(err, "importing pilosa roaring") } } else { @@ -388,7 +395,7 @@ func importWorker(importWork chan importJob) { // field.importRoaring changes the standard roaring run format to pilosa roaring data := make([]byte, len(viewData)) copy(data, viewData) - if err := j.field.importRoaring(j.ctx, data, j.shard, viewName, doClear); err != nil { + if err := j.field.importRoaring(j.ctx, j.tx, data, j.shard, viewName, doClear); err != nil { return errors.Wrap(err, "importing standard roaring") } } @@ -439,6 +446,10 @@ func (api *API) ImportRoaring(ctx context.Context, indexName, fieldName string, return newPreconditionFailedError(err) } + // Obtain transaction. + tx := index.Txf.NewTx(Txo{Write: true, Index: index}) + defer tx.Rollback() + nodes := api.cluster.shardNodes(indexName, shard) errCh := make(chan error, len(nodes)) for _, node := range nodes { @@ -446,6 +457,7 @@ func (api *API) ImportRoaring(ctx context.Context, indexName, fieldName string, if node.ID == api.server.nodeID { api.importWork <- importJob{ ctx: ctx, + tx: tx, req: req, shard: shard, field: field, @@ -465,9 +477,11 @@ func (api *API) ImportRoaring(ctx context.Context, indexName, fieldName string, for { select { case <-ctx.Done(): + // defered tx.Rollback() happens automatically here. return ctx.Err() case nodeErr := <-errCh: if nodeErr != nil { + // defered tx.Rollback() happens automatically here. return nodeErr } maxNode++ @@ -475,7 +489,7 @@ func (api *API) ImportRoaring(ctx context.Context, indexName, fieldName string, // Exit once all nodes are processed. if maxNode == len(nodes) { - return nil + return tx.Commit() } } } @@ -583,7 +597,8 @@ func (api *API) ExportCSV(ctx context.Context, indexName string, fieldName strin } // Obtain transaction - tx := &RoaringTx{Index: index} + tx := index.Txf.NewTx(Txo{Write: !writable, Index: index}) + defer tx.Rollback() // Wrap writer with a CSV writer. cw := csv.NewWriter(w) @@ -626,10 +641,8 @@ func (api *API) ExportCSV(ctx context.Context, indexName string, fieldName strin // Ensure data is flushed. cw.Flush() - span.LogKV("n", n) - - return nil + return tx.Commit() } // ShardNodes returns the node and all replicas which should contain a shard's data. @@ -1061,7 +1074,8 @@ func (api *API) Import(ctx context.Context, req *ImportRequest, opts ...ImportOp } // Obtain transaction. - tx := &RoaringTx{Index: index} + tx := index.Txf.NewTx(Txo{Write: true, Index: index}) + defer tx.Rollback() if err := req.ValidateWithTimestamp(index.CreatedAt(), field.CreatedAt()); err != nil { return errors.Wrap(err, "validating import value request") @@ -1165,8 +1179,12 @@ func (api *API) Import(ctx context.Context, req *ImportRequest, opts ...ImportOp err = field.Import(tx, req.RowIDs, req.ColumnIDs, timestamps, opts...) if err != nil { api.server.logger.Printf("import error: index=%s, field=%s, shard=%d, columns=%d, err=%s", req.Index, req.Field, req.Shard, len(req.ColumnIDs), err) + } else { + err = tx.Commit() } + return errors.Wrap(err, "importing") + } // ImportValue bulk imports values into a particular field. @@ -1188,7 +1206,8 @@ func (api *API) ImportValue(ctx context.Context, req *ImportValueRequest, opts . } // Obtain transaction. - tx := &RoaringTx{Index: index} + tx := index.Txf.NewTx(Txo{Write: true, Index: index}) + defer tx.Rollback() // Set up import options. options, err := setUpImportOptions(opts...) @@ -1274,7 +1293,9 @@ func (api *API) ImportValue(ctx context.Context, req *ImportValueRequest, opts . api.server.logger.Printf("import error: index=%s, field=%s, shard=%d, columns=%d, err=%s", req.Index, req.Field, req.Shard, len(req.ColumnIDs), err) } } - + if err == nil { + err = tx.Commit() + } return errors.Wrap(err, "importing value") } diff --git a/badger.go b/badger.go new file mode 100644 index 000000000..65052b525 --- /dev/null +++ b/badger.go @@ -0,0 +1,1701 @@ +// 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 ( + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "time" + "unsafe" + + badger "github.com/dgraph-io/badger/v2" + "github.com/pilosa/pilosa/v2/roaring" +) + +// TODO: is there a more optimal time to do badger garbage collection? +// As in: do we need to be more aggressive about cleaning in +// proportion to write activity? Space monitoring available with the +// badger.DB.Size() (lsm, vlog int64) call. +// +// See: https://godoc.org/github.com/dgraph-io/badger#DB.RunValueLogGC +// and: https://github.com/dgraph-io/badger#garbage-collection +// +// For now we run GC periodically every 1 minute or as set by the +// BadgerDBWrapper.GcEveryDur duration. +// +// Background: (quoting from docs referenced above) +// +// "Badger relies on the client to perform garbage collection at a time of +// their choosing. It provides the following method, which can be invoked +// at an appropriate time: +// +// "DB.RunValueLogGC(): This method is designed to do garbage collection while +// Badger is online. Along with randomly picking a file, it uses statistics +// generated by the LSM-tree compactions to pick files that are likely to +// lead to maximum space reclamation. It is recommended to be called during +// periods of low activity in your system, or periodically. One call would +// only result in removal of at max one log file. As an optimization, you +// could also immediately re-run it whenever it returns nil error (indicating +// a successful value log GC), as shown below." +// +// ticker := time.NewTicker(5 * time.Minute) +// defer ticker.Stop() +// for range ticker.C { +// again: +// err := db.RunValueLogGC(0.5) +// if err == nil { +// goto again +// } +// } +// + +// ========================================================= +// A note on using a recent version of badgerdb: +// +// We require a v2 release of badger after 2020 May 13, when support for +// multiple read-write iterators within one transaction was added. +// Many executor_test.go tests do foreachRow() operations, +// which call BadgerTx.ContainerIterator(), which in turn creates +// a first read-write iterator, and then OffsetRange(), which needs a +// second iterator, while still in the same read-write transaction. +// +// The most recent v2 master was pulled in and added to go.mod +// by doing go get github.com/dgraph-io/badger/v2@master +// resulting in the go.mod line +// github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200709123515-8e896a7af361 +// as of this writing, 2020 July 09. This version contains the support +// for having multiple read-write iterators. +// +// Reference on github.com/dgraph-io/badger +// +// commit af22dfd8d51317d765f0c05dcdf1d15981cca4f3 +// Author: Elliot Courant +// Date: Wed May 13 01:07:33 2020 -0500 +// +// Support multiple iterators in read-write transactions. (#1286) +// +// This adds support for multiple iterators during a read-write transaction. The +// iterators created in a read-write transaction will only be able to see writes +// that were performed before the iterator was created. Any writes that occur +// after the iterator is created will be invisible to the iterator. +// +// Fixes https://github.com/dgraph-io/badger/issues/981 +// +// +// Otherwise we'll get these panics: +// 'Only one iterator can be active at one time, for a RW txn.' +// when trying to open a second iterator on the same write transaction. +// e.g. go test -v -run TestExecutor_TranslateRowsOnBool + +var badgerDefaultLogger *BadgerLog +var badgerTestLogger *BadgerLog + +func init() { + // badger test output clutters up the screen, dump to /dev/null for now. + // TODO(jea): figure out where badger logging should go. + null, err := os.Open(os.DevNull) + panicOn(err) + badgerTestLogger = &BadgerLog{Logger: log.New(null, "badger ", log.LstdFlags)} + badgerDefaultLogger = badgerTestLogger + + // BadgerDB recommends a minimum of 128 GOMAXPROCS to make use of the IOPs + // available on the SSD. So we set that here. Details: + // + // from https://github.com/dgraph-io/badger#are-there-any-go-specific-settings-that-i-should-use + // + // "We *highly* recommend setting a high number for GOMAXPROCS, + // which allows Go to observe the full IOPS throughput provided by + // modern SSDs. In Dgraph, we have set it to 128. For more details, + // see this thread [https://groups.google.com/forum/#!topic/golang-nuts/jPb_h3TvlKE/discussion]." + // + // From that thread on golang-nuts: + // + // "Manish Rai Jain + // 8/7/17 + // Hey folks, + // During Gophercon, I happened to meet Russ Cox and asked him the same question. + // If File::Read blocks goroutines, which then spawn new OS threads, in a long running job, + // there should be plenty of OS threads created already, so the random read throughput + // should increase over time and stabilize to the maximum possible value. But, that's + // not what I see in my benchmarks. + // + // And his explanation was that the GOMAXPROCS in a way acts like a multiplexer. + // From docs, "the GOMAXPROCS variable limits the number of operating system threads + // that can execute user-level Go code simultaneously." Which basically means, all + // reads must first be run only via GOMAXPROCS number of goroutines, before switching + // over to some OS thread (not really a switch, but conceptually speaking). This + // introduces a bottleneck for throughput. + // I re-ran my benchmarks with a much higher GOMAXPROCS and was able to then + // achieve the maximum throughput. The numbers are here: + // https://github.com/dgraph-io/badger-bench/blob/master/randread/maxprocs.txt + // To summarize these benchmarks, Linux fio achieves 118K IOPS, and with GOMAXPROCS=64/128, + // I'm able to achieve 105K IOPS, which is close enough. Win! + // + // Regarding the point about using io_submit etc., instead of goroutines; I managed to + // find a library which does that, but it performed worse than just using goroutines. + // https://github.com/traetox/goaio/issues/3 + // From what I gather (talking to Russ and Ian), whatever work is going on in user space, + // the same work has to happen in kernel space; so there's not much benefit here. + // + // Overall, with GOMAXPROCS set to a higher value (as I've done in Dgraph), one can get + // the advertised SSD throughput using goroutines." + // + runtime.GOMAXPROCS(128) +} + +// BadgerLog exists because badger requires a particular logger interface, with a +// Debugf method that is not on standard library log.Logger +type BadgerLog struct { + *log.Logger +} + +// Errorf logs an error. +func (l *BadgerLog) Errorf(f string, v ...interface{}) { + l.Printf("ERROR: "+f, v...) +} + +// Warningf logs a warning. +func (l *BadgerLog) Warningf(f string, v ...interface{}) { + l.Printf("WARNING: "+f, v...) +} + +// Infof logs an informational statement. +func (l *BadgerLog) Infof(f string, v ...interface{}) { + l.Printf("INFO: "+f, v...) +} + +// Debugf logs a debug statement. +func (l *BadgerLog) Debugf(f string, v ...interface{}) { + l.Printf("DEBUG: "+f, v...) +} + +// newBadgerDBWrapper creates a new empty database, blowing away +// any prior path + "-badgerdb" directory. +func newBadgerDBWrapper(path string) (*BadgerDBWrapper, error) { + bpath := badgerPath(path) + err := os.RemoveAll(bpath) + if err != nil { + return nil, err + } + return openBadgerDBWrapper(bpath) +} + +// badgerPath is a helper for determining the full directory +// in which the badger database will be stored. +func badgerPath(path string) string { + if !strings.HasSuffix(path, "-badgerdb") { + return path + "-badgerdb" + } + return path +} + +// openBadgerDB opens the database in the bpath directoy +// without deleting any prior content. Any BadgerDB +// database directory will have the "-badgerdb" suffix. +func openBadgerDBWrapper(bpath string) (*BadgerDBWrapper, error) { + + // now that newTxFactory can call us directly, we might not + // have the -badgerdb suffix. + if !strings.HasSuffix(bpath, "-badgerdb") { + bpath += "-badgerdb" + } + + // regular: works on amd64, but 386 doesn't work. + opt := badger.DefaultOptions(bpath).WithLogger(badgerDefaultLogger) + + // to get memory only do: + //opt := badger.DefaultOptions("").WithLogger(badgerDefaultLogger).WithInMemory(true) + + db, err := badger.Open(opt) + if err != nil { + return nil, err + } + halt := make(chan bool) + w := &BadgerDBWrapper{ + path: bpath, + db: db, + halt: halt, + hasher: NewBlake3Hasher(), + } + w.startBadgerGarbageCollectionBackgroundGoro() + return w, nil +} + +// DeleteIndex deletes all the containers associated with +// the named index from the badger database. +func (w *BadgerDBWrapper) DeleteIndex(indexName string) error { + + // We use the apostrophie rune `'` to locate the end of the + // index name in the key prefix, so we cannot allow indexNames + // themselves to contain apostrophies. + if strings.Contains(indexName, "'") { + return fmt.Errorf("error: bad indexName `%v` in BadgerDBWrapper.DeleteIndex() call: indexName cannot contain apostrophes/single quotes.", indexName) + } + w.muDb.Lock() + defer w.muDb.Unlock() + + // a) do key-ony iteration, no value fetch; + // + // b) do deletes in large batches, to avoid alot of txn overhead; + // per recommendation https://github.com/dgraph-io/badger/issues/598 + // + // c) we do not, at present, try to maintain one large + // transaction with all the keys in a index in it. Because + // there can be too many keys. Hence the index will disappear + // in chucks of 100K keys, not atomically-all-at-once. + + prefix := badgerIndexOnlyPrefix(indexName) + + noMoreKeysWithPrefix := false + const maxDeletesPerTxn = 100000 + + for !noMoreKeysWithPrefix { + err := w.db.Update(func(txn *badger.Txn) error { + o := badger.DefaultIteratorOptions + o.AllVersions = false + o.PrefetchValues = false // key-only iteration, no values. + + // note: panic: Unclosed iterator at time of Txn.Discard ? panic on segfault here? + // This means we messed up and Closed() the Database already; too early. + it := txn.NewIterator(o) + + defer it.Close() + n := 0 + goners := make([][]byte, 0, maxDeletesPerTxn) + for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { + + // KeyCopy() is required; Key() means corruption and possible segfault. + key := it.Item().KeyCopy(nil) + goners = append(goners, key) + n++ + if n >= maxDeletesPerTxn { + break + } + } + if !it.ValidForPrefix(prefix) { + noMoreKeysWithPrefix = true // done with the full delete of up to maxDeletesPerTxn + } + for _, key := range goners { + if err := txn.Delete(key); err != nil { + return err + } + } + return nil // auto-commit happens + }) + // err back from Update can be ErrConflict in case of + // a conflict. Badger docs: "Depending on the state + // of your application, you have the option to + // retry the operation if you receive this error." + panicOn(err) + + } // end for: proceed to next bath of 100K keys + + // Finally, run a garbage collection to delete values from the value log. + // + // "Only one GC is allowed at a time. If another value log GC + // is running, or DB has been closed, this would return an ErrRejected." + // -- https://godoc.org/github.com/dgraph-io/badger#DB.RunValueLogGC + // Still, we don't see a mutex inside the RunValueLogGC code, so + // lock muGC just to be sure. + w.muGC.Lock() + defer w.muGC.Unlock() + _ = w.db.RunValueLogGC(0.5) + + return nil +} + +// startBadgerGarbageCollectionBackgroundGoro handles Badger DB +// garbage colection by regularly purging the value log from +// a background goroutine. w.GcEveryDur controls how often +// it runs. The default is after every 60 seconds. +func (w *BadgerDBWrapper) startBadgerGarbageCollectionBackgroundGoro() { + go func() { + dur := w.GcEveryDur + if dur == 0 { + dur = time.Minute + } + ticker := time.NewTicker(dur) + defer ticker.Stop() + for { + select { + case <-ticker.C: + w.muGC.Lock() + again: + err := w.db.RunValueLogGC(0.5) + if err == nil { + goto again + } + w.muGC.Unlock() + case <-w.halt: + return + } + } + }() +} + +// statically confirm that BadgerTx satisfies the Tx interface. +var _ Tx = (*BadgerTx)(nil) + +// BadgerDBWrapper provides the NewBadgerTx() method. +// The methods on BadgerDBWrapper are thread-safe, and can be called +// from different goroutines/threads. +type BadgerDBWrapper struct { + // serialize operations on BadgerDBWrapper and thus on the .db too, + // when obtaining new txns on different goroutines. + muDb sync.Mutex + + path string + db *badger.DB + + // openTx and openIt are BadgerDBWrapper scoped tables of all open + // transactions and iterators. These are primarily for debugging purposes. + // openTx and openIt should only be read/written after locking the muOpenTxIt mutex. + + // the bool value is the writable attribute of the key *BadgerTx + openTx map[*BadgerTx]bool + + // the bool value is whether the iterator is reversed + openIt map[*BadgerIterator]bool + + // protect openTx and openIt + muOpenTxIt sync.Mutex + + // close(halt) to shutdown the badger gc goroutine in Close() + halt chan bool + + // make BadgerDBWrapper.Close() idempotent, avoiding panic on double Close() + closed bool + + // GcEveryDur controls how often the background goroutine + // runs garbage collection on the on-disk values-log. + // It defaults to running a GC every 1 minute if left as 0. + GcEveryDur time.Duration + + // muGC ensures we only run one Garbage Collection at a time. + muGC sync.Mutex + + hasher *Blake3Hasher + + // doAllocZero sets the corresponding flag on all new BadgerTx. + // When doAllocZero is true, we zero out any data from badger + // after transcation commit and rollback. This simulates + // what would happen if we were to use the mmap-ed data + // from badger directly. Currently we copy by default for + // safety because otherwise TestAPI_ImportColumnAttrs sees + // corrupted data. + doAllocZero bool +} + +// unprotectedListOpenTxAsString is a debugging helper. +// It is not thread safe, but is only used for debugging. Called internally while +// holding locks. +func (w *BadgerDBWrapper) unprotectedListOpenTxAsString() (r string) { + + r = "openTx list = [" + for txn, write := range w.openTx { + r += fmt.Sprintf("txn p=%p(write:%v), ", txn, write) + } + return r + "]" +} + +var _ = (*BadgerDBWrapper)(nil).unprotectedListOpenTxAsString // linter happy + +// UnprotectedListOpenItAsString is exported because it is +// used for debugging in some of the pilosa_test tests. +// It is not thread safe, but only used for debugging. Called internally +// while holding locks and externally while not. +func (w *BadgerDBWrapper) UnprotectedListOpenItAsString() (r string) { + r = "openIt list = [" + for it, reverse := range w.openIt { + r += fmt.Sprintf("it p=%p(reverse:%v), ", it, reverse) + } + return r + "]" +} + +// NewBadgerTx produces BadgerDB based ACID transactions. If +// the transaction will modify data, then the write flag must be true. +// Read-only queries should set write to false, to allow more concurrency. +// Methods on a BadgerTx are thread-safe, and can be called from +// different goroutines. +func (w *BadgerDBWrapper) NewBadgerTx(write bool) (tx *BadgerTx) { + w.muDb.Lock() + defer w.muDb.Unlock() + + tx = &BadgerTx{ + write: write, + tx: w.db.NewTransaction(write), + Db: w, + initloc: stack(), + doAllocZero: w.doAllocZero, + } + //vv("NewBadgerTx(write=%v) top, p=%p", write, tx) + //pp("NewBadgerTx(write=%v) top, p=%p, stack=\n\n'%v'", write, tx, stack()) + + if w.openTx == nil { + w.openTx = make(map[*BadgerTx]bool) + } + //pp("NewBadgerTx(write=%v); p=%p; (currently open txn: '%v', its: '%v'). initloc:'%v'", write, tx, w.unprotectedListOpenTxAsString(), w.UnprotectedListOpenItAsString(), tx.initloc) + w.muOpenTxIt.Lock() + w.openTx[tx] = write + w.muOpenTxIt.Unlock() + return +} + +// Close shuts down the Badger database. +func (w *BadgerDBWrapper) Close() (err error) { + w.muDb.Lock() + defer w.muDb.Unlock() + if !w.closed { + close(w.halt) + w.closed = true + } + return w.db.Close() +} + +// BadgerTx wraps a badger.Txn and provides the Tx interface +// method implementations. +// The methods on BadgerTx are thread-safe, and can be called +// from different goroutines. +type BadgerTx struct { + + // mu serializes badger operations on this single txn instance. + // + // reference: https://godoc.org/github.com/dgraph-io/badger + // "Running [two separate -jea] transactions concurrently is OK. However, a + // transaction itself isn't thread safe, and should only + // be run serially. It doesn't matter if a transaction is + // created by one goroutine and passed down to other, as + // long as the Txn APIs are called serially." + mu sync.Mutex + + write bool + Db *BadgerDBWrapper + tx *badger.Txn + + opcount int + + initloc string // stack trace of where we were initially created. + + doAllocZero bool + + // for tracking txn boundary issues, track all the memory + // that we deploy for roaring containers, and zero it on + // transaction commit/rollback. + ourAllocs [][]byte + ourContainers []*roaring.Container +} + +func (tx *BadgerTx) UseRowCache() bool { + return false +} + +// overWriteOurAllocs provides detection of memory +// access outside the transactional context, similar to the +// old school electric fence techniques but without setting +// memory mappings to read-only... instead we just zero +// out the memory allocated to roaring containers by a +// transaction after the commit or rollback. This, +// hopefully, will cause some downstream confusion and +// test failures, which we can use to locate who has been +// holding on to memory they should have copied prior +// to transaction commit. +func (tx *BadgerTx) overWriteOurAllocs() { + + for _, s := range tx.ourAllocs { + + // The Go compiler recognizes the following pattern and inserts + // an efficient memclr instruction. + // See https://github.com/golang/go/issues/5373 + // and https://codereview.appspot.com/137880043 + for i := range s { + s[i] = 0 + } + } + // keep this around if we need to activate out-of-mmap memory access again. + //for _, v := range tx.ourContainers { + //v.Invalid = true + //v.Tx = tx + //} +} + +// WholeDatabaseBlake3Hash returns the root-hash from the Merkle tree +// built by hashing all bits stored in the database backing this transaction. +func (tx *BadgerTx) WholeDatabaseBlake3Hash(index, field, view string, shard uint64) (hash string, err error) { + return +} + +// Pointer gives us a memory address for the underlying transaction for debugging. +// It is public because we use it in roaring to report invalid container memory access +// outside of a transaction. +func (tx *BadgerTx) Pointer() string { + return fmt.Sprintf("%p", tx) +} + +// Rollback rolls back the transaction. +func (tx *BadgerTx) Rollback() { + tx.mu.Lock() + defer tx.mu.Unlock() + + //pp("BadgerTx.Rollback p=%p, its: '%v' initloc: '%v',\n rollbackloc:'%v'", tx, tx.Db.UnprotectedListOpenItAsString(), tx.initloc, stack()) + tx.tx.Discard() // must hold tx.mu mutex lock + + tx.Db.muOpenTxIt.Lock() + delete(tx.Db.openTx, tx) + tx.Db.muOpenTxIt.Unlock() + + if tx.doAllocZero { + // and clear our allocs, to find code using them outside of a txn. + tx.overWriteOurAllocs() + } +} + +// Commit commits the transaction to permanent storage. +// Commits can handle up to 100k updates to fragments +// at once, but not more. This is a BadgerDB imposed limit. +func (tx *BadgerTx) Commit() error { + tx.mu.Lock() + defer tx.mu.Unlock() + + tx.Db.muOpenTxIt.Lock() + delete(tx.Db.openTx, tx) + tx.Db.muOpenTxIt.Unlock() + + //pp("BadgerTx.Commit (write:%v) p=%p, stackID=%x openit: '%v' initloc: '%v', commitloc:\n%v", tx.write, tx, stackID, tx.Db.UnprotectedListOpenItAsString(), tx.initloc, stack()) + + err := tx.tx.Commit() // must hold tx.mu mutex lock + + if tx.doAllocZero { + tx.overWriteOurAllocs() + } + return err +} + +// Readonly returns true iff the BadgerTx is read-only. +func (tx *BadgerTx) Readonly() bool { + return !tx.write +} + +// LeftShifted16MaxContainerKey is 0xffffffffffff0000. It is similar +// to the roaring.maxContainerKey 0x0000ffffffffffff, but +// shifted 16 bits to the left so its domain is the full [0, 2^64) bit space. +// It is used to match the semantics of the roaring.OffsetRange() API. +// This is the maximum endx value for Tx.OffsetRange(), because the lowbits, +// as in the roaring.OffsetRange(), are not allowed to be set. +// It is used in Tx.RoaringBitamp() to obtain the full contents of a fragment +// from a call from tx.OffsetRange() by requesting [0, LeftShifted16MaxContainerKey) +// with an offset of 0. +const LeftShifted16MaxContainerKey = uint64(0xffffffffffff0000) // or math.MaxUint64 - (1<<16 - 1), or 18446744073709486080 + +// RoaringBitmap returns the roaring.Bitmap for all bits in the fragment. +func (tx *BadgerTx) RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) { + + return tx.OffsetRange(index, field, view, shard, 0, 0, LeftShifted16MaxContainerKey) +} + +// badgerKey produces the bytes that we use as a key to query badger. +// The roaringContainerKey argument is a container key into a roaring Container. +// Output examples: +// +// "idx:'i';fld:'f';vw:'standard';shd:'0';ckey@00000000000000000000" // smallest container-key +// "idx:'i';fld:'f';vw:'standard';shd:'0';ckey@18446744073709551615" // largest container-key (math.MaxUint64) +// +// NB must be kept in sync with badgerPrefix() and badgerKeyExtractContainerKey(). +// +func badgerKey(index, field, view string, shard uint64, roaringContainerKey uint64) []byte { + // The %020d which adds zero padding up to 20 runes is required to + // allow the textual sort to accurately + // reflect a numeric sort order. This is because, as a string, + // math.MaxUint64 is 20 bytes long. + // Example of such a badgerKey with a container-key that is math.MaxUint64: + // ...........................................12345678901234567890 + // idx:'i';fld:'f';vw:'standard';shd:'1';ckey@18446744073709551615 + + prefix := badgerPrefix(index, field, view, shard) + ckey := []byte(fmt.Sprintf("%020d", roaringContainerKey)) + return append(prefix, ckey...) +} + +// badgerKeyAndPrefix returns the equivalent of badgerKey() and badgerPrefix() calls. +func badgerKeyAndPrefix(index, field, view string, shard uint64, roaringContainerKey uint64) (key, prefix []byte) { + prefix = badgerPrefix(index, field, view, shard) + ckey := []byte(fmt.Sprintf("%020d", roaringContainerKey)) + return append(prefix, ckey...), prefix +} + +var _ = badgerKeyAndPrefix // keep linter happy + +// badgerKeyExtractContainerKey extracts the containerKey from bkey. +func badgerKeyExtractContainerKey(bkey []byte) (containerKey uint64) { + + // The zero padding means that the container-key is always the last 20 bytes of the bkey. + // + // Be sure to catch the problematic case of a user passing in only a prefix. A prefix + // ends in 'key@' rather than a full key that has 'key@00000000000000000001' (for example) + // at the end. The ParseUint call below will fail in that case. + n := len(bkey) + if n < 20 { + panic(fmt.Sprintf("badgerKeyExtractContainerKey() error: bad bkey '%v', too short!", string(bkey))) + } + last := bkey[n-20:] // badgerKey() and badgerPrefix() always return more than 20 rune []byte. + var err error + containerKey, err = strconv.ParseUint(string(last), 10, 64) // has to be the container key + if err != nil { + panic(fmt.Sprintf("badgerKeyExtractContainerKey() error: bad bkey '%v', could not convert last 20 bytes ('%v') to a unit64: '%v'", string(bkey), string(last), err)) + } + return +} + +// badgerPrefix returns everything from badgerKey up to and +// including the '@' fune in a badger key. The prefix excludes the roaring container key itself. +// NB must be kept in sync with badgerKey() and badgerKeyExtractContainerKey(). +func badgerPrefix(index, field, view string, shard uint64) []byte { + return []byte(fmt.Sprintf("idx:'%v';fld:'%v';vw:'%v';shd:'%x';ckey@", index, field, view, shard)) +} + +// badgerIndexOnlyPrefix returns a prefix suitable for DeleteIndex and a key-scan to +// remove all storage associated with one index. +// +// The full name of the index must be provided, no partial index names will work. +// +// The provided key is terminated by `';` and so DeleteIndex("i") will not delete the index "i2". +// +func badgerIndexOnlyPrefix(indexName string) []byte { + return []byte(fmt.Sprintf("idx:'%v';", indexName)) +} + +// 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) { + + // values returned from Get() are only valid while the transaction + // is open. If you need to use a value outside of the transaction then + // you must use copy() to copy it to another byte slice. + // BUT here we are already inside the Txn. + + bkey := badgerKey(index, field, view, shard, ckey) + tx.mu.Lock() + var item *badger.Item + item, err = tx.tx.Get(bkey) + tx.mu.Unlock() + if err == badger.ErrKeyNotFound { + // Seems crazy, but we, for now at least, + // match what RoaringTx does by returning nil, nil. + return nil, nil + } else { + panicOn(err) + } + + err = item.Value(func(v []byte) error { + // This func with val would only be called if item.Value encounters no error + c = tx.toContainer(item.UserMeta(), v) + return nil + }) + panicOn(err) + return +} + +// PutContainer stores rc under the specified fragment and container ckey. +func (tx *BadgerTx) PutContainer(index, field, view string, shard uint64, ckey uint64, rc *roaring.Container) error { + + bkey := badgerKey(index, field, view, shard, ckey) + var by []byte + + ct := roaring.ContainerType(rc) + + switch ct { + case containerArray: + by = fromArray16(roaring.AsArray(rc)) + case containerBitmap: + by = fromArray64(roaring.AsBitmap(rc)) + case containerRun: + by = fromInterval16(roaring.AsRuns(rc)) + case containerNil: + panic("wat? nil container is unexpected, no?!?") + default: + panic(fmt.Sprintf("unknown container type: %v", ct)) + } + entry := badger.NewEntry(bkey, by).WithMeta(ct) + tx.mu.Lock() + err := tx.tx.SetEntry(entry) + tx.mu.Unlock() + + // ErrTxnTooBig is returned if too many writes are fit into a single transaction. + // badger docs: "An ErrTxnTooBig will be reported in case the number of pending + // writes/deletes in the transaction exceeds a certain limit. In that case, it + // is best to commit the transaction and start a new transaction immediately." + // + if err == badger.ErrTxnTooBig { + // As now, we don't deal with this. The current strategy is to recommend setting lots + // of bits on your container and then change it in a single operation + // within the txn, rather than having too many SetEntry() calls in a transactions. + // The tests currently have these default limits: + // maxBatchCount:104857, maxBatchSize:10066329 + // For now, we just panic. The user should re-write their code to do + // most of the work of setting bits outside the transaction. + panic(fmt.Sprintf("error: do not do more than 100K writes in a transaction: '%v'", err)) + } + return err +} + +// RemoveContainer deletes the container specified by the shard and container key ckey +func (tx *BadgerTx) RemoveContainer(index, field, view string, shard uint64, ckey uint64) error { + bkey := badgerKey(index, field, view, shard, ckey) + tx.mu.Lock() + err := tx.tx.Delete(bkey) + tx.mu.Unlock() + return err +} + +// Add sets all the a bits hot in the specified fragment. +func (tx *BadgerTx) Add(index, field, view string, shard uint64, batched bool, a ...uint64) (changeCount int, err error) { + + // pure hack to match RoaringTx + defer func() { + if !batched { + if changeCount > 0 { + changeCount = 1 + } + } + }() + + // TODO: optimization: group 'a' elements into their containers, + // and then do all the Adds on that + // container at once, so we don't retrieve a container per bit. + // (maybe, for example, using ImportRoaringBits with clear=false). + + for _, v := range a { + hi, lo := highbits(v), lowbits(v) + + var rct *roaring.Container + rct, err = tx.Container(index, field, view, shard, hi) + panicOn(err) + if err != nil { + return 0, err + } + chng := false + // TODO optimization: set all the bits in the current container at once. group by container first. + rc1, chng := rct.Add(lo) + panicOn(err) + if chng { + changeCount++ + } + if err != nil { + return changeCount, err + } + err = tx.PutContainer(index, field, view, shard, hi, rc1) + panicOn(err) + } + return +} + +// Remove clears all the specified a bits in the chosen fragment. +func (tx *BadgerTx) Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) { + + // TODO: optimization: group 'a' elements into their containers, + // and then do all the Removes on that + // container at once, so we don't retrieve a container per bit. + // (maybe, for example, using ImportRoaringBits with clear=true). + for _, v := range a { + hi, lo := highbits(v), lowbits(v) + + var rct *roaring.Container + rct, err = tx.Container(index, field, view, shard, hi) + panicOn(err) + if err != nil { + return 0, err + } + chng := false + rc1, chng := rct.Remove(lo) + panicOn(err) + if chng { + changeCount++ + } + if err != nil { + return changeCount, err + } + if rc1.N() == 0 { + err = tx.RemoveContainer(index, field, view, shard, hi) + if err != nil { + return + } + } else { + err = tx.PutContainer(index, field, view, shard, hi, rc1) + panicOn(err) + } + } + return +} + +// Contains returns exists true iff the bit chosen by key is +// hot (set to 1) in specified fragment. +func (tx *BadgerTx) Contains(index, field, view string, shard uint64, key uint64) (exists bool, err error) { + + lo, hi := lowbits(key), highbits(key) + bkey := badgerKey(index, field, view, shard, hi) + tx.mu.Lock() + item, err := tx.tx.Get(bkey) + tx.mu.Unlock() + if err == badger.ErrKeyNotFound { + return false, nil + } + if err != nil { + return false, err + } + err = item.Value(func(v []byte) error { + // This func with val would only be called if item.Value encounters no error + c := tx.toContainer(item.UserMeta(), v) + exists = c.Contains(lo) + return nil + }) + return exists, err +} + +// key is the container key for the first roaring Container +// roaring docs: Iterator returns a ContainterIterator which *after* a call to Next(), a call to Value() will +// return the first container at or after key. found will be true if a +// container is found at key. +// +// BadgerTx notes: We auto-stop at the end of this shard, not going beyond. +func (tx *BadgerTx) ContainerIterator(index, field, view string, shard uint64, firstRoaringContainerKey uint64) (citer roaring.ContainerIterator, found bool, err error) { + + // needle example: "index:'i';field:'f';view:'v';shard:'0';key@00000000000000000000" + needle := badgerKey(index, field, view, shard, firstRoaringContainerKey) + + // prefix example: "index:'i';field:'f';view:'v';shard:'0';key@" + prefix := badgerPrefix(index, field, view, shard) + + bi := NewBadgerIterator(tx, prefix) + bi.Seek(needle) + if !bi.it.Valid() { + return bi, false, nil + } + + if !bi.it.ValidForPrefix(prefix) { + return bi, false, nil + } + return bi, true, nil +} + +// BadgerIterator is the iterator returned from a BadgerTx.ContainerIterator() call. +// It implements the roaring.ContainerIterator interface. +type BadgerIterator struct { + tx *BadgerTx + it *badger.Iterator + + prefix []byte + seekto []byte + + // seen counts how many Next() calls we have seen. + // It is used to match roaring.ContainerIterator semantics. + // Also useful for testing. + seen int +} + +// NewBadgerIterator creates an iterator on tx that will +// only return badgerKeys that start with prefix. +func NewBadgerIterator(tx *BadgerTx, prefix []byte) (bi *BadgerIterator) { + + tx.Db.muOpenTxIt.Lock() + defer tx.Db.muOpenTxIt.Unlock() + + defer func() { + r := recover() + if r != nil { + panic(r) + } + }() + opts := badger.DefaultIteratorOptions + opts.PrefetchValues = false // else by default, pre-fetches the 1st 100 values, which would be slow. + opts.Reverse = false + + tx.mu.Lock() + it := tx.tx.NewIterator(opts) + tx.mu.Unlock() + + bi = &BadgerIterator{ + tx: tx, + it: it, + prefix: prefix, + } + if tx.Db.openIt == nil { + tx.Db.openIt = make(map[*BadgerIterator]bool) + } + tx.Db.openIt[bi] = false // true for reverse, false for forward iteration. + bi.it.Seek(prefix) + return +} + +// NewBadgerReverseIterator makes a highest-to-lowest key iterator. +// Only keys that are prefixed with prefix will be returned. +// seekto tells where to start, and should be typically shard+1 +// to start at the end of shard. Really only used in Max() at the moment. +// After creating a reverse badger iterator it, we will call it.Seek(seekto). +func NewBadgerReverseIterator(tx *BadgerTx, prefix, seekto []byte) (bi *BadgerIterator) { + + tx.Db.muOpenTxIt.Lock() + defer tx.Db.muOpenTxIt.Unlock() + + opts := badger.DefaultIteratorOptions + opts.PrefetchValues = false // else by default, pre-fetches the 1st 100 values, which would be slow. + opts.Reverse = true + opts.Prefix = prefix // possible storage IOPs optimization by badger + tx.mu.Lock() + it := tx.tx.NewIterator(opts) + tx.mu.Unlock() + + bi = &BadgerIterator{ + tx: tx, + it: it, + prefix: prefix, + seekto: seekto, + } + if tx.Db.openIt == nil { + tx.Db.openIt = make(map[*BadgerIterator]bool) + } + bi.tx.Db.openIt[bi] = true // true for reverse, false for forward iteration. + bi.it.Seek(seekto) + return +} + +// Close tells the database and transaction that the user is done +// with the iterator. +// From the badger docs: It is important to call this when you're done with iteration. +// else you will get an error on tx.Discard()/Commit(). +func (bi *BadgerIterator) Close() { + + bi.tx.Db.muOpenTxIt.Lock() + delete(bi.tx.Db.openIt, bi) + bi.it.Close() + + bi.tx.Db.muOpenTxIt.Unlock() +} + +// Valid returns false if there are no more values in the iterator's range. +func (bi *BadgerIterator) Valid() bool { + return bi.it.Valid() +} + +// Seek allows the iterator to start at needle instead of the global begining. +func (bi *BadgerIterator) Seek(needle []byte) { + bi.it.Seek(needle) +} + +// Next advances the iterator. +func (bi *BadgerIterator) Next() bool { + + // have to skip the first bi.it.Next() call because badger iterators point to the + // first value immediately, but Pilosa iterators must have Next() called + // on a fresh iterator to get the first value. + if bi.seen > 0 { + bi.it.Next() + } + bi.seen++ + return bi.it.ValidForPrefix(bi.prefix) // does the bi.it.Valid() inside and false if not valid always. +} + +// Value retrieves what is pointed at currently by the iterator. +func (bi *BadgerIterator) Value() (containerKey uint64, c *roaring.Container) { + if !bi.it.Valid() { + panic("bi.it not valid") + } + item := bi.it.Item() + if item == nil { + panic("item was nil") + } + key := item.Key() + containerKey = badgerKeyExtractContainerKey(key) + + err := item.Value(func(v []byte) error { + c = bi.tx.toContainer(item.UserMeta(), v) + return nil + }) + panicOn(err) + return +} + +// Closer is used by badgerFinder +type Closer interface { + Close() +} + +// badgerFinder implements roaring.IteratorFinder. +// It is used by BadgerTx.ForEach() +type badgerFinder struct { + tx *BadgerTx + index string + field string + view string + shard uint64 + needClose []Closer +} + +// FindIterator lets badgerFinder implement the roaring.FindIterator interface. +func (bf *badgerFinder) FindIterator(seek uint64) (roaring.ContainerIterator, bool) { + a, found, err := bf.tx.ContainerIterator(bf.index, bf.field, bf.view, bf.shard, seek) + panicOn(err) + bf.needClose = append(bf.needClose, a) + return a, found +} + +// Close closes all bf.needClose listed Closers. +func (bf *badgerFinder) Close() { + for _, i := range bf.needClose { + i.Close() + } +} + +// NewTxIterator returns a *roaring.Iterator that MUST have Close() called on it BEFORE +// the transaction Commits or Rollsback. +func (tx *BadgerTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator { + bf := &badgerFinder{tx: tx, index: index, field: field, view: view, shard: shard, needClose: make([]Closer, 0)} + itr := roaring.NewIterator(bf) + return itr +} + +// ForEach applies fn to each bitmap in the fragment. +func (tx *BadgerTx) ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error { + itr := tx.NewTxIterator(index, field, view, shard) + defer itr.Close() + + // Seek can create many container iterators, thus bf.Close() needClose list. + itr.Seek(0) + // v is the bit we are operating on. + for v, eof := itr.Next(); !eof; v, eof = itr.Next() { + if err := fn(v); err != nil { + return err + } + } + return nil +} + +// ForEachRange applies fn on the selected range of bits on the chosen fragment. +func (tx *BadgerTx) ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error { + + itr := tx.NewTxIterator(index, field, view, shard) + defer itr.Close() + + itr.Seek(start) + + // v is the bit we are operating on. + for v, eof := itr.Next(); !eof && v < end; v, eof = itr.Next() { + if err := fn(v); err != nil { + return err + } + } + return nil +} + +// Count operates on the full bitmap level, so it sums over all the containers +// in the bitmap. +func (tx *BadgerTx) Count(index, field, view string, shard uint64) (uint64, error) { + + a, found, err := tx.ContainerIterator(index, field, view, shard, 0) + panicOn(err) + defer a.Close() + if !found { + return 0, nil + } + result := int32(0) + for a.Next() { + ckey, cont := a.Value() + _ = ckey + result += cont.N() + } + + return uint64(result), nil +} + +// Max is the maximum bit-value in your bitmap. +func (tx *BadgerTx) Max(index, field, view string, shard uint64) (uint64, error) { + + prefix := badgerPrefix(index, field, view, shard) + seekto := badgerPrefix(index, field, view, shard+1) + + it := NewBadgerReverseIterator(tx, prefix, seekto) // this iterator is still open, when we commit/discard tx. + defer it.Close() + + hb, rc := it.Value() + lb := rc.Max() + + return hb<<16 | uint64(lb), nil +} + +// Min returns the smallest bit set in the fragment. If no bit is hot, +// the second return argument is false. +func (tx *BadgerTx) Min(index, field, view string, shard uint64) (uint64, bool, error) { + + // Seek can create many container iterators, thus the bf.Close() needClose list. + bf := &badgerFinder{tx: tx, index: index, field: field, view: view, shard: shard, needClose: make([]Closer, 0)} + defer bf.Close() + itr := roaring.NewIterator(bf) + + itr.Seek(0) + + // v is the bit we are operating on. + v, eof := itr.Next() + if eof { + return 0, false, nil + } + return v, true, nil +} + +// UnionInPlace unions all the others Bitmaps into a new Bitmap, and then writes it to the +// specified fragment. +func (tx *BadgerTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error { + + rbm, err := tx.RoaringBitmap(index, field, view, shard) + panicOn(err) + + rbm.UnionInPlace(others...) + // iterate over the containers that changed within rbm, and write them back to disk. + + it, found := rbm.Containers.Iterator(0) + _ = found // don't care about the value of found, because first containerKey might be > 0 + + for it.Next() { + containerKey, rc := it.Value() + + // TODO: only write the changed ones back, as optimization? + // Compare to ImportRoaringBits. + err := tx.PutContainer(index, field, view, shard, containerKey, rc) + panicOn(err) + } + return nil +} + +// CountRange returns the count of hot bits in the start, end range on the fragment. +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) + panicOn(err) + + defer citer.Close() // doesn't seem to be getting called. + if !found { + return 0, nil + } + // If range is entirely in one container then just count that range. + if skey == ekey { + citer.Next() + _, c := citer.Value() + return uint64(c.CountRange(int32(lowbits(start)), int32(lowbits(end)))), nil + } + + for citer.Next() { + k, c := citer.Value() + if k < skey { + citer.Close() + panic(fmt.Sprintf("should be impossible for k(%v) to be less than skey(%v). tx p=%p", k, skey, tx)) + } + + // k > ekey handles the case when start > end and where start and end + // are in different containers. Same container case is already handled above. + if k > ekey { + break + } + if k == skey { + n += uint64(c.CountRange(int32(lowbits(start)), roaring.MaxContainerVal+1)) + continue + } + if k < ekey { + n += uint64(c.N()) + continue + } + if k == ekey { + n += uint64(c.CountRange(0, int32(lowbits(end)))) + break + } + } + + return n, nil +} + +// OffsetRange creates a new roaring.Bitmap to return in other. For all the +// hot bits in [start, endx) of the chosen fragment, it stores +// them into other but with offset added to their bit position. +// The primary client is doing this, using ShardWidth, already; see +// fragment.rowFromStorage() in fragment.go. For example: +// +// data, err := tx.OffsetRange(f.index, f.field, f.view, f.shard, +// f.shard*ShardWidth, rowID*ShardWidth, (rowID+1)*ShardWidth) +// ^ offset ^ start ^ endx +// +// The start and endx arguments are container keys that have been shifted left by 16 bits; +// their highbits() will be taken to determine the actual container keys. This +// is done to conform to the roaring.OffsetRange() argument convention. +// +func (tx *BadgerTx) OffsetRange(index, field, view string, shard, offset, start, endx uint64) (other *roaring.Bitmap, err error) { + + // roaring does these three checks in its OffsetRange + if lowbits(offset) != 0 { + panic("offset must not contain low bits") + } + if lowbits(start) != 0 { + panic("range start must not contain low bits") + } + if lowbits(endx) != 0 { + panic("range end must not contain low bits") + } + + other = roaring.NewSliceBitmap() + off := highbits(offset) + hi0, hi1 := highbits(start), highbits(endx) + + // TODO(jea): question: do we have to account for ShardWidth here? what if the move goes + // beyond a shard? + + needle := badgerKey(index, field, view, shard, hi0) + prefix := badgerPrefix(index, field, view, shard) + + n2, pre2 := badgerKeyAndPrefix(index, field, view, shard, hi0) + if string(n2) != string(needle) { + panic(fmt.Sprintf("problem! n2(%v) != needle(%v), badgerKeyAndPrefix not consitent with badgerKey()", string(n2), string(needle))) + } + if string(pre2) != string(prefix) { + panic(fmt.Sprintf("problem! pre2(%v) != prefix(%v), badgerKeyAndPrefix not consitent with badgerKey()", string(pre2), string(prefix))) + } + + it := NewBadgerIterator(tx, prefix) // see OffsetRange() panic 'Only one iterator can be active at one time, for a RW txn + defer it.Close() + it.Seek(needle) + for ; it.it.ValidForPrefix(prefix); it.Next() { + item := it.it.Item() + bkey := item.Key() + k := badgerKeyExtractContainerKey(bkey) + + if uint64(k) >= hi1 { + break + } + destCkey := off + (k - hi0) + err := item.Value(func(v []byte) error { + + c := tx.toContainer(item.UserMeta(), v) + other.Containers.Put(destCkey, c.Freeze()) + + return nil + }) + if err != nil { + return nil, err + } + } + return other, nil +} + +// IncrementOpN increments the tx opcount by changedN +func (tx *BadgerTx) IncrementOpN(index, field, view string, shard uint64, changedN int) { + tx.opcount += changedN +} + +// ImportRoaringBits handles deletes by setting clear=true. +// rowSet[rowID] returns the number of bit changed on that rowID. +func (tx *BadgerTx) ImportRoaringBits(index, field, view string, shard uint64, itr roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) { + n := itr.Len() + if n == 0 { + return + } + rowSet = make(map[uint64]int) + + var currRow uint64 + + var oldC *roaring.Container + for itrKey, synthC := itr.NextContainer(); synthC != nil; itrKey, synthC = itr.NextContainer() { + if rowSize != 0 { + currRow = itrKey / rowSize + } + nsynth := int(synthC.N()) + if nsynth == 0 { + continue + } + // INVAR: nsynth > 0 + + oldC, err = tx.Container(index, field, view, shard, itrKey) + panicOn(err) + if err != nil { + return + } + + if oldC == nil || oldC.N() == 0 { + // no container at the itrKey in badger (or all zero container). + if clear { + // changed of 0 and empty rowSet is perfect, no need to change the defaults. + continue + } else { + + changed += nsynth + rowSet[currRow] += nsynth + + err = tx.PutContainer(index, field, view, shard, itrKey, synthC) + if err != nil { + return + } + continue + } + } + + if clear { + existN := oldC.N() // number of bits set in the old container + newC := oldC.Difference(synthC) + + // update rowSet and changes + if newC.N() == existN { + // INVAR: do changed need adjusting? nope. same bit count, + // so no change could have happened. + continue + } else { + changes := int(existN - newC.N()) + changed += changes + rowSet[currRow] -= changes + + if newC.N() == 0 { + err = tx.RemoveContainer(index, field, view, shard, itrKey) + if err != nil { + return + } + continue + } + err = tx.PutContainer(index, field, view, shard, itrKey, newC) + if err != nil { + return + } + continue + } + } else { + // setting bits + + existN := oldC.N() + if existN == roaring.MaxContainerVal+1 { + // completely full container already, set will do nothing. so changed of 0 default is perfect. + continue + } + if existN == 0 { + // can nsynth be zero? No, because of the continue/invariant above where nsynth > 0 + changed += nsynth + rowSet[currRow] += nsynth + err = tx.PutContainer(index, field, view, shard, itrKey, synthC) + if err != nil { + return + } + continue + } + + newC := oldC.UnionInPlace(synthC) + + if roaring.ContainerType(newC) == containerBitmap { + newC.Repair() // update the bit-count so .n is valid. b/c UnionInPlace doesn't update it. + } + if newC.N() != existN { + changes := int(newC.N() - existN) + changed += changes + rowSet[currRow] += changes + + err = tx.PutContainer(index, field, view, shard, itrKey, newC) + if err != nil { + panicOn(err) + return + } + continue + } + } + } + return +} + +////////////////////////////////// +// badger helper utility functions + +func highbits(v uint64) uint64 { return v >> 16 } +func lowbits(v uint64) uint16 { return uint16(v & 0xFFFF) } + +func toArray16(a []byte) []uint16 { + return (*[4096]uint16)(unsafe.Pointer(&a[0]))[: len(a)/2 : len(a)/2] +} +func toArray64(a []byte) []uint64 { + return (*[1024]uint64)(unsafe.Pointer(&a[0]))[:1024:1024] +} +func toInterval16(a []byte) []roaring.Interval16 { + return (*[2048]roaring.Interval16)(unsafe.Pointer(&a[0]))[: len(a)/4 : len(a)/4] +} + +// should really be exported from the pilosa/roaring package so we don't get out of sync... +const ( + containerNil byte = iota // no container + containerArray // slice of bit position values + containerBitmap // slice of 1024 uint64s + containerRun // container of run-encoded bits +) + +func (tx *BadgerTx) toContainer(typ byte, v []byte) (r *roaring.Container) { + + // 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 + // to do this if all of our use stays within the lifetime + // of the badger transaction we were started on. Hence: + // + // TODO: performance tuning might want w := v here, if we can guarantee no access to memory past the Tx lifetime. + // + // Problem is, at least some tests appear to not respect transaction boundaries... + w := make([]byte, len(v)) + copy(w, v) // green go test -v -run TestAPI_ImportColumnAttrs + //w := v // if instead of append we use v directly, it causes red: go test -v -run TestAPI_ImportColumnAttrs + + // register w so we can catch out-of-tx memory access + tx.ourAllocs = append(tx.ourAllocs, w) + + switch typ { + case containerArray: + c := roaring.NewContainerArray(toArray16(w)) + tx.ourContainers = append(tx.ourContainers, c) + return c + case containerBitmap: + c := roaring.NewContainerBitmap(-1, toArray64(w)) + tx.ourContainers = append(tx.ourContainers, c) + return c + case containerRun: + c := roaring.NewContainerRun(toInterval16(w)) + tx.ourContainers = append(tx.ourContainers, c) + return c + default: + panic(fmt.Sprintf("unknown container: %v", typ)) + } +} + +// fromArray16 converts to an 8KB page +func fromArray16(a []uint16) []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 { + return (*[8192]byte)(unsafe.Pointer(&a[0]))[:8192:8192] +} + +// fromInterval16 converts to 8KB page +func fromInterval16(a []roaring.Interval16) []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) { + if optionalUseThisTx == nil { + tx := w.NewBadgerTx(!writable) + defer tx.Rollback() + r = stringifiedBadgerKeysTx(tx) + return + } + + btx, ok := optionalUseThisTx.(*BadgerTx) + if !ok { + return fmt.Sprintf("", optionalUseThisTx) + } + r = stringifiedBadgerKeysTx(btx) + return +} + +// countBitsSet returns the number of bits set (or "hot") in +// the roaring container value found by the badgerKey() +// formatted bkey. +func (tx *BadgerTx) countBitsSet(bkey []byte) (n int) { + + item, err := tx.tx.Get(bkey) + if err == badger.ErrKeyNotFound { + panic(fmt.Sprintf("badger did not have value for bkey = '%v'", string(bkey))) + } + panicOn(err) + + var rc *roaring.Container + err = item.Value(func(v []byte) error { + // This func with val would only be called if item.Value encounters no error + rc = tx.toContainer(item.UserMeta(), v) + return nil + }) + panicOn(err) + + n = int(rc.N()) + return +} + +// stringifiedBadgerKeysTx reports all the badger keys and a +// corresponding blake3 hash viewable by txn within the entire +// badger database. +// It also reports how many bits are hot in the roaring container +// (how many bits are set, or 1 rather than 0). +// +// By convention, we must return the empty string if there +// are no keys present. The tests use this to confirm +// an empty database. +func stringifiedBadgerKeysTx(tx *BadgerTx) (r string) { + + r = "allkeys:[\n" + it := tx.tx.NewIterator(badger.DefaultIteratorOptions) + defer it.Close() + any := false + for it.Rewind(); it.Valid(); it.Next() { + any = true + item := it.Item() + bkey := item.Key() + key := string(bkey) + ckey := badgerKeyExtractContainerKey(bkey) + hash := "" + srbm := "" + err := item.Value(func(val []byte) error { + hash = blake3sum16(val) + ct := tx.toContainer(item.UserMeta(), val) + cts := roaring.NewSliceContainers() + cts.Put(ckey, ct) + rbm := &roaring.Bitmap{Containers: cts} + srbm = bitmapAsString(rbm) + return nil + }) + panicOn(err) + r += fmt.Sprintf("%v -> %v (%v hot)\n", key, hash, tx.countBitsSet(bkey)) + r += " ......." + srbm + "\n" + } + r += "]\n all-in-blake3:" + blake3sum16([]byte(r)) + + if !any { + return "" + } + return "badger-" + r +} + +func sliceToMap(slc []uint64) (m map[uint64]bool) { + m = make(map[uint64]bool) + for _, v := range slc { + m[v] = true + } + return +} + +// return A - B +func mapDiff(mapA, mapB map[uint64]bool) (r []int) { + for a := range mapA { + _, ok := mapB[a] + if !ok { + r = append(r, int(a)) + } + } + return +} + +func asInts(a []uint64) (r []int) { + r = make([]int, len(a)) + for i, v := range a { + r[i] = int(v) + } + return +} + +func bitmapAsString(rbm *roaring.Bitmap) (r string) { + r = "c(" + slc := rbm.Slice() + width := 0 + s := "" + for _, v := range slc { + if width == 0 { + s = fmt.Sprintf("%v", v) + } else { + s = fmt.Sprintf(", %v", v) + } + width += len(s) + r += s + if width > 70 { + r += ",\n" + width = 0 + } + } + if width == 0 && len(r) > 2 { + r = r[:len(r)-2] + } + return r + ")" +} + +func containerAsString(ckey uint64, rc *roaring.Container) (r string) { + rbm := roaring.NewBitmap() + rbm.Containers.Put(ckey, rc) + return bitmapAsString(rbm) +} + +var _ = containerAsString // happy linter + +func roaringBitmapDiff(a, b *roaring.Bitmap) error { + nA := a.Count() + nB := b.Count() + + slcA := a.Slice() + slcB := b.Slice() + + mapA := sliceToMap(slcA) + mapB := sliceToMap(slcB) + + AminusB := mapDiff(mapA, mapB) + BminusA := mapDiff(mapB, mapA) + + sort.Ints(AminusB) + sort.Ints(BminusA) + + res := fmt.Sprintf("nA = %v; nB = %v;\n", nA, nB) + ndiff := 0 + if nA != nB { + ndiff++ + } + + if len(AminusB) > 0 { + res += fmt.Sprintf("==> AminusB = (len %v) '%#v'; ", len(AminusB), AminusB) + ndiff++ + } + if len(BminusA) > 0 { + res += fmt.Sprintf("\n==> BminusA = (len %v) '%#v'; ", len(BminusA), BminusA) + ndiff++ + } + if ndiff == 0 { + return nil + } + res += fmt.Sprintf("\n ==> A = '%#v'\n ==> B = '%#v'", asInts(slcA), asInts(slcB)) + return errors.New(res) +} + +func dirAsString(path string) (r string) { + r = fmt.Sprintf("dump of directory '%v':\n", path) + files, err := ioutil.ReadDir(path) + panicOn(err) + for _, f := range files { + r += f.Name() + "\n" + } + return r +} + +var _ = dirAsString // happy linter diff --git a/badger_test.go b/badger_test.go new file mode 100644 index 000000000..354afb684 --- /dev/null +++ b/badger_test.go @@ -0,0 +1,1442 @@ +// 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. + +// +build !386 + +package pilosa + +import ( + "bytes" + "fmt" + "math" + "os" + "strconv" + "testing" + + "github.com/dgraph-io/badger/v2" + "github.com/pilosa/pilosa/v2/roaring" +) + +var _ = &roaring.Bitmap{} + +// helpers, each runs their own new txn, and commits if a change/delete +// was made. The txn is rolled back if it is just viewing the data. + +func badgerDBMustHaveBitvalue(dbwrap *BadgerDBWrapper, index, field, view string, shard uint64, bitvalue uint64) { + + tx := dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + exists, err := tx.Contains(index, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic(fmt.Sprintf("ARG bitvalue '%v' was NOT SET!!!", bitvalue)) + } + + tx.Rollback() +} + +func badgerDBMustNotHaveBitvalue(dbwrap *BadgerDBWrapper, index, field, view string, shard uint64, bitvalue uint64) { + + tx := dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + exists, err := tx.Contains(index, field, view, shard, bitvalue) + panicOn(err) + if exists { + panic(fmt.Sprintf("ARG bitvalue '%v' WAS SET but should not have been.!!!", bitvalue)) + } + tx.Rollback() +} + +func badgerDBMustSetBitvalue(dbwrap *BadgerDBWrapper, index, field, view string, shard uint64, putme uint64) { + tx := dbwrap.NewBadgerTx(writable) + + // add a bit + changed, err := tx.Add(index, field, view, shard, doBatched, putme) + if changed != 1 { + panic("should have 1 bit changed") + } + panicOn(err) + + exists, err := tx.Contains(index, field, view, shard, putme) + panicOn(err) + if !exists { + panic("ARG putme was NOT SET!!!") + } + panicOn(tx.Commit()) +} + +func badgerDBMustDeleteBitvalueContainer(dbwrap *BadgerDBWrapper, index, field, view string, shard uint64, putme uint64) { + tx := dbwrap.NewBadgerTx(writable) + hi := highbits(putme) + panicOn(tx.RemoveContainer(index, field, view, shard, hi)) + panicOn(tx.Commit()) +} + +func badgerDBMustDeleteBitvalue(dbwrap *BadgerDBWrapper, index, field, view string, shard uint64, putme uint64) { + tx := dbwrap.NewBadgerTx(writable) + _, err := tx.Remove(index, field, view, shard, putme) + panicOn(err) + panicOn(tx.Commit()) +} + +func mustOpenEmptyBadgerWrapper(path string) (w *BadgerDBWrapper, cleaner func()) { + var err error + fn := badgerPath(path) + panicOn(os.RemoveAll(fn)) + w, err = newBadgerDBWrapper(path) + panicOn(err) + + // verify it is empty + allkeys := w.StringifiedBadgerKeys(nil) + if allkeys != "" { + panic(fmt.Sprintf("freshly created database was not empty! had keys:'%v'", allkeys)) + } + + return w, func() { + os.RemoveAll(fn) + } +} + +// +func TestBadger_SetBitmap(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_SetBitmap") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + index, field, view, shard := "i", "f", "v", uint64(0) + bitvalue := uint64(0) + changed, err := tx.Add(index, field, view, shard, doBatched, bitvalue) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + + exists, err := tx.Contains(index, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic("ARG bitvalue was NOT SET!!!") + } + + err = tx.Commit() + panicOn(err) + + // + // commited, so should be visible outside the txn + // + + tx2 := dbwrap.NewBadgerTx(!writable) + exists, err = tx2.Contains(index, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic("ARG bitvalue was NOT SET!!! on tx2") + } + + n, err := tx2.Count(index, field, view, shard) + panicOn(err) + if n != 1 { + panic(fmt.Sprintf("should have Count 1; instead n = %v", n)) + } + tx2.Rollback() +} + +func TestBadger_OffsetRange(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_SetBitmap") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + + index, field, view, shard := "i", "f", "v", uint64(0) + bitvalue := uint64(1 << 20) + changed, err := tx.Add(index, field, view, shard, doBatched, bitvalue) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + + bitvalue2 := uint64(1<<20 + 1) + changed, err = tx.Add(index, field, view, shard, doBatched, bitvalue2) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + + exists, err := tx.Contains(index, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic("ARG bitvalue was NOT SET!!!") + } + exists, err = tx.Contains(index, field, view, shard, bitvalue2) + panicOn(err) + if !exists { + panic("ARG bitvalue2 was NOT SET!!!") + } + + err = tx.Commit() + panicOn(err) + + offset := uint64(0 << 20) + start := uint64(0 << 16) + endx := bitvalue + 1<<16 + + tx2 := dbwrap.NewBadgerTx(!writable) + rbm2, err := tx2.OffsetRange(index, field, view, shard, offset, start, endx) + panicOn(err) + tx2.Rollback() + + // should see our 1M value + s2 := bitmapAsString(rbm2) + expect2 := "c(1048576, 1048577)" + if s2 != expect2 { + panic(fmt.Sprintf("s2='%v', but expected '%v'", s2, expect2)) + } + + // now offset by 2M + offset = uint64(2 << 20) + tx3 := dbwrap.NewBadgerTx(!writable) + rbm3, err := tx3.OffsetRange(index, field, view, shard, offset, start, endx) + panicOn(err) + tx3.Rollback() + + //expect to see 3M == 3145728 + s3 := bitmapAsString(rbm3) + expect3 := "c(3145728, 3145729)" + + if s3 != expect3 { + panic(fmt.Sprintf("s3='%v', but expected '%v'", s3, expect3)) + } +} + +func TestBadger_Count_on_many_containers(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_Count_on_many_containers") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + putmeValues := []uint64{0, 2 << 16, 4 << 16} + + for _, putme := range putmeValues { + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + } + + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + + n, err := tx.Count(index, field, view, shard) + panicOn(err) + if int(n) != len(putmeValues) { + panic(fmt.Sprintf("expected Count of %v but got n=%v", len(putmeValues), n)) + } +} + +func TestBadger_Count_dense_containers(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_Count_dense_containers") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + tx := dbwrap.NewBadgerTx(writable) + + expected := 0 + // can't do more than about 100k writes per badger txn by default, so + // have to keep this kind of small. + // (See maxBatchCount:104857, maxBatchSize:10066329). + for i := uint64(0); i < (1<<16)+2; i += 2 { + changed, err := tx.Add(index, field, view, shard, doBatched, i) + panicOn(err) + if changed <= 0 { + panic("wat? should have changed") + } + expected++ + } + defer tx.Rollback() + + n, err := tx.Count(index, field, view, shard) + panicOn(err) + if int(n) != expected { + panic(fmt.Sprintf("expected Count of %v but got n=%v", expected, n)) + } +} + +func TestBadger_ContainerIterator_on_empty(t *testing.T) { + // iterate on empty container, should not find anything. + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_ContainerIterator") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + index, field, view, shard := "i", "f", "v", uint64(0) + bitvalue := uint64(0) + citer, found, err := tx.ContainerIterator(index, field, view, shard, bitvalue) + panicOn(err) + defer citer.Close() + if found { + panic("should not have found anything") + } + panicOn(err) +} + +func TestBadger_ContainerIterator_on_one_bit(t *testing.T) { + // set one bit, iterate. + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_ContainerIterator_on_one_bit") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + index, field, view, shard := "i", "f", "v", uint64(0) + + bitvalue := uint64(42) + + // add a bit + changed, err := tx.Add(index, field, view, shard, doBatched, bitvalue) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + + exists, err := tx.Contains(index, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic("ARG bitvalue was NOT SET!!!") + } + + // same Tx, continues in use. + + citer, found, err := tx.ContainerIterator(index, field, view, shard, highbits(bitvalue)) + if !found { + panic("ContainerIterator did not find the 42 bit") + } + panicOn(err) + defer citer.Close() + + loopCount := 0 + for citer.Next() { + key, container := citer.Value() + if key != 0 { + panic("42 should have had key 0") + } + if container == nil { + panic("container was nil") + } + if container.N() != 1 { + panic("put a bit in, but size of container was not 1") + } + if !container.Contains(lowbits(bitvalue)) { + panic("container did not have our bitvalue!") + } + loopCount++ + if loopCount > 0 { // happier linter + break + } + } + if loopCount != 1 { + panic("ContainerIterator did not return a citer that scanned our set bit") + } +} + +func TestBadger_badgerKey_badgerPrefix(t *testing.T) { + + // badgerPrefix() must agree with badgerKey(), but not have the key at the end. + // This is important for iteration over containers. + + index, field, view, shard := "i", "f", "v", uint64(0) + + // needle examples with the container-key extremes: + // "index:'i';field:'f';view:'v';shard:'0';key@00000000000000000000" // smallest + // "index:'i';field:'f';view:'v';shard:'0';key@18446744073709551615" // largest + needle := badgerKey(index, field, view, shard, 0) + + // prefix example: "index:'i';field:'f';view:'v';shard:'0';key@" + prefix := badgerPrefix(index, field, view, shard) + + if !bytes.HasPrefix(needle, prefix) { + panic(fmt.Sprintf("badgerPrefix() output '%v'was not a prefix of badgerKey() '%v'", string(needle), string(prefix))) + } + if len(prefix)+20 != len(needle) { + panic(fmt.Sprintf("badgerPrefix() output '%v'was 20 characters shorter than badgerKey() '%v'", string(needle), string(prefix))) + } + + // validate assumption that badgerKeyExtractContainerKey() makes about strconv.ParseUint() error reporting; + // for distinguishing prefixes from full keys. Even if the shard number is so large that the prefix + // starts with a legitimate decimal number. + shouldNotParse := "12345123451234';key@" + containerKey, err := strconv.ParseUint(shouldNotParse, 10, 64) + if err == nil { + panic(fmt.Sprintf("strconv.ParseUint should have returned an error parsing this string '%v'; instead we got '%v'", shouldNotParse, containerKey)) + } + + // verify panic on submitting a prefix + func() { + defer func() { + r := recover() + if r == nil { + panic(fmt.Sprintf("should have seen panic on call to badgerKeyExtractContainerKey(prefix='%v')", prefix)) + } + }() + badgerKeyExtractContainerKey(prefix) // should panic. + }() +} + +func TestBadger_ContainerIterator_on_one_bit_fail_to_find(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_ContainerIterator_on_one_bit") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + index, field, view, shard := "i", "f", "v", uint64(0) + + putme := uint64(1<<16) + 3 // in the key:1 container + searchme := putme + 1 + + // add a bit + changed, err := tx.Add(index, field, view, shard, doBatched, putme) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + + exists, err := tx.Contains(index, field, view, shard, putme) + panicOn(err) + if !exists { + panic("ARG putme was NOT SET!!!") + } + + // same Tx, continues in use. + + citer, found, err := tx.ContainerIterator(index, field, view, shard, highbits(searchme)) + if !found { + panic("ContainerIterator did not find the searchme") + } + defer citer.Close() + loopCount := 0 + for citer.Next() { + key, container := citer.Value() + if key != 1 { + panic("Containeriterator searching for highbits(searchme) should not have had a bit") + } + if container == nil { + panic("container was nil") + } + if container.N() != 1 { + panic("put a bit in, but size of container was not 1") + } + if container.Contains(lowbits(searchme)) { + panic("container should have putme but not our searchme!") + } + loopCount++ + // only want first pass. keep linter happy by avoiding raw break + if loopCount > 0 { + break + } + } + panicOn(err) +} + +func TestBadger_ContainerIterator_empty_iteration_loop(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_ContainerIterator_empty_iteration_loop") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + index, field, view, shard := "i", "f", "v", uint64(0) + + putme := uint64(1<<16) + 3 // in the key:1 container + searchme := uint64(1 << 17) // in the next container, key:2 + + // add a bit + changed, err := tx.Add(index, field, view, shard, doBatched, putme) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + + exists, err := tx.Contains(index, field, view, shard, putme) + panicOn(err) + if !exists { + panic("ARG putme was NOT SET!!!") + } + + // same Tx, continues in use. + + citer, found, err := tx.ContainerIterator(index, field, view, shard, highbits(searchme)) + panicOn(err) + if found { + panic("ContainerIterator found the searchme, when it should not have") + } + defer citer.Close() + if citer.Next() { + panic("expected no looping, 0 iterations, b/c started searchme past our data in putme") + } + + // expect to see a blow up from the citer.Value() call, verify that we do. + func() { + defer func() { + r := recover() + if r == nil { + panic("expected a panic from citer.Value() in this case") + } + }() + citer.Value() // should panic + }() + +} + +func TestBadger_ForEach_on_one_bit(t *testing.T) { + + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_ContainerIterator_on_one_bit") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + index, field, view, shard := "i", "f", "v", uint64(0) + + bitvalue := uint64(42) + + // add a bit + changed, err := tx.Add(index, field, view, shard, doBatched, bitvalue) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + + exists, err := tx.Contains(index, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic("ARG bitvalue was NOT SET!!!") + } + + // same Tx, continues in use. + count := 0 + err = tx.ForEach(index, field, view, shard, func(v uint64) error { + if v != bitvalue { + panic(fmt.Sprintf("bitvalue corrupt got %v want %v", v, bitvalue)) + } + count += 1 + return nil + }) + panicOn(err) + if count != 1 { + panic(fmt.Sprintf("Expected single iteration got %v ", count)) + } +} + +func TestBadger_RemoveContainer_one_bit_test(t *testing.T) { + + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_RemoveContainer_one_bit_test") + defer clean() + defer dbwrap.Close() + + index, field, view, shard := "i", "f", "v", uint64(0) + + putmeValues := []uint64{0, 13, 77, 1511} + + for _, putme := range putmeValues { + + // a) delete of whole container in a seperate txn. Commit should establish the deletion. + + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustDeleteBitvalueContainer(dbwrap, index, field, view, shard, putme) + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + + // b) deletion + rollback on the txn should restore the deleted bit + + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + + // delete, but rollback instead of commit + tx := dbwrap.NewBadgerTx(writable) + hi := highbits(putme) + panicOn(tx.RemoveContainer(index, field, view, shard, hi)) + tx.Rollback() + + // verify that the rollback undid the deletion. + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + + // c) within one Tx, after delete it should be gone as viewed within the txn. + tx = dbwrap.NewBadgerTx(writable) + hi = highbits(putme) + + exists, err := tx.Contains(index, field, view, shard, putme) + panicOn(err) + if !exists { + panic(fmt.Sprintf("ARG putme '%v' was NOT SET!!!", putme)) + } + + panicOn(tx.RemoveContainer(index, field, view, shard, hi)) + + exists, err = tx.Contains(index, field, view, shard, putme) + panicOn(err) + if exists { + panic(fmt.Sprintf("ARG putme '%v' was SET even after RemoveContiner in this txn.", putme)) + } + + tx.Rollback() + + // verify that the rollback undid the deletion. + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + // leave with clean slate + badgerDBMustDeleteBitvalueContainer(dbwrap, index, field, view, shard, putme) + } +} + +func TestBadger_Remove_one_bit_test(t *testing.T) { + + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_Remove_one_bit_test") + defer clean() + defer dbwrap.Close() + + index, field, view, shard := "i", "f", "v", uint64(0) + + putmeValues := []uint64{0, 13, 77, 1511} + + for _, putme := range putmeValues { + + // a) delete of whole container in a seperate txn. Commit should establish the deletion. + + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustDeleteBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + + // b) deletion + rollback on the txn should restore the deleted bit + + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + + // delete, but rollback instead of commit + tx := dbwrap.NewBadgerTx(writable) + hi, lo := highbits(putme), lowbits(putme) + _, _ = hi, lo + _, err := tx.Remove(index, field, view, shard, hi) + panicOn(err) + tx.Rollback() + + // verify that the rollback undid the deletion. + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + + // c) within one Tx, after delete it should be gone as viewed within the txn. + tx = dbwrap.NewBadgerTx(writable) + + exists, err := tx.Contains(index, field, view, shard, putme) + panicOn(err) + if !exists { + panic(fmt.Sprintf("ARG putme '%v' was NOT SET!!!", putme)) + } + + mustRemove(tx.Remove(index, field, view, shard, putme)) + + exists, err = tx.Contains(index, field, view, shard, putme) + panicOn(err) + if exists { + panic(fmt.Sprintf("ARG putme '%v' was SET even after Remove in this txn.", putme)) + } + + tx.Rollback() + + // verify that the rollback undid the deletion. + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + // leave with clean slate + badgerDBMustDeleteBitvalueContainer(dbwrap, index, field, view, shard, putme) + } +} + +func TestBadger_reverse_badger_iterator(t *testing.T) { + + // sanity check our understanding of Seek()-ing on reverse iterators. + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_reverse_badger_iterator") + defer clean() + defer dbwrap.Close() + + // add 0, 1, 2 to badgerdb as keys (and same value). + err := dbwrap.db.Update(func(txn *badger.Txn) error { + for i := 0; i < 3; i++ { + kv := []byte(fmt.Sprintf("a:%v", i)) + err := txn.Set(kv, kv) + panicOn(err) + } + return nil + }) + panicOn(err) + + tx := dbwrap.db.NewTransaction(!writable) + + opts := badger.DefaultIteratorOptions + opts.PrefetchValues = false // else by default, pre-fetches the 1st 100 values, which would be slow. + opts.Reverse = true + it := tx.NewIterator(opts) + it.Rewind() + if !it.Valid() { + panic("invalid reversed iterator?") + } + a := []byte("a:3") + it.Seek(a) + if !it.Valid() { + panic("invalid reversed iterator after seek") + } + + it.Next() + if !it.Valid() { + panic("invalid reversed iterator after seek and next") + } + item := it.Item() + + err = item.Value(func(val []byte) error { + // This func with val would only be called if item.Value encounters no error. + if string(val) != "a:1" { + panic(fmt.Sprintf("we are in trouble, should have gotten 'a:1' but instead got '%v'", string(val))) + } + return nil + }) + panicOn(err) +} + +func TestBadger_Max_on_many_containers(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_Max_on_many_containers") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + putmeValues := []uint64{0, 2 << 16, 4 << 16} + + for _, putme := range putmeValues { + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + } + + tx := dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + + max, err := tx.Max(index, field, view, shard) + panicOn(err) + expected := putmeValues[len(putmeValues)-1] + if max != expected { + panic(fmt.Sprintf("expected Max() of %v but got max=%v", expected, max)) + } +} + +func TestBadger_Min_on_many_containers(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_Min_on_many_containers") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + // verify no containers flag works + tx := dbwrap.NewBadgerTx(!writable) + min, containersExist, err := tx.Min(index, field, view, shard) + _ = min + panicOn(err) + if containersExist { + panic("no containers should exist") + } + tx.Rollback() + + putmeValues := []uint64{3, 2 << 16, 4 << 16} + + for _, putme := range putmeValues { + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + } + + tx = dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + + min, containersExist, err = tx.Min(index, field, view, shard) + panicOn(err) + if !containersExist { + panic("containers should exist") + } + expected := putmeValues[0] + if min != expected { + panic(fmt.Sprintf("expected Min() of %v but got min=%v", expected, min)) + } +} + +func TestBadger_CountRange_on_many_containers(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_CountRange_on_many_containers") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + // verify no containers flag works + tx := dbwrap.NewBadgerTx(!writable) + n, err := tx.CountRange(index, field, view, shard, 0, math.MaxUint64) + panicOn(err) + if n != 0 { + panic("no containers should exist") + } + tx.Rollback() + + putmeValues := []uint64{3, 2 << 16, 4 << 16} + + for _, putme := range putmeValues { + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + } + + tx = dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + + n, err = tx.CountRange(index, field, view, shard, 0, math.MaxUint64) + panicOn(err) + if n == 0 { + panic("containers should exist") + } + expected := uint64(len(putmeValues)) + if n != expected { + panic(fmt.Sprintf("expected CountRange() of %v but got n=%v", expected, n)) + } +} + +func TestBadger_CountRange_middle_container(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_CountRange_middle_container") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + putmeValues := []uint64{3, 2 << 16, 4 << 16} + + for _, putme := range putmeValues { + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + } + + tx := dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + + // pick out just the middle container with the 1 bit set on it. + n, err := tx.CountRange(index, field, view, shard, 4, (2<<16)+1) + panicOn(err) + if n != 1 { + panic("middle 1 bit container should exist") + } +} + +func TestBadger_CountRange_many_middle_container(t *testing.T) { + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_CountRange_many_middle_container") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + putmeValues := []uint64{3, 2 << 16, 4 << 16} + + for _, putme := range putmeValues { + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + } + + tx := dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + + // get them all + n, err := tx.CountRange(index, field, view, shard, 0, (4<<16)+1) + panicOn(err) + if n != 3 { + panic("count should have been all 3 bits") + } +} + +func TestBadger_UnionInPlace(t *testing.T) { + + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_UnionInPlace") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + putmeValues := []uint64{3, 2 << 16} + + others := roaring.NewBitmap() + others2 := roaring.NewBitmap() + others3 := roaring.NewBitmap() + // populate others with putmeValues +1 into others + + for _, putme := range putmeValues { + badgerDBMustNotHaveBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + badgerDBMustHaveBitvalue(dbwrap, index, field, view, shard, putme) + } + + tx2 := dbwrap.NewBadgerTx(!writable) + n, err := tx2.Count(index, field, view, shard) + panicOn(err) + if n != 2 { + panic("should have 2 bits set") + } + tx2.Rollback() + + for _, putme := range putmeValues { + mustAddR(others.Add(putme)) // should not change count, b/c putme already in the rbm + mustAddR(others.Add(putme + 1)) + mustAddR(others2.Add(putme + 2)) + } + mustAddR(others3.Add(4 << 16)) // outside the 2<<16 container + + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + err = tx.UnionInPlace(index, field, view, shard, others, others2, others3) + panicOn(err) + + // end game, check we got the union. + rbm, err := tx.RoaringBitmap(index, field, view, shard) + panicOn(err) + n = rbm.Count() + if n != 7 { + panic("should have a total 3 + 3 +1 = 7 bits set on the containers") + } +} + +func TestBadger_RoaringBitmap(t *testing.T) { + + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_RoaringBitmap") + defer clean() + defer dbwrap.Close() + index, field, view, shard := "i", "f", "v", uint64(0) + + expected := uint64(3) + putme := expected + badgerDBMustSetBitvalue(dbwrap, index, field, view, shard, putme) + + tx := dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + + rbm, err := tx.RoaringBitmap(index, field, view, shard) + panicOn(err) + + slc := rbm.Slice() + if slc[0] != uint64(expected) { + panic(fmt.Sprintf("should have gotten %v back", expected)) + } +} + +func TestBadger_reverse_badger_iterator_and_prefix_valid(t *testing.T) { + + // does a reverse iterator and ValidForPrefix behave like we expect it too? + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_reverse_badger_iterator_and_prefix_valid") + defer clean() + defer dbwrap.Close() + + // add 0, 1, 2 to badgerdb as keys (and same value). + err := dbwrap.db.Update(func(txn *badger.Txn) error { + for _, prefix := range []string{"a", "b", "c"} { + for i := 0; i < 3; i++ { + kv := []byte(fmt.Sprintf("%v:%v", prefix, i)) + err := txn.Set(kv, kv) + panicOn(err) + } + } + return nil + }) + panicOn(err) + //vv("stringifiedBadgerKeys(db) = '%v'", stringifiedBadgerKeys(dbwrap.db)) + // allkeys:["a:0", "a:1", "a:2", "b:0", "b:1", "b:2", "c:0", "c:1", "c:2", ]' + + tx := dbwrap.NewBadgerTx(!writable) + + prefix := []byte("b:") + it := NewBadgerIterator(tx, prefix) + + if !it.it.Valid() { + panic("why is underlying badger it not valid here?") + } + results := "" + for it.Next() { + item := it.it.Item() + sk := string(item.Key()) + results += sk + ", " + } + expected := `b:0, b:1, b:2, ` + if results != expected { + panic(fmt.Sprintf("observed: '%v' but expected: '%v'", results, expected)) + } + it.Close() + + // now reversed + seekto := []byte("c:") + rit := NewBadgerReverseIterator(tx, prefix, seekto) // Seeks("b:") goes to b:0 + defer rit.Close() + + if !rit.it.Valid() { + panic("why is underlying badger it not valid here?") + } + results = "" + for rit.Next() { + item := rit.it.Item() + sk := string(item.Key()) + results += sk + ", " + } + expected = `b:2, b:1, b:0, ` + if results != expected { + panic(fmt.Sprintf("observed: '%v' but expected: '%v'", results, expected)) + } + rit.Close() +} + +func TestBadger_just_reverse_badger_iterator_and_prefix_valid(t *testing.T) { + + // does a reverse iterator and ValidForPrefix behave like we expect it too? + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_reverse_badger_iterator_and_prefix_valid") + defer clean() + defer dbwrap.Close() + + // add 0, 1, 2 to badgerdb as keys (and same value). + err := dbwrap.db.Update(func(txn *badger.Txn) error { + for _, prefix := range []string{"a", "b", "c"} { + for i := 0; i < 3; i++ { + kv := []byte(fmt.Sprintf("%v:%v", prefix, i)) + err := txn.Set(kv, kv) + panicOn(err) + } + } + return nil + }) + panicOn(err) + //vv("stringifiedBadgerKeys(db) = '%v'", stringifiedBadgerKeys(dbwrap.db)) + // allkeys:["a:0", "a:1", "a:2", "b:0", "b:1", "b:2", "c:0", "c:1", "c:2", ]' + + tx := dbwrap.NewBadgerTx(!writable) + + seekto := []byte("c:") + prefix := []byte("b:") + // now reversed + rit := NewBadgerReverseIterator(tx, prefix, seekto) // Seeks("b:") goes to b:0 + defer rit.Close() + + if !rit.it.Valid() { + panic("why is underlying badger rit not valid here?") + } + results := "" + for rit.Next() { + item := rit.it.Item() + sk := string(item.Key()) + results += sk + ", " + } + expected := `b:2, b:1, b:0, ` + if results != expected { + panic(fmt.Sprintf("observed: '%v' but expected: '%v'", results, expected)) + } + rit.Close() +} + +func TestBadger_ImportRoaringBits(t *testing.T) { + + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_ImportRoaringBits") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + index, field, view, shard := "i", "f", "v", uint64(0) + + //bitvalue := uint64(42) + + // get some roaring bits, get an itr RoaringIterator from them + rowSize := uint64(0) + //bits := []uint64{0} + bits := []uint64{0, 2, 5, 1<<16 + 1, 2 << 16} + data := getTestBitmapAsRawRoaring(bits...) + itr, err := roaring.NewRoaringIterator(data) + panicOn(err) + clear := false + logme := false + + changed, rowSet, err := tx.ImportRoaringBits(index, field, view, shard, itr, clear, logme, rowSize) + _ = rowSet + if changed != len(bits) { + panic(fmt.Sprintf("should have changed %v bits: changed='%v', rowSet='%#v', err='%v'", len(bits), changed, rowSet, err)) + } + panicOn(err) + + for _, v := range bits { + exists, err := tx.Contains(index, field, view, shard, v) + panicOn(err) + if !exists { + panic(fmt.Sprintf("ARG bitvalue was NOT SET!!! '%v'", v)) + } + } + + // now test the union in place with the same set gives no change. + + changed, rowSet, err = tx.ImportRoaringBits(index, field, view, shard, itr, clear, logme, rowSize) + _ = rowSet + if changed != 0 { + panic(fmt.Sprintf("should have not changed any bits on the second import, but we see changed='%v', rowSet='%#v', err='%v'", changed, rowSet, err)) + } + panicOn(err) + + for _, v := range bits { + exists, err := tx.Contains(index, field, view, shard, v) + panicOn(err) + if !exists { + panic(fmt.Sprintf("ARG bitvalue was NOT SET!!! '%v'", v)) + } + } + + // now test the clear path + clear = true + + for _, v := range bits { + // clear 1 bit at a time + data := getTestBitmapAsRawRoaring(v) + itr, err := roaring.NewRoaringIterator(data) + panicOn(err) + + changed, rowSet, err := tx.ImportRoaringBits(index, field, view, shard, itr, clear, logme, rowSize) + _ = rowSet + if changed != 1 { + panic(fmt.Sprintf("should have changed 1 bit: '%v', rowSet='%#v', err='%v'", changed, rowSet, err)) + } + panicOn(err) + } + n, err := tx.Count(index, field, view, shard) + panicOn(err) + if n != 0 { + panic(fmt.Sprintf("n = %v not zero so the clearbits didn't happen!", n)) + } + allkeys := stringifiedBadgerKeysTx(tx) + + // should have no keys + if allkeys != "" { + panic("badger should have no keys now") + } +} + +func TestBadger_ImportRoaringBits_set_nonoverlapping_bits(t *testing.T) { + + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_ImportRoaringBits_set_nonoverlapping_bits") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + index, field, view, shard := "i", "f", "v", uint64(0) + + // get some roaring bits, get an itr RoaringIterator from them + rowSize := uint64(0) + //bits := []uint64{0} + bits := []uint64{0, 2, 1 << 16, 1<<16 + 2} + data := getTestBitmapAsRawRoaring(bits...) + itr, err := roaring.NewRoaringIterator(data) + panicOn(err) + + bits2 := []uint64{1, 2, 3, 1<<16 + 1, 1<<16 + 2, 1<<16 + 3} //, 5, 1<<16 + 1, 2 << 16} + data2 := getTestBitmapAsRawRoaring(bits2...) + itr2, err := roaring.NewRoaringIterator(data2) + panicOn(err) + + clear := false + logme := false + + changed, rowSet, err := tx.ImportRoaringBits(index, field, view, shard, itr, clear, logme, rowSize) + _ = rowSet + if changed != len(bits) { + panic(fmt.Sprintf("should have changed %v bits: changed='%v', rowSet='%#v', err='%v'", len(bits), changed, rowSet, err)) + } + panicOn(err) + + for _, v := range bits { + exists, err := tx.Contains(index, field, view, shard, v) + panicOn(err) + if !exists { + panic(fmt.Sprintf("ARG bitvalue was NOT SET!!! '%v'", v)) + } + } + + // now import the 2nd, overlapping set and set them. + + changed, rowSet, err = tx.ImportRoaringBits(index, field, view, shard, itr2, clear, logme, rowSize) + _ = rowSet + if changed != 4 { + panic(fmt.Sprintf("should have changed 2 bits: the 1 and the 3, but we see changed='%v', rowSet='%#v', err='%v'", changed, rowSet, err)) + } + panicOn(err) +} + +func TestBadger_ImportRoaringBits_clear_nonoverlapping_bits(t *testing.T) { + + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_ImportRoaringBits_clear_nonoverlapping_bits") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + defer tx.Rollback() + index, field, view, shard := "i", "f", "v", uint64(0) + + // get some roaring bits, get an itr RoaringIterator from them + rowSize := uint64(0) + //bits := []uint64{0} + bits := []uint64{0, 2, 1 << 16, 1<<16 + 2} //, 5, 1<<16 + 1, 2 << 16} + data := getTestBitmapAsRawRoaring(bits...) + itr, err := roaring.NewRoaringIterator(data) + panicOn(err) + + bits2 := []uint64{1, 2, 3, 1<<16 + 1, 1<<16 + 2, 1<<16 + 3} //, 5, 1<<16 + 1, 2 << 16} + data2 := getTestBitmapAsRawRoaring(bits2...) + itr2, err := roaring.NewRoaringIterator(data2) + panicOn(err) + + clear := false + logme := false + + changed, rowSet, err := tx.ImportRoaringBits(index, field, view, shard, itr, clear, logme, rowSize) + _ = rowSet + if changed != len(bits) { + panic(fmt.Sprintf("should have changed %v bits: changed='%v', rowSet='%#v', err='%v'", len(bits), changed, rowSet, err)) + } + panicOn(err) + + for _, v := range bits { + exists, err := tx.Contains(index, field, view, shard, v) + panicOn(err) + if !exists { + panic(fmt.Sprintf("ARG bitvalue was NOT SET!!! '%v'", v)) + } + } + + // now import the 2nd overlapping set and clear them. + clear = true + + changed, rowSet, err = tx.ImportRoaringBits(index, field, view, shard, itr2, clear, logme, rowSize) + _ = rowSet + if changed != 2 { + panic(fmt.Sprintf("should have changed 1 bit: the 2, but we see changed='%v', rowSet='%#v', err='%v'", changed, rowSet, err)) + } + panicOn(err) + + n, err := tx.Count(index, field, view, shard) + panicOn(err) + if n != 2 { // just the 0 and the 1<<16 bits should be left set. + panic(fmt.Sprintf("n = %v not 2 so the clearbits didn't happen!", n)) + } + +} + +func getTestBitmapAsRawRoaring(bitsToSet ...uint64) []byte { + b := roaring.NewBitmap() + changed := b.DirectAddN(bitsToSet...) + n := len(bitsToSet) + if changed != n { + panic(fmt.Sprintf("changed=%v but bitsToSet len = %v", changed, n)) + } + buf := bytes.NewBuffer(make([]byte, 0, 100000)) + _, err := b.WriteTo(buf) + if err != nil { + panic(err) + } + return buf.Bytes() +} + +func TestBadger_DeleteIndex(t *testing.T) { + + // setup + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_DeleteIndex") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + bitvalue := uint64(777) + index, field, view, shard := "i", "f", "v", uint64(0) + bits := []uint64{0, 3, 1 << 16, 1<<16 + 3, 8 << 16} + for _, v := range bits { + changed, err := tx.Add(index, field, view, shard, doBatched, v) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + } + + index2 := "i2" // should not be deleted, even though it shares a prefix with 'i' + changed, err := tx.Add(index2, field, view, shard, doBatched, bitvalue) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + + for _, v := range bits { + exists, err := tx.Contains(index, field, view, shard, v) + panicOn(err) + if !exists { + panic("ARG bitvalue was NOT SET!!!") + } + } + exists, err := tx.Contains(index2, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic("ARG bitvalue was NOT SET!!! on index2") + } + err = tx.Commit() + panicOn(err) + + // end of setup + err = dbwrap.DeleteIndex(index) + panicOn(err) + + tx = dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + exists, err = tx.Contains(index2, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic(fmt.Sprintf("after delete of '%v', another index '%v' was gone too?!?", index, index2)) + } + + for _, v := range bits { + exists, err = tx.Contains(index, field, view, shard, v) + panicOn(err) + if exists { + allkeys := stringifiedBadgerKeysTx(tx) + panic(fmt.Sprintf("after delete of index '%v', bit v=%v was not gone?!?; allkeys='%v'", index, v, allkeys)) + } + } +} + +func TestBadger_DeleteIndex_over100k(t *testing.T) { + + // setup + dbwrap, clean := mustOpenEmptyBadgerWrapper("TestBadger_DeleteIndex_over100k") + defer clean() + defer dbwrap.Close() + tx := dbwrap.NewBadgerTx(writable) + bitvalue := uint64(777) + index, field, view, shard := "i", "f", "v", uint64(0) + limit := uint64(100002) // default batch size in DeleteIndex is 100k keys per delete transaction. + //limit := uint64(101) + for v := uint64(1); v < limit; v++ { + // shift by << 16 to get into a different shard + changed, err := tx.Add(index, field, view, shard, doBatched, v<<16) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + if v%100000 == 0 { + panicOn(tx.Commit()) + tx = dbwrap.NewBadgerTx(writable) + } + } + + index2 := "i2" // should not be deleted, even though it shares a prefix with 'i' + changed, err := tx.Add(index2, field, view, shard, doBatched, bitvalue) + if changed <= 0 { + panic("should have changed") + } + panicOn(err) + err = tx.Commit() + panicOn(err) + + // end of setup + err = dbwrap.DeleteIndex(index) + panicOn(err) + + tx = dbwrap.NewBadgerTx(!writable) + defer tx.Rollback() + exists, err := tx.Contains(index2, field, view, shard, bitvalue) + panicOn(err) + if !exists { + panic(fmt.Sprintf("after delete of '%v', another index '%v' was gone too?!?", index, index2)) + } + + for v := uint64(0); v < limit; v++ { + exists, err = tx.Contains(index, field, view, shard, v<<16) + panicOn(err) + if exists { + allkeys := stringifiedBadgerKeysTx(tx) + panic(fmt.Sprintf("after delete of index '%v', bit v=%v was not gone?!?; allkeys='%v'", index, v, allkeys)) + } + } +} + +func TestBitmapDiff(t *testing.T) { + a := roaring.NewBitmap() + b := roaring.NewBitmap() + err := roaringBitmapDiff(a, b) + panicOn(err) + err = roaringBitmapDiff(b, a) + panicOn(err) + + a = roaring.NewBitmap(0) + err = roaringBitmapDiff(a, b) + if err == nil { + panic("diff should have been noticed") + } + err = roaringBitmapDiff(b, a) + if err == nil { + panic("diff should have been noticed") + } + + b = roaring.NewBitmap(0) + err = roaringBitmapDiff(a, b) + panicOn(err) + err = roaringBitmapDiff(b, a) + panicOn(err) + + a = roaring.NewBitmap() + + err = roaringBitmapDiff(a, b) + if err == nil { + panic("diff should have been noticed") + } + err = roaringBitmapDiff(b, a) + if err == nil { + panic("diff should have been noticed") + } + + a = roaring.NewBitmap(1) + + err = roaringBitmapDiff(a, b) + if err == nil { + panic("diff should have been noticed") + } + err = roaringBitmapDiff(b, a) + if err == nil { + panic("diff should have been noticed") + } + + b = roaring.NewBitmap(1, 2) + a = roaring.NewBitmap(0, 1) + + err = roaringBitmapDiff(a, b) + if err == nil { + panic("diff should have been noticed") + } + err = roaringBitmapDiff(b, a) + if err == nil { + panic("diff should have been noticed") + } + + b = roaring.NewBitmap(1, 2, 3) + a = roaring.NewBitmap(1, 2) + + err = roaringBitmapDiff(a, b) + if err == nil { + panic("diff should have been noticed") + } + err = roaringBitmapDiff(b, a) + if err == nil { + panic("diff should have been noticed") + } +} + +// mustAddR is a helper for calling roaring.Container.Add() in tests to +// keep the linter happy that we are checking the error. +func mustAddR(changed bool, err error) { + panicOn(err) +} + +// mustRemove is a helper for calling Tx.Remove() in tests to +// keep the linter happy that we are checking the error. +func mustRemove(changeCount int, err error) { + panicOn(err) +} diff --git a/blake3.go b/blake3.go new file mode 100644 index 000000000..22e3da86b --- /dev/null +++ b/blake3.go @@ -0,0 +1,95 @@ +// 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 ( + "encoding/binary" + "fmt" + "sync" + + cryptorand "crypto/rand" + "github.com/zeebo/blake3" +) + +// Blake3Hasher is a thread/goroutine safe way to +// obtain a blake3 cryptographic hash of input []byte. +// Reference https://github.com/BLAKE3-team/BLAKE3 +// suggests it is 6x faster than BLAKE2B. +// The Go github.com/zeebo/blake3 version is +// AVX2 and SSE4.1 accelerated. +type Blake3Hasher struct { + hasher *blake3.Hasher + hasherMu sync.Mutex +} + +// NewBlake3Hasher returns a new Blake3Hasher. +func NewBlake3Hasher() *Blake3Hasher { + return &Blake3Hasher{ + hasher: blake3.New(), + } +} + +// CryptoHash writes the blake3 cryptographic hash of +// input into buffer and returns it. +// Like the standard libary's hash.Hash interface's Sum() method, +// the buffer is re-used and overwritten +// to avoid allocation. The caller determines the byte length of +// the outputCryptohash by the size of the supplied buffer +// slice, and this will be exactly equal to the supplies bytes. +// In this way, shorter or longer hashes can be provided as +// needed. +func (w *Blake3Hasher) CryptoHash(input []byte, buffer []byte) (outputCryptohash []byte) { + w.hasherMu.Lock() + w.hasher.Reset() + + // "Write implements part of the hash.Hash interface. It never returns an error." + // -- https://godoc.org/github.com/zeebo/blake3#Hasher.Write + _, _ = w.hasher.Write(input) + + // Digest.Read reads data from the hasher into buffer. + // "It always fills the entire buffer and never errors." + // -- https://godoc.org/github.com/zeebo/blake3#Digest + _, _ = w.hasher.Digest().Read(buffer) + + // no chance of panic, so avoid any defer cost. + w.hasherMu.Unlock() + + return buffer +} + +// blake3sum16 might be slower because we allocate a new hasher every time, but +// it is more conenient for writing debug code. It returns +// a 16 byte hash as a hexidecimal string. +func blake3sum16(input []byte) string { + hasher := blake3.New() + + _, _ = hasher.Write(input) + var buf [16]byte + _, _ = hasher.Digest().Read(buf[0:]) + + return fmt.Sprintf("%x", buf) +} + +// cryptoRandInt64 uses crypto/rand to get an random int64 +func cryptoRandInt64() int64 { + c := 8 + b := make([]byte, c) + _, err := cryptorand.Read(b) + if err != nil { + panic(err) + } + r := int64(binary.LittleEndian.Uint64(b)) + return r +} diff --git a/blake3_test.go b/blake3_test.go new file mode 100644 index 000000000..f2c85cb93 --- /dev/null +++ b/blake3_test.go @@ -0,0 +1,47 @@ +// 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 ( + "fmt" + "testing" + + "encoding/hex" +) + +func TestBlake3Hasher(t *testing.T) { + + hasher := NewBlake3Hasher() + hash := make([]byte, 16) + input := []byte("hello world") + hash = hasher.CryptoHash(input, hash) + expected := "d74981efa70a0c880b8d8c1985d075db" + observed := hex.EncodeToString(hash) + if observed != expected { + panic(fmt.Sprintf("expected hash:'%v' but observed hash '%v'", expected, observed)) + } + + obs2 := blake3sum16(input) + if obs2 != expected { + panic(fmt.Sprintf("expected hash:'%v' but observed hash from blake2sum16: '%v'", expected, obs2)) + } +} + +func TestCryptoRandInt64(t *testing.T) { + rnd := cryptoRandInt64() + if rnd == 0 { + panic("cryptoRandInt64() gave 0, very high odds it has broken") + } +} diff --git a/bluegreentx.go b/bluegreentx.go new file mode 100644 index 000000000..dd58c69fd --- /dev/null +++ b/bluegreentx.go @@ -0,0 +1,421 @@ +// 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 ( + "fmt" + + "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. +type blueGreenTx struct { + a Tx + b Tx // b's output is returned + + idx *Index +} + +func newBlueGreenTx(a, b Tx, idx *Index) *blueGreenTx { + return &blueGreenTx{a: a, b: b, idx: idx} +} + +var _ = newBlueGreenTx // keep linter happy + +var _ Tx = (*blueGreenTx)(nil) + +func (c *blueGreenTx) Readonly() bool { + a := c.a.Readonly() + b := c.b.Readonly() + if a != b { + panic(fmt.Sprintf("a=%v, but b =%v", a, b)) + } + return b +} + +func (c *blueGreenTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator { + return c.b.NewTxIterator(index, field, view, shard) +} + +func (c *blueGreenTx) Pointer() string { + return fmt.Sprintf("%p", c) +} + +func (c *blueGreenTx) IncrementOpN(index, field, view string, shard uint64, changedN int) { + c.a.IncrementOpN(index, field, view, shard, changedN) + c.b.IncrementOpN(index, field, view, shard, changedN) +} + +func (c *blueGreenTx) Rollback() { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Rollback() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + c.a.Rollback() + c.b.Rollback() +} + +func (c *blueGreenTx) Commit() error { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Commit() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + errA := c.a.Commit() + _ = errA + errB := c.b.Commit() + + compareErrors(errA, errB) + return errB +} + +func (c *blueGreenTx) RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see RoaringBitmap() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + a, errA := c.a.RoaringBitmap(index, field, view, shard) + _, _ = a, errA + b, errB := c.b.RoaringBitmap(index, field, view, shard) + compareErrors(errA, errB) + return b, errB +} + +func (c *blueGreenTx) Container(index, field, view string, shard uint64, key uint64) (ct *roaring.Container, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Container() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + a, errA := c.a.Container(index, field, view, shard, key) + b, errB := c.b.Container(index, field, view, shard, key) + compareErrors(errA, errB) + err = a.BitwiseCompare(b) + panicOn(err) + + return b, errB +} + +func (c *blueGreenTx) PutContainer(index, field, view string, shard uint64, key uint64, rc *roaring.Container) error { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see PutContainer() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + errA := c.a.PutContainer(index, field, view, shard, key, rc) + errB := c.b.PutContainer(index, field, view, shard, key, rc) + compareErrors(errA, errB) + + /* draft idea of how to check the full databases afterwards: + hashA := c.a.RootHashString() + hashB := c.b.RootHashString() + if hashA != hashB { + panic(fmt.Sprintf("hashA = '%v' but hashB = '%v'", hashA, hashB)) + } + */ + return errB +} + +func (c *blueGreenTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see ImportRoaringBits() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + + // remember where the iterator started, so we can replay it a second time. + rit2 := rit.Clone() + + changedA, rowSetA, errA := c.a.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize) + + changedB, rowSetB, errB := c.b.ImportRoaringBits(index, field, view, shard, rit2, clear, log, rowSize) + + if changedA != changedB { + panic(fmt.Sprintf("changedA = %v, but changedB = %v", changedA, changedB)) + } + if len(rowSetA) != len(rowSetB) { + panic(fmt.Sprintf("rowSetA = %#v, but rowSetB = %#v", rowSetA, rowSetB)) + } + for k, va := range rowSetA { + vb, ok := rowSetB[k] + if !ok { + panic(fmt.Sprintf("diff on key '%v': present in rowSetA, but not in rowSet B. rowSetA = %#v, but rowSetB = %#v", k, rowSetA, rowSetB)) + } + if va != vb { + panic(fmt.Sprintf("diff on key '%v', rowSetA has value '%v', but rowSetB has value '%v'", k, va, vb)) + } + } + + compareErrors(errA, errB) + + //compareDatabases(c.a, c.b) + return changedB, rowSetB, errB +} + +/* // TODO: get a database-wide checksum working +func compareDatabases(a, b Tx) { + + index, field, view, shard := "i", "f", "v", uint64(0) + + ha, errA := a.WholeDatabaseBlake3Hash(index, field, view, shard) + panicOn(errA) + hb, errB := b.WholeDatabaseBlake3Hash(index, field, view, shard) + panicOn(errB) + + if ha != hb { + panic(fmt.Sprintf("a.WholeDatabaseBlake3Hash(%T) = '%v' but b.WholeDatabaseBlake3Hash(%T) = '%v'", a, ha, b, hb)) + } +} +*/ + +func (c *blueGreenTx) RemoveContainer(index, field, view string, shard uint64, key uint64) error { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see RemoveContainer() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + errA := c.a.RemoveContainer(index, field, view, shard, key) + errB := c.b.RemoveContainer(index, field, view, shard, key) + compareErrors(errA, errB) + return errB +} + +func (c *blueGreenTx) UseRowCache() bool { + return c.b.UseRowCache() +} + +func (c *blueGreenTx) Add(index, field, view string, shard uint64, batched bool, a ...uint64) (changeCount int, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Add() panic '%v' for index='%v', field='%v', view='%v', shard='%v' at '%v'", r, index, field, view, shard, stack()) + panic(r) + } + }() + + // must copy a before calling Add(), since RoaringTx.Add() uses roaring.DirectAddN() + // which modifies the input array a. + a2 := make([]uint64, len(a)) + copy(a2, a) + + ach, errA := c.a.Add(index, field, view, shard, batched, a...) + _, _ = ach, errA + + bch, errB := c.b.Add(index, field, view, shard, batched, a2...) + + if ach != bch { + panic(fmt.Sprintf("Add() difference, ach=%v, but bch=%v; errA='%v'; errB='%v'", ach, bch, errA, errB)) + } + compareErrors(errA, errB) + return bch, errB +} + +func compareErrors(errA, errB error) { + switch { + case errA == nil && errB == nil: + // OK + case errA == nil: + panic(fmt.Sprintf("errA is nil, but errB = %#v", errB)) + case errB == nil: + panic(fmt.Sprintf("errB is nil, but errA = %#v", errA)) + default: + ae := errA.Error() + be := errB.Error() + if ae != be { + panic(fmt.Sprintf("errA is '%v', but errB is '%v'", ae, be)) + } + } +} + +func (c *blueGreenTx) Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Remove() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + ach, errA := c.a.Remove(index, field, view, shard, a...) + _, _ = ach, errA + bch, errB := c.b.Remove(index, field, view, shard, a...) + compareErrors(errA, errB) + return bch, errB +} + +func (c *blueGreenTx) Contains(index, field, view string, shard uint64, key uint64) (exists bool, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Contains() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + ax, errA := c.a.Contains(index, field, view, shard, key) + _, _ = ax, errA + bx, errB := c.b.Contains(index, field, view, shard, key) + + compareErrors(errA, errB) + return bx, errB +} + +func (c *blueGreenTx) ContainerIterator(index, field, view string, shard uint64, firstRoaringContainerKey uint64) (citer roaring.ContainerIterator, found bool, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see ContainerIterator() panic '%v' at '%v'", r, stack()) + 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) + return bit, bfound, errB +} + +func (c *blueGreenTx) ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error { + 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 + + compareErrors(errA, errB) + return errB +} + +func (c *blueGreenTx) ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error { + 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 +} + +func (c *blueGreenTx) Count(index, field, view string, shard uint64) (uint64, error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Count() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + a, errA := c.a.Count(index, field, view, shard) + _, _ = a, errA + b, errB := c.b.Count(index, field, view, shard) + _, _ = b, errB + + compareErrors(errA, errB) + return b, errB +} + +func (c *blueGreenTx) Max(index, field, view string, shard uint64) (uint64, error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Max() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + a, errA := c.a.Max(index, field, view, shard) + _, _ = a, errA + b, errB := c.b.Max(index, field, view, shard) + _, _ = b, errB + + compareErrors(errA, errB) + return b, errB +} + +func (c *blueGreenTx) Min(index, field, view string, shard uint64) (uint64, bool, error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Min() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + amin, afound, errA := c.a.Min(index, field, view, shard) + _, _, _ = amin, afound, errA + bmin, bfound, errB := c.b.Min(index, field, view, shard) + _, _, _ = bmin, bfound, errB + + compareErrors(errA, errB) + return bmin, bfound, errB +} + +func (c *blueGreenTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see UnionInPlace() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + errA := c.a.UnionInPlace(index, field, view, shard, others...) + errB := c.b.UnionInPlace(index, field, view, shard, others...) + compareErrors(errA, errB) + return errB +} + +func (c *blueGreenTx) CountRange(index, field, view string, shard uint64, start, end uint64) (n uint64, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see CountRange() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + a, errA := c.a.CountRange(index, field, view, shard, start, end) + b, errB := c.b.CountRange(index, field, view, shard, start, end) + + if a != b { + panic(fmt.Sprintf("a = %v, but b = %v", a, b)) + } + + compareErrors(errA, errB) + return b, errB +} + +func (c *blueGreenTx) OffsetRange(index, field, view string, shard, offset, start, end uint64) (other *roaring.Bitmap, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see OffsetRange() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + a, errA := c.a.OffsetRange(index, field, view, shard, offset, start, end) + b, errB := c.b.OffsetRange(index, field, view, shard, offset, start, end) + + err = roaringBitmapDiff(a, b) + panicOn(err) + compareErrors(errA, errB) + return b, errB +} diff --git a/cache.go b/cache.go index ec7b3aa85..9195f63c9 100644 --- a/cache.go +++ b/cache.go @@ -350,6 +350,13 @@ type PairField struct { Field string } +func (p PairField) Clone() (r PairField) { + return PairField{ + Pair: p.Pair, + Field: p.Field, + } +} + // ToTable implements the ToTabler interface. func (p PairField) ToTable() (*pb.TableResponse, error) { return pb.RowsToTable(p, 1) @@ -469,6 +476,15 @@ type PairsField struct { Field string } +func (p *PairsField) Clone() (r *PairsField) { + r = &PairsField{ + Pairs: make([]Pair, len(p.Pairs)), + Field: p.Field, + } + copy(r.Pairs, p.Pairs) + return +} + // ToTable implements the ToTabler interface. func (p *PairsField) ToTable() (*pb.TableResponse, error) { return pb.RowsToTable(p, len(p.Pairs)) diff --git a/catcher.go b/catcher.go new file mode 100644 index 000000000..a4d832c20 --- /dev/null +++ b/catcher.go @@ -0,0 +1,277 @@ +// 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 ( + "fmt" + + "github.com/pilosa/pilosa/v2/roaring" +) + +// catcher is useful to report error locations with a +// stack dump before the complexity +// of the executor_test swallows up +// the location of a panic. +type catcherTx struct { + b *BadgerTx +} + +func newCatcherTx(b *BadgerTx) *catcherTx { + return &catcherTx{b: b} +} + +func init() { + // keep golangci-lint happy + _ = newCatcherTx +} + +var _ Tx = (*catcherTx)(nil) + +func (c *catcherTx) IncrementOpN(index, field, view string, shard uint64, changedN int) { + c.b.IncrementOpN(index, field, view, shard, changedN) +} + +func (c *catcherTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator { + return c.b.NewTxIterator(index, field, view, shard) +} + +func (c *catcherTx) WholeDatabaseBlake3Hash(index, field, view string, shard uint64) (hash string, err error) { + return c.b.WholeDatabaseBlake3Hash(index, field, view, shard) +} + +func (c *catcherTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see ImportRoaringBits() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize) +} + +func (c *catcherTx) Readonly() bool { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Readonly() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Readonly() +} + +func (tx *catcherTx) Pointer() string { + return fmt.Sprintf("%p", tx) +} + +func (c *catcherTx) Rollback() { + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Rollback() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + c.b.Rollback() +} + +func (c *catcherTx) Commit() error { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Commit() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Commit() +} + +func (c *catcherTx) RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see RoaringBitmap() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.RoaringBitmap(index, field, view, shard) +} + +func (c *catcherTx) Container(index, field, view string, shard uint64, key uint64) (ct *roaring.Container, err error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Container() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Container(index, field, view, shard, key) +} + +func (c *catcherTx) PutContainer(index, field, view string, shard uint64, key uint64, rc *roaring.Container) error { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see PutContainer() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.PutContainer(index, field, view, shard, key, rc) +} + +func (c *catcherTx) RemoveContainer(index, field, view string, shard uint64, key uint64) error { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see RemoveContainer() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.RemoveContainer(index, field, view, shard, key) +} + +func (c *catcherTx) UseRowCache() bool { + return c.b.UseRowCache() +} + +func (c *catcherTx) Add(index, field, view string, shard uint64, batched bool, a ...uint64) (changeCount int, err error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Add() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Add(index, field, view, shard, batched, a...) +} + +func (c *catcherTx) Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Remove() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Remove(index, field, view, shard, a...) +} + +func (c *catcherTx) Contains(index, field, view string, shard uint64, key uint64) (exists bool, err error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Contains() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Contains(index, field, view, shard, key) +} + +func (c *catcherTx) ContainerIterator(index, field, view string, shard uint64, firstRoaringContainerKey uint64) (citer roaring.ContainerIterator, found bool, err error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see ContainerIterator() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.ContainerIterator(index, field, view, shard, firstRoaringContainerKey) +} + +func (c *catcherTx) ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see ForEach() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.ForEach(index, field, view, shard, fn) +} + +func (c *catcherTx) ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see ForEachRange() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.ForEachRange(index, field, view, shard, start, end, fn) +} + +func (c *catcherTx) Count(index, field, view string, shard uint64) (uint64, error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Count() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Count(index, field, view, shard) +} + +func (c *catcherTx) Max(index, field, view string, shard uint64) (uint64, error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Max() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Max(index, field, view, shard) +} + +func (c *catcherTx) Min(index, field, view string, shard uint64) (uint64, bool, error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see Min() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.Min(index, field, view, shard) +} + +func (c *catcherTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see UnionInPlace() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.UnionInPlace(index, field, view, shard, others...) +} + +func (c *catcherTx) CountRange(index, field, view string, shard uint64, start, end uint64) (n uint64, err error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see CountRange() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.CountRange(index, field, view, shard, start, end) +} + +func (c *catcherTx) OffsetRange(index, field, view string, shard, offset, start, end uint64) (other *roaring.Bitmap, err error) { + + defer func() { + if r := recover(); r != nil { + AlwaysPrintf("see OffsetRange() panic '%v' at '%v'", r, stack()) + panic(r) + } + }() + return c.b.OffsetRange(index, field, view, shard, offset, start, end) +} diff --git a/cluster_internal_test.go b/cluster_internal_test.go index 00492d068..c31b9f294 100644 --- a/cluster_internal_test.go +++ b/cluster_internal_test.go @@ -161,7 +161,7 @@ func TestFragSources(t *testing.T) { // Obtain transaction. tx := &RoaringTx{Index: idx} - defer func() { _ = tx.Rollback() }() + defer tx.Rollback() field, err := idx.CreateFieldIfNotExists("f", OptFieldTypeDefault()) if err != nil { @@ -787,6 +787,7 @@ func TestCluster_ResizeStates(t *testing.T) { if err := tc.CreateField("i", "f", OptFieldTypeDefault()); err != nil { t.Fatalf("creating field: %v", err) } + // Each tc.SetBit starts and commits its own Tx. if err := tc.SetBit("i", "f", 1, 101, nil); err != nil { t.Fatalf("setting bit: %v", err) } @@ -804,6 +805,12 @@ func TestCluster_ResizeStates(t *testing.T) { t.Fatal(err) } + idx0 := node0.holder.Index("i") + if idx0 == nil { + t.Fatal(`idx0 was nil, could not retrieve Index("i")`) + } + //idx0.Dump("node0") + // addNode needs to block until the resize process has completed. if err := tc.addNode(); err != nil { t.Fatalf("adding node: %v", err) @@ -816,6 +823,7 @@ func TestCluster_ResizeStates(t *testing.T) { } else if node1.State() != ClusterStateNormal { t.Errorf("expected node1 state: %v, but got: %v", ClusterStateNormal, node1.State()) } + // INVAR: after node1.State() is normal, the rebalancing should have been done. expectedTop := &Topology{ nodeIDs: []string{node0.Node.ID, node1.Node.ID}, @@ -834,11 +842,19 @@ func TestCluster_ResizeStates(t *testing.T) { node1View := node1Field.view("standard") node1Fragment := node1View.Fragment(1) + idx1 := node1.holder.Index("i") + if idx1 == nil { + t.Fatal(`idx1 was nil, could not retrieve Index("i")`) + } + //idx0.Dump("after rebalance, node0") + //idx1.Dump("after rebalance, node1") + // Ensure checksums are the same. if chksum, err := node1Fragment.Checksum(); err != nil { 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. diff --git a/ctl/server.go b/ctl/server.go index 8aa0edeb9..aa0b9a077 100644 --- a/ctl/server.go +++ b/ctl/server.go @@ -85,4 +85,7 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) { // Profiling flags.IntVar(&srv.Config.Profile.BlockRate, "profile.block-rate", srv.Config.Profile.BlockRate, "Sampling rate for goroutine blocking profiler. One sample per ns.") flags.IntVar(&srv.Config.Profile.MutexFraction, "profile.mutex-fraction", srv.Config.Profile.MutexFraction, "Sampling fraction for mutex contention profiling. Sample 1/ of events.") + + // Transactional storage engine + flags.StringVarP(&srv.Config.Txsrc, "tx", "", "roaring", "transaction/storage to use: one of roaring, rbf, badger, rbf_roaring, roaring_rbf, badger_roaring, roaring_badger, badger_rbf, or rbf_badger") } diff --git a/executor.go b/executor.go index 3812c8451..62b98b5be 100644 --- a/executor.go +++ b/executor.go @@ -180,8 +180,14 @@ func (e *executor) Execute(ctx context.Context, index string, q *pql.Query, shar return resp, ErrIndexNotFound } + needWriteTxn := false + nw := q.WriteCallN() + if nw > 0 { + needWriteTxn = true + } + // Verify that the number of writes do not exceed the maximum. - if e.MaxWritesPerRequest > 0 && q.WriteCallN() > e.MaxWritesPerRequest { + if e.MaxWritesPerRequest > 0 && nw > e.MaxWritesPerRequest { return resp, ErrTooManyWrites } @@ -210,12 +216,11 @@ func (e *executor) Execute(ctx context.Context, index string, q *pql.Query, shar } } - // TODO: Determine if query is read-only. - tx, err := e.Holder.Begin(true) + tx, err := e.Holder.BeginTx(needWriteTxn, idx) if err != nil { return resp, err } - defer func() { _ = tx.Rollback() }() + defer tx.Rollback() results, err := e.execute(ctx, tx, index, q, shards, opt) if err != nil { @@ -267,19 +272,75 @@ func (e *executor) Execute(ctx context.Context, index string, q *pql.Query, shar // Translate response objects from ids to keys, if necessary. // No need to translate a remote call. if !opt.Remote { + // only translateResults if this local node is the final destination. only string/column keys. if err := e.translateResults(ctx, index, idx, q.Calls, results); err != nil { return resp, err } else if err := validateQueryContext(ctx); err != nil { return resp, err } } + // Must copy out of Tx data before Commiting, because it will become invalid afterwards. + respSafeNoTxData := e.safeCopy(resp) // Commit transaction. if err := tx.Commit(); err != nil { - return resp, err + return respSafeNoTxData, err } + return respSafeNoTxData, nil +} - return resp, nil +// safeCopy copies everything in resp that has Bitmap material, +// to avoid anything coming from the mmap-ed Tx storage. +func (e *executor) safeCopy(resp QueryResponse) (out QueryResponse) { + out = QueryResponse{ + // not transactional, from attribute storage so no need to clone these: + ColumnAttrSets: resp.ColumnAttrSets, // []*ColumnAttrSet + Err: resp.Err, // error + Profile: resp.Profile, // *tracing.Profile + } + // Results can contain *roaring.Bitmap, so need to copy from Tx mmap-ed memory. + for _, v := range resp.Results { + switch x := v.(type) { + case *Row: + rowSafe := x.Clone() + out.Results = append(out.Results, rowSafe) + case bool: + out.Results = append(out.Results, x) + case nil: + out.Results = append(out.Results, nil) + case uint64: + out.Results = append(out.Results, x) // for counts + case *PairsField: + // no bitmap material, so should be ok to skip Clone() + out.Results = append(out.Results, x) + case PairField: // not PairsField but PairField + // no bitmap material, so should be ok to skip Clone() + out.Results = append(out.Results, x) + case ValCount: + // no bitmap material, so should be ok to skip Clone() + out.Results = append(out.Results, x) + case SignedRow: + // has *Row in it, so has Bitmap material, and very likely needs Clone. + y := x.Clone() + out.Results = append(out.Results, *y) + case GroupCount: + // no bitmap material, so should be ok to skip Clone() + out.Results = append(out.Results, x) + case []GroupCount: + out.Results = append(out.Results, x) + case RowIdentifiers: + // no bitmap material, so should be ok to skip Clone() + out.Results = append(out.Results, x) + case RowIDs: + // defined as: type RowIDs []uint64 + // so does not contain bitmap material, and + // should not need to be cloned. + out.Results = append(out.Results, x) + default: + panic(fmt.Sprintf("handle %T here", v)) + } + } + return } // readColumnAttrSets returns a list of column attribute objects by id. @@ -308,6 +369,7 @@ func (e *executor) readColumnAttrSets(index *Index, ids []uint64) ([]*ColumnAttr // handlePreCalls traverses the call tree looking for calls that need // precomputed values. Right now, that's just Distinct. func (e *executor) handlePreCalls(ctx context.Context, tx Tx, index string, c *pql.Call, shards []uint64, opt *execOptions) error { + if c.Name == "Precomputed" { idx := c.Args["valueidx"].(int64) if idx >= 0 && idx < int64(len(opt.EmbeddedData)) { @@ -457,6 +519,7 @@ func (e *executor) execute(ctx context.Context, tx Tx, index string, q *pql.Quer // Execute each call serially. results := make([]interface{}, 0, len(q.Calls)) for i, call := range q.Calls { + if err := validateQueryContext(ctx); err != nil { return nil, err } @@ -1019,6 +1082,7 @@ func (e *executor) executeAllCallMapReduce(ctx context.Context, tx Tx, index str // executeIncludesColumnCallShard func (e *executor) executeIncludesColumnCallShard(ctx context.Context, tx Tx, index string, c *pql.Call, shard uint64, column uint64) (bool, error) { + span, ctx := tracing.StartSpanFromContext(ctx, "Executor.executeIncludesColumnCallShard") defer span.Finish() @@ -1308,7 +1372,8 @@ func (e *executor) executeBitmapCall(ctx context.Context, tx Tx, index string, c reduceFn := func(ctx context.Context, prev, v interface{}) interface{} { other, _ := prev.(*Row) if other == nil { - other = NewRow() + + other = NewRow() // bug! this row ends up containing Badger Txn data that should be accessed outside the Txn. } if err := ctx.Err(); err != nil { return err @@ -1823,6 +1888,21 @@ type RowIdentifiers struct { field string } +func (r *RowIdentifiers) Clone() (clone *RowIdentifiers) { + clone = &RowIdentifiers{ + field: r.field, + } + if r.Rows != nil { + clone.Rows = make([]uint64, len(r.Rows)) + copy(clone.Rows, r.Rows) + } + if r.Keys != nil { + clone.Keys = make([]string, len(r.Keys)) + copy(clone.Keys, r.Keys) + } + return +} + // ToTable implements the ToTabler interface. func (r RowIdentifiers) ToTable() (*pb.TableResponse, error) { var n int @@ -2041,6 +2121,20 @@ type FieldRow struct { Value *int64 `json:"value,omitempty"` } +func (fr *FieldRow) Clone() (clone *FieldRow) { + clone = &FieldRow{ + Field: fr.Field, + RowID: fr.RowID, + RowKey: fr.RowKey, + } + if fr.Value != nil { + // deep copy, for safety. + v := *fr.Value + clone.Value = &v + } + return +} + // MarshalJSON marshals FieldRow to JSON such that // either a Key or an ID is included. func (fr FieldRow) MarshalJSON() ([]byte, error) { @@ -2138,6 +2232,18 @@ type GroupCount struct { Sum int64 `json:"sum"` } +func (g *GroupCount) Clone() (r *GroupCount) { + r = &GroupCount{ + Group: make([]FieldRow, len(g.Group)), + Count: g.Count, + Sum: g.Sum, + } + for i := range g.Group { + r.Group[i] = *(g.Group[i].Clone()) + } + return +} + // mergeGroupCounts merges two slices of GroupCounts throwing away any that go // beyond the limit. It assume that the two slices are sorted by the row ids in // the fields of the group counts. It may modify its arguments. @@ -2642,6 +2748,7 @@ func (e *executor) executeRowShard(ctx context.Context, tx Tx, index string, c * // Simply return row if times are not set. if c.Name == "Row" && timeNotSet { + frag := e.Holder.fragment(index, fieldName, viewStandard, shard) if frag == nil { return NewRow(), nil @@ -3771,6 +3878,7 @@ func (e *executor) executeSetColumnAttrs(ctx context.Context, tx Tx, index strin delete(attrs, "field") // Set attributes. + if err := idx.ColumnAttrStore().SetAttrs(col, attrs); err != nil { return err } @@ -4654,6 +4762,15 @@ type SignedRow struct { field string } +func (s *SignedRow) Clone() (r *SignedRow) { + r = &SignedRow{ + Neg: s.Neg.Clone(), // Row.Clone() returns nil for nil. + Pos: s.Pos.Clone(), + field: s.field, + } + return +} + // Field returns the field name associated to the signed row. func (s *SignedRow) Field() string { return s.field @@ -4768,6 +4885,18 @@ type ValCount struct { Count int64 `json:"count"` } +func (v *ValCount) Clone() (r *ValCount) { + r = &ValCount{ + Val: v.Val, + FloatVal: v.FloatVal, + Count: v.Count, + } + if v.DecimalVal != nil { + r.DecimalVal = v.DecimalVal.Clone() + } + return +} + // ToTable implements the ToTabler interface. func (v ValCount) ToTable() (*pb.TableResponse, error) { return pb.RowsToTable(&v, 1) diff --git a/executor_internal_test.go b/executor_internal_test.go index 28dd369ef..8ae31da98 100644 --- a/executor_internal_test.go +++ b/executor_internal_test.go @@ -137,12 +137,6 @@ func TestExecutor_TranslateRowsOnBool(t *testing.T) { holder := NewHolder(DefaultPartitionN) defer holder.Close() - tx, err := holder.Begin(true) - if err != nil { - t.Fatal(err) - } - defer func() { _ = tx.Rollback() }() - e := &executor{ Holder: holder, Cluster: NewTestCluster(1), @@ -157,6 +151,12 @@ func TestExecutor_TranslateRowsOnBool(t *testing.T) { t.Fatalf("creating index: %v", err) } + tx, err := holder.BeginTx(writable, idx) + if err != nil { + t.Fatal(err) + } + defer tx.Rollback() + fb, errb := idx.CreateField("b", OptFieldTypeBool()) _, errbk := idx.CreateField("bk", OptFieldTypeBool(), OptFieldKeys()) if errb != nil || errbk != nil { diff --git a/executor_test.go b/executor_test.go index 32fc01b27..9a78b96c0 100644 --- a/executor_test.go +++ b/executor_test.go @@ -41,6 +41,9 @@ import ( "github.com/pkg/errors" ) +// writable initializes Tx that update, use !writable for read-only. +const writable = true + var ( TempDir = getTempDirString() ) @@ -142,6 +145,7 @@ func TestExecutor_Execute_Difference(t *testing.T) { c := test.MustRunCluster(t, 1) defer c.Close() hldr := test.Holder{Holder: c[0].Server.Holder()} + hldr.SetBit("i", "general", 10, 1) hldr.SetBit("i", "general", 10, 2) hldr.SetBit("i", "general", 10, 3) @@ -223,7 +227,6 @@ func TestExecutor_Execute_Intersect(t *testing.T) { hldr.SetBit("i", "general", 10, 1) hldr.SetBit("i", "general", 10, ShardWidth+1) hldr.SetBit("i", "general", 10, ShardWidth+2) - hldr.SetBit("i", "general", 11, 1) hldr.SetBit("i", "general", 11, 2) hldr.SetBit("i", "general", 11, ShardWidth+2) @@ -904,11 +907,11 @@ func TestExecutor_Execute_SetValue(t *testing.T) { } // Obtain transaction. - tx, err := hldr.Begin(false) + tx, err := hldr.BeginTx(!writable, index.Index) if err != nil { t.Fatal(err) } - defer func() { _ = tx.Rollback() }() + defer tx.Rollback() f := hldr.Field("i", "f") if value, exists, err := f.Value(tx, 10); err != nil { @@ -3435,7 +3438,6 @@ func TestExecutor_Execute_Existence(t *testing.T) { } else if bits := res.Results[0].(*pilosa.Row).Columns(); !reflect.DeepEqual(bits, []uint64{ShardWidth + 2}) { t.Fatalf("unexpected columns after Not: %+v", bits) } - // Reopen cluster to ensure existence field is reloaded. if err := c[0].Reopen(); err != nil { t.Fatal(err) @@ -3820,7 +3822,6 @@ func TestExecutor_Execute_ClearRow(t *testing.T) { if res := responses[1].Results[0].(bool); !res { t.Fatalf("unexpected clear row result: %+v", res) } - // Clear the row again and ensure we get a `false` response. if res := responses[2].Results[0].(bool); res { t.Fatalf("unexpected clear row result: %+v", res) @@ -4023,6 +4024,7 @@ func TestExecutor_Execute_ClearRow(t *testing.T) { // Ensure a row can be set. func TestExecutor_Execute_SetRow(t *testing.T) { + t.Run("Set_NewRow", func(t *testing.T) { c := test.MustRunCluster(t, 1) defer c.Close() @@ -4824,6 +4826,7 @@ func TestExecutor_ForeignIndex(t *testing.T) { } join := c.Query(t, "parent", `Intersect(Row(general=3), Distinct(Row(color="blue"), index="child", field="parent_id"))`).Results[0].(*pilosa.Row) + if !reflect.DeepEqual(join.Keys, []string{"one"}) { t.Fatalf("unexpected keys: %v", join.Keys) } @@ -4870,6 +4873,7 @@ func TestExecutor_Execute_GroupBy(t *testing.T) { {12, 2}, {12, ShardWidth + 2}, }) + c.ImportBits(t, "i", "sub", [][2]uint64{ {100, 0}, {100, 1}, diff --git a/field.go b/field.go index 956fbd452..a9a80e2fe 100644 --- a/field.go +++ b/field.go @@ -1737,7 +1737,7 @@ func (f *Field) importValue(tx Tx, columnIDs []uint64, values []int64, options * return nil } -func (f *Field) importRoaring(ctx context.Context, data []byte, shard uint64, viewName string, clear bool) error { +func (f *Field) importRoaring(ctx context.Context, tx Tx, data []byte, shard uint64, viewName string, clear bool) error { span, ctx := tracing.StartSpanFromContext(ctx, "Field.importRoaring") defer span.Finish() @@ -1754,7 +1754,7 @@ func (f *Field) importRoaring(ctx context.Context, data []byte, shard uint64, vi if err != nil { return errors.Wrap(err, "creating fragment") } - if err := frag.importRoaring(ctx, data, clear); err != nil { + if err := frag.importRoaring(ctx, tx, data, clear); err != nil { return err } diff --git a/fragment.go b/fragment.go index c7bd1e241..b0bf22e05 100644 --- a/fragment.go +++ b/fragment.go @@ -129,7 +129,14 @@ type fragment struct { // Cache for row counts. CacheType string // passed in by field - cache cache + + // cache keeps a local rowid,count ranking: + // telling us which is the most populated rows in that field. + // Is only on "set fields" with rowCache enabled. So + // BSI, mutex, bool fields do not have this. + // Good: it Only has a string and a count, so cannot use Tx memory. + cache cache + CacheSize uint32 // Stats reporting. @@ -160,6 +167,15 @@ type fragment struct { stats stats.StatsClient bitmapInfo *roaring.BitmapInfo + + // txTestingOnly: this looks gross. + // Nonetheless, it allowed us to + // integrate Tx into the + // fragment_internal_test.go suite + // and not break the world all at once. + // + // Only for testing, obviously. + txTestingOnly Tx } // newFragment returns a new instance of Fragment. @@ -192,6 +208,10 @@ type FragmentInfo struct { BlockChecksums []FragmentBlock `json:"BlockChecksums,omitempty"` } +func (f *fragment) Index() *Index { + return f.holder.Index(f.index) +} + func (f *fragment) inspect(params InspectRequestParams) (fi FragmentInfo) { if f.bitmapInfo == nil { fi.BitmapInfo = f.storage.Info(params.Containers) @@ -529,16 +549,20 @@ func (f *fragment) mustRow(tx Tx, rowID uint64) *Row { // unprotectedRow returns a row from the row cache if available or from storage // (updating the cache). func (f *fragment) unprotectedRow(tx Tx, rowID uint64) (*Row, error) { - r, ok := f.rowCache.Fetch(rowID) - if ok && r != nil { - return r, nil + useRowCache := tx.UseRowCache() + if useRowCache { + r, ok := f.rowCache.Fetch(rowID) + if ok && r != nil { + return r, nil + } } - row, err := f.rowFromStorage(tx, rowID) if err != nil { return nil, err } - f.rowCache.Add(rowID, row) + if useRowCache { + f.rowCache.Add(rowID, row) + } return row, nil } @@ -559,7 +583,7 @@ func (f *fragment) rowFromStorage(tx Tx, rowID uint64) (*Row, error) { row := &Row{ segments: []rowSegment{{ - data: data, + data: data, // this data contains BadgerTx data, which should not survive Txn commit. shard: f.shard, writable: true, }}, @@ -602,7 +626,7 @@ func (f *fragment) handleMutex(tx Tx, rowID, columnID uint64) error { // unprotectedSetBit TODO should be replaced by an invocation of importPositions with a single bit to set. func (f *fragment) unprotectedSetBit(tx Tx, 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 { @@ -610,7 +634,10 @@ func (f *fragment) unprotectedSetBit(tx Tx, rowID, columnID uint64) (changed boo } // Write to storage. - if changed, err = tx.Add(f.index, f.field, f.view, f.shard, pos); err != nil { + changeCount := 0 + changeCount, err = tx.Add(f.index, f.field, f.view, f.shard, doBatched, pos) + changed = changeCount > 0 + if err != nil { return false, errors.Wrap(err, "writing") } @@ -623,7 +650,7 @@ func (f *fragment) unprotectedSetBit(tx Tx, rowID, columnID uint64) (changed boo delete(f.checksums, int(rowID/HashBlockSize)) // Increment number of operations until snapshot is required. - f.incrementOpN(1) + tx.IncrementOpN(f.index, f.field, f.view, f.shard, 1) // If we're using a cache, update it. Otherwise skip the // possibly-expensive count operation. @@ -671,20 +698,23 @@ func (f *fragment) unprotectedClearBit(tx Tx, rowID, columnID uint64) (changed b } // Write to storage. - if changed, err = tx.Remove(f.index, f.field, f.view, f.shard, pos); err != nil { + changeCount := 0 + if changeCount, err = tx.Remove(f.index, f.field, f.view, f.shard, pos); err != nil { return false, errors.Wrap(err, "writing") } // Don't update the cache if nothing changed. - if !changed { - return changed, nil + if changeCount <= 0 { + return false, nil + } else { + changed = true } // Invalidate block checksum. delete(f.checksums, int(rowID/HashBlockSize)) // Increment number of operations until snapshot is required. - f.incrementOpN(1) + tx.IncrementOpN(f.index, f.field, f.view, f.shard, 1) // If we're using a cache, update it. Otherwise skip the // possibly-expensive count operation. @@ -997,15 +1027,17 @@ func (f *fragment) importSetValue(tx Tx, columnID uint64, bitDepth uint, value i } if uvalue&(1< 0 { changed++ } } else { - if c, err := tx.Remove(f.index, f.field, f.view, f.shard, bit); err != nil { + changeCount, err := tx.Remove(f.index, f.field, f.view, f.shard, bit) + if err != nil { return changed, errors.Wrap(err, "removing") - } else if c { + } else if changeCount > 0 { changed++ } } @@ -1017,13 +1049,13 @@ func (f *fragment) importSetValue(tx Tx, columnID uint64, bitDepth uint, value i } else if clear { if c, err := tx.Remove(f.index, f.field, f.view, f.shard, p); err != nil { return changed, errors.Wrap(err, "removing not-null from storage") - } else if c { + } else if c > 0 { changed++ } } else { - if c, err := tx.Add(f.index, f.field, f.view, f.shard, p); err != nil { + if c, err := tx.Add(f.index, f.field, f.view, f.shard, !doBatched, p); err != nil { return changed, errors.Wrap(err, "adding not-null to storage") - } else if c { + } else if c > 0 { changed++ } } @@ -1034,13 +1066,13 @@ func (f *fragment) importSetValue(tx Tx, columnID uint64, bitDepth uint, value i } else if value >= 0 || clear { if c, err := tx.Remove(f.index, f.field, f.view, f.shard, p); err != nil { return changed, errors.Wrap(err, "removing sign from storage") - } else if c { + } else if c > 0 { changed++ } } else { - if c, err := tx.Add(f.index, f.field, f.view, f.shard, p); err != nil { + if c, err := tx.Add(f.index, f.field, f.view, f.shard, !doBatched, p); err != nil { return changed, errors.Wrap(err, "adding sign to storage") - } else if c { + } else if c > 0 { changed++ } } @@ -1889,8 +1921,17 @@ func (f *fragment) Blocks() ([]FragmentBlock, error) { var a []FragmentBlock - // Initialize the iterator. - itr := f.storage.Iterator() + idx := f.holder.Index(f.index) + if idx == nil { + panic(fmt.Sprintf("index was nil in fragment.Blocks(): f.index='%v'; f.holder.indexes='%#v'\n", f.index, f.holder.indexes)) + } + tx := idx.Txf.NewTx(Txo{Write: !writable, Index: idx, Fragment: f}) + defer tx.Rollback() + // no Commit below, b/c is read-only. + + itr := tx.NewTxIterator(f.index, f.field, f.view, f.shard) + defer itr.Close() + itr.Seek(0) // Initialize block hasher. @@ -1967,7 +2008,13 @@ func (f *fragment) readContiguousChecksums(a *[]FragmentBlock, blockID int) (n i func (f *fragment) blockData(id int) (rowIDs, columnIDs []uint64, err error) { f.mu.Lock() defer f.mu.Unlock() - if err := f.storage.ForEachRange(uint64(id)*HashBlockSize*ShardWidth, (uint64(id)+1)*HashBlockSize*ShardWidth, func(i uint64) error { + + idx := f.holder.Index(f.index) + tx := idx.Txf.NewTx(Txo{Write: !writable, Index: idx}) + defer tx.Rollback() + // readonly, so no Commit() + + if err := tx.ForEachRange(f.index, f.field, f.view, f.shard, uint64(id)*HashBlockSize*ShardWidth, (uint64(id)+1)*HashBlockSize*ShardWidth, func(i uint64) error { rowIDs = append(rowIDs, i/ShardWidth) columnIDs = append(columnIDs, i%ShardWidth) return nil @@ -2101,7 +2148,7 @@ func (f *fragment) mergeBlock(tx Tx, id int, data []pairSet) (sets, clears []pai rowSet[clears[0].rowIDs[i]] = struct{}{} clears[0].columnIDs[i] += clears[0].rowIDs[i] * ShardWidth } - err = f.importPositions(sets[0].columnIDs, clears[0].columnIDs, rowSet) + err = f.importPositions(tx, sets[0].columnIDs, clears[0].columnIDs, rowSet) return sets[1:], clears[1:], err } @@ -2149,9 +2196,9 @@ func (f *fragment) bulkImportStandard(tx Tx, rowIDs, columnIDs []uint64, options f.mu.Lock() defer f.mu.Unlock() if options.Clear { - err = f.importPositions(nil, positions, rowSet) + err = f.importPositions(tx, nil, positions, rowSet) } else { - err = f.importPositions(positions, nil, rowSet) + err = f.importPositions(tx, positions, nil, rowSet) } return errors.Wrap(err, "bulkImportStandard") } @@ -2164,26 +2211,31 @@ func (f *fragment) bulkImportStandard(tx Tx, rowIDs, columnIDs []uint64, options // 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 { +func (f *fragment) importPositions(tx Tx, set, clear []uint64, rowSet map[uint64]struct{}) error { + //tx.AddN() err := f.gen.Transaction(&f.storage.OpWriter, func() error { if len(set) > 0 { f.stats.Count(MetricImportingN, int64(len(set)), 1) - changedN, err := f.storage.AddN(set...) // TODO benchmark Add/RemoveN behavior with sorted/unsorted positions + + // TODO benchmark Add/RemoveN behavior with sorted/unsorted positions + // Note: AddN() avoids writing to the op-log. While Add() does. + changedN, err := tx.Add(f.index, f.field, f.view, f.shard, !doBatched, set...) + if err != nil { return errors.Wrap(err, "adding positions") } f.stats.Count(MetricImportedN, int64(changedN), 1) - f.incrementOpN(changedN) + tx.IncrementOpN(f.index, f.field, f.view, f.shard, changedN) } if len(clear) > 0 { f.stats.Count(MetricClearingN, int64(len(clear)), 1) - changedN, err := f.storage.RemoveN(clear...) + changedN, err := tx.Remove(f.index, f.field, f.view, f.shard, clear...) if err != nil { return errors.Wrap(err, "clearing positions") } f.stats.Count(MetricClearedN, int64(changedN), 1) - f.incrementOpN(changedN) + tx.IncrementOpN(f.index, f.field, f.view, f.shard, changedN) } // Update cache counts for all affected rows. @@ -2192,7 +2244,14 @@ func (f *fragment) importPositions(set, clear []uint64, rowSet map[uint64]struct delete(f.checksums, int(rowID/HashBlockSize)) if f.CacheType != CacheTypeNone { - n := f.storage.CountRange(rowID*ShardWidth, (rowID+1)*ShardWidth) + start := rowID * ShardWidth + end := (rowID + 1) * ShardWidth + + n, err := tx.CountRange(f.index, f.field, f.view, f.shard, start, end) + if err != nil { + return errors.Wrap(err, "CountRange") + } + f.cache.BulkAdd(rowID, n) } @@ -2275,10 +2334,10 @@ func (f *fragment) bulkImportMutex(tx Tx, rowIDs, columnIDs []uint64) error { toSet := rowIDs[:i] toClear := columnIDs[:clearIdx] - return errors.Wrap(f.importPositions(toSet, toClear, rowSet), "importing positions") + return errors.Wrap(f.importPositions(tx, toSet, toClear, rowSet), "importing positions") } -func (f *fragment) importValueSmallWrite(columnIDs []uint64, values []int64, bitDepth uint, clear bool) error { +func (f *fragment) importValueSmallWrite(tx Tx, 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 @@ -2310,7 +2369,7 @@ func (f *fragment) importValueSmallWrite(columnIDs []uint64, values []int64, bit for i := uint(0); i < bitDepth+1; i++ { rowSet[uint64(i)] = struct{}{} } - err := f.importPositions(toSet, toClear, rowSet) + err := f.importPositions(tx, toSet, toClear, rowSet) if err != nil { return errors.Wrap(err, "importing positions") } @@ -2332,7 +2391,7 @@ func (f *fragment) importValue(tx Tx, columnIDs []uint64, values []int64, bitDep } if len(columnIDs)*int(bitDepth+1)+f.opN < f.MaxOpN { - return errors.Wrap(f.importValueSmallWrite(columnIDs, values, bitDepth, clear), "import small write") + return errors.Wrap(f.importValueSmallWrite(tx, columnIDs, values, bitDepth, clear), "import small write") } // Process every value. @@ -2374,7 +2433,7 @@ func (f *fragment) importValue(tx Tx, columnIDs []uint64, values []int64, bitDep // 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 { +func (f *fragment) importRoaring(ctx context.Context, tx Tx, data []byte, clear bool) error { span, ctx := tracing.StartSpanFromContext(ctx, "fragment.importRoaring") defer span.Finish() span, ctx = tracing.StartSpanFromContext(ctx, "importRoaring.AcquireFragmentLock") @@ -2382,16 +2441,22 @@ func (f *fragment) importRoaring(ctx context.Context, data []byte, clear bool) e defer f.mu.Unlock() span.Finish() - return f.unprotectedImportRoaring(ctx, data, clear) + return f.unprotectedImportRoaring(ctx, tx, data, clear) } -func (f *fragment) unprotectedImportRoaring(ctx context.Context, data []byte, clear bool) error { +func (f *fragment) unprotectedImportRoaring(ctx context.Context, tx Tx, data []byte, clear bool) error { rowSize := uint64(1 << shardVsContainerExponent) span, ctx := tracing.StartSpanFromContext(ctx, "importRoaring.ImportRoaringBits") var changed int var rowSet map[uint64]int err := f.gen.Transaction(&f.storage.OpWriter, func() (err error) { - changed, rowSet, err = f.storage.ImportRoaringBits(data, clear, true, rowSize) + var rit roaring.RoaringIterator + rit, err = roaring.NewRoaringIterator(data) + if err != nil { + return err + } + + changed, rowSet, err = tx.ImportRoaringBits(f.index, f.field, f.view, f.shard, rit, clear, true, rowSize) return err }) @@ -2428,7 +2493,9 @@ func (f *fragment) unprotectedImportRoaring(ctx context.Context, data []byte, cl } span, _ = tracing.StartSpanFromContext(ctx, "importRoaring.incrementOpN") - f.incrementOpN(changed) + + tx.IncrementOpN(f.index, f.field, f.view, f.shard, changed) + span.Finish() return nil } @@ -2444,7 +2511,7 @@ func (f *fragment) importRoaringOverwrite(ctx context.Context, tx Tx, data []byt } // Union the new block data with the fragment data. - return f.unprotectedImportRoaring(ctx, data, false) + return f.unprotectedImportRoaring(ctx, tx, data, false) } // incrementOpN increase the operation count by one. @@ -2486,7 +2553,6 @@ func (f *fragment) snapshot() (err error) { defer func() { debug.SetPanicOnFault(wouldPanic) if r := recover(); r != nil { - fmt.Printf("snapshot panic!\n") if e2, ok := r.(error); ok { err = e2 // special case: if we caught a page fault, we diagnose that directly. sadly, @@ -2869,6 +2935,7 @@ func (f *fragment) unprotectedRows(ctx context.Context, tx Tx, start uint64, fil if err != nil { return nil, err } + defer i.Close() // must close iterators allocated on a Tx rows := make([]uint64, 0) var lastRow uint64 = math.MaxUint64 @@ -3074,6 +3141,8 @@ func (f *fragment) foreachRow(tx Tx, filters []rowFilter, fn func(rid uint64) er if err != nil { return err } + defer i.Close() // must close tx allocated iterators when done. + // Loop over the existing containers. for i.Next() { key, c := i.Value() @@ -3263,7 +3332,7 @@ func (s *fragmentSyncer) syncFragment() error { for _, node := range nodes { // Read local blocks. if node.ID == s.Node.ID { - b, err := s.Fragment.Blocks() + b, err := s.Fragment.Blocks() // comes from Tx store, creates its own Tx. if err != nil { return err } @@ -3404,7 +3473,6 @@ func (s *fragmentSyncer) syncBlock(id int) error { defer span.Finish() f := s.Fragment - tx := &RoaringTx{fragment: f} // Read pairs from each remote block. var uris []*URI @@ -3423,6 +3491,7 @@ func (s *fragmentSyncer) syncBlock(id int) error { uris = append(uris, uri) // Only sync the standard block. + // Does a remote fetch 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") @@ -3439,12 +3508,24 @@ func (s *fragmentSyncer) syncBlock(id int) error { return nil } + idx := f.holder.Index(f.index) + tx := idx.Txf.NewTx(Txo{Write: writable, Index: idx}) + defer tx.Rollback() + // Merge blocks together. sets, clears, err := f.mergeBlock(tx, id, pairSets) if err != nil { return errors.Wrap(err, "merging") } + // no safeCopy needed here. We are not leaking data outside the tx, because + // sets and clears only contain columnIDs. + + err = tx.Commit() + if err != nil { + return err + } + // Write updates to remote blocks. for i := 0; i < len(uris); i++ { set, clear := sets[i], clears[i] diff --git a/fragment_internal_test.go b/fragment_internal_test.go index 661f8430a..16fca1dfc 100644 --- a/fragment_internal_test.go +++ b/fragment_internal_test.go @@ -28,7 +28,6 @@ import ( "runtime" "runtime/debug" "sort" - "sync/atomic" "testing" "testing/quick" @@ -51,11 +50,12 @@ var ( // Ensure a fragment can set a bit and retrieve it. func TestFragment_SetBit(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // Set bits on the fragment. if _, err := f.setBit(tx, 120, 1); err != nil { @@ -65,6 +65,7 @@ func TestFragment_SetBit(t *testing.T) { } else if _, err := f.setBit(tx, 121, 0); err != nil { t.Fatal(err) } + // should have two containers set in the fragment. // Verify counts on rows. if n := f.mustRow(tx, 120).Count(); n != 2 { @@ -73,10 +74,18 @@ func TestFragment_SetBit(t *testing.T) { t.Fatalf("unexpected count: %d", n) } + // commit the change, and verify it is still there + panicOn(tx.Commit()) + // Close and reopen the fragment & verify the data. - if err := f.Reopen(); err != nil { + err := f.Reopen() // roaring data not being flushed? red on roaring + if err != nil { t.Fatal(err) - } else if n := f.mustRow(tx, 120).Count(); n != 2 { + } + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() + + if n := f.mustRow(tx, 120).Count(); n != 2 { t.Fatalf("unexpected count (reopen): %d", n) } else if n := f.mustRow(tx, 121).Count(); n != 1 { t.Fatalf("unexpected count (reopen): %d", n) @@ -85,11 +94,13 @@ func TestFragment_SetBit(t *testing.T) { // Ensure a fragment can clear a set bit. func TestFragment_ClearBit(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // Set and then clear bits on the fragment. if _, err := f.setBit(tx, 1000, 1); err != nil { @@ -104,6 +115,12 @@ func TestFragment_ClearBit(t *testing.T) { if n := f.mustRow(tx, 1000).Count(); n != 1 { t.Fatalf("unexpected count: %d", n) } + // The Reopen below implies this test is looking at storage consistency. + // In that spirit, we will check that the Tx Commit is visible afterwards. + panicOn(tx.Commit()) + + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() // Close and reopen the fragment & verify the data. if err := f.Reopen(); err != nil { @@ -113,20 +130,28 @@ func TestFragment_ClearBit(t *testing.T) { } } +/* We suspect this test is no longer valid under the new Tx + framework in which we always copy mmap-ed rows before + returning them. So we will comment it out for now. + If someone knows any reason for this to stick around, + let us know; we couldn't figure out how to adapt + to do a meaningful test under Tx. - jaten / tgruben + // What about rowcache timing. func TestFragment_RowcacheMap(t *testing.T) { var done int64 - f := mustOpenFragment("i", "f", viewStandard, 0, "") - + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // Under -race, this test turns out to take a fairly long time // to run with larger OpN, because we write 50,000 bits to // the bitmap, and everything is being race-detected, and we don't // actually need that many to get the result we care about. f.MaxOpN = 2000 - defer f.Clean(t) + defer f.Clean(t) // failing here with TestFragment_RowcacheMap: fragment_internal_test.go:2859: fragment /var/folders/2x/hm9gp5ys3k9gmm5f_vzm_6wc0000gn/T/pilosa-fragment-001943331: unmarshalled bitmap different: differing containers for key 0: vs ch := make(chan struct{}) @@ -136,6 +161,7 @@ func TestFragment_RowcacheMap(t *testing.T) { // force snapshot so we get a mmapped row... _ = f.Snapshot() row := f.mustRow(tx, 0) + tx.Commit(0) segment := row.Segments()[0] bitmap := segment.data @@ -159,14 +185,17 @@ func TestFragment_RowcacheMap(t *testing.T) { atomic.StoreInt64(&done, 1) <-ch } +*/ // Ensure a fragment can clear a row. func TestFragment_ClearRow(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // Set and then clear bits on the fragment. if _, err := f.setBit(tx, 1000, 1); err != nil { @@ -181,6 +210,9 @@ func TestFragment_ClearRow(t *testing.T) { if n := f.mustRow(tx, 1000).Count(); n != 0 { t.Fatalf("unexpected count: %d", n) } + panicOn(tx.Commit()) + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() // Close and reopen the fragment & verify the data. if err := f.Reopen(); err != nil { @@ -192,11 +224,13 @@ func TestFragment_ClearRow(t *testing.T) { // Ensure a fragment can set a row. func TestFragment_SetRow(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 7, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 7, "") + _ = idx defer f.Clean(t) // Obtain transction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() rowID := uint64(1000) @@ -224,6 +258,10 @@ func TestFragment_SetRow(t *testing.T) { t.Fatalf("expected changed value: %v", changed) } + panicOn(tx.Commit()) + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() + // Verify data on row. if cols := f.mustRow(tx, rowID).Columns(); !reflect.DeepEqual(cols, []uint64{7*ShardWidth + 1, 7*ShardWidth + 65537, 7*ShardWidth + 140000}) { t.Fatalf("unexpected columns after set row: %+v", cols) @@ -233,6 +271,10 @@ func TestFragment_SetRow(t *testing.T) { t.Fatalf("unexpected count after set row: %d", n) } + panicOn(tx.Commit()) + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() + // Close and reopen the fragment & verify the data. if err := f.Reopen(); err != nil { t.Fatal(err) @@ -244,11 +286,13 @@ func TestFragment_SetRow(t *testing.T) { // Ensure a fragment can set & read a value. func TestFragment_SetValue(t *testing.T) { t.Run("OK", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // Set value. if changed, err := f.setValue(tx, 100, 16, 3829); err != nil { @@ -272,14 +316,39 @@ func TestFragment_SetValue(t *testing.T) { } else if changed { t.Fatal("expected no change") } + + // same after Commit + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() + + // Read value. + if value, exists, err := f.value(tx, 100, 16); err != nil { + t.Fatal(err) + } else if value != 3829 { + t.Fatalf("unexpected value: %d", value) + } else if !exists { + t.Fatal("expected to exist") + } + + // Setting value should return no change. + if changed, err := f.setValue(tx, 100, 16, 3829); err != nil { + t.Fatal(err) + } else if changed { + t.Fatal("expected no change") + } }) t.Run("Overwrite", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // Set value. if changed, err := f.setValue(tx, 100, 16, 3829); err != nil { @@ -303,14 +372,32 @@ func TestFragment_SetValue(t *testing.T) { } else if !exists { t.Fatal("expected to exist") } + + // Read value after commit. + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() + + if value, exists, err := f.value(tx, 100, 16); err != nil { + t.Fatal(err) + } else if value != 2028 { + t.Fatalf("unexpected value: %d", value) + } else if !exists { + t.Fatal("expected to exist") + } + }) t.Run("Clear", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // Set value. if changed, err := f.setValue(tx, 100, 16, 3829); err != nil { @@ -334,14 +421,31 @@ func TestFragment_SetValue(t *testing.T) { } else if exists { t.Fatal("expected to not exist") } + + // Same after Commit + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() + + if value, exists, err := f.value(tx, 100, 16); err != nil { + t.Fatal(err) + } else if value != 0 { + t.Fatalf("unexpected value: %d", value) + } else if exists { + t.Fatal("expected to not exist") + } }) t.Run("NotExists", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set value. if changed, err := f.setValue(tx, 100, 10, 20); err != nil { @@ -358,6 +462,7 @@ func TestFragment_SetValue(t *testing.T) { } else if exists { t.Fatal("expected to not exist") } + }) t.Run("QuickCheck", func(t *testing.T) { @@ -370,11 +475,13 @@ func TestFragment_SetValue(t *testing.T) { values[i] = values[i] % (1 << bitDepth) } - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set values. m := make(map[uint64]int64) @@ -400,6 +507,25 @@ func TestFragment_SetValue(t *testing.T) { } } + // Same after Commit + if err := tx.Commit(); err != nil { + t.Fatal(err) + } + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() + + // Ensure values are set. + for columnID, value := range m { + v, exists, err := f.value(tx, columnID, bitDepth) + if err != nil { + t.Fatal(err) + } else if value != int64(v) { + t.Fatalf("value mismatch: columnID=%d, bitdepth=%d, value: %d != %d", columnID, bitDepth, value, v) + } else if !exists { + t.Fatalf("value should exist: columnID=%d", columnID) + } + } + return true }, nil); err != nil { t.Fatal(err) @@ -411,11 +537,13 @@ func TestFragment_SetValue(t *testing.T) { func TestFragment_Sum(t *testing.T) { const bitDepth = 16 - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set values. vals := []struct { @@ -473,11 +601,13 @@ func TestFragment_Sum(t *testing.T) { func TestFragment_MinMax(t *testing.T) { const bitDepth = 16 - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set values. if _, err := f.setValue(tx, 1000, bitDepth, 382); err != nil { @@ -553,11 +683,13 @@ func TestFragment_Range(t *testing.T) { const bitDepth = 16 t.Run("EQ", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set values. if _, err := f.setValue(tx, 1000, bitDepth, 382); err != nil { @@ -579,11 +711,13 @@ func TestFragment_Range(t *testing.T) { }) t.Run("NEQ", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set values. if _, err := f.setValue(tx, 1000, bitDepth, 382); err != nil { @@ -605,11 +739,13 @@ func TestFragment_Range(t *testing.T) { }) t.Run("LT", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set values. if _, err := f.setValue(tx, 1000, bitDepth, 382); err != nil { @@ -656,11 +792,13 @@ func TestFragment_Range(t *testing.T) { }) t.Run("LTRegression", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME if _, err := f.setValue(tx, 1, 1, 1); err != nil { t.Fatal(err) @@ -674,11 +812,13 @@ func TestFragment_Range(t *testing.T) { }) t.Run("LTMaxRegression", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME if _, err := f.setValue(tx, 1, 2, 3); err != nil { t.Fatal(err) @@ -694,11 +834,13 @@ func TestFragment_Range(t *testing.T) { }) t.Run("GT", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set values. if _, err := f.setValue(tx, 1000, bitDepth, 382); err != nil { @@ -745,11 +887,13 @@ func TestFragment_Range(t *testing.T) { }) t.Run("GTMinRegression", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME if _, err := f.setValue(tx, 1, 2, 0); err != nil { t.Fatal(err) @@ -765,11 +909,13 @@ func TestFragment_Range(t *testing.T) { }) t.Run("BETWEEN", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set values. if _, err := f.setValue(tx, 1000, bitDepth, 382); err != nil { @@ -820,7 +966,8 @@ func TestFragment_Range(t *testing.T) { // of setting values. func benchmarkSetValues(b *testing.B, bitDepth uint, f *fragment, cfunc func(uint64) uint64) { // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME column := uint64(0) for i := 0; i < b.N; i++ { @@ -836,13 +983,15 @@ func BenchmarkFragment_SetValue(b *testing.B) { depths := []uint{4, 8, 16} for _, bitDepth := range depths { name := fmt.Sprintf("Depth%d", bitDepth) - f := mustOpenFragment("i", "f", viewBSIGroupPrefix+"foo", 0, "none") + f, idx := mustOpenFragment("i", "f", viewBSIGroupPrefix+"foo", 0, "none") + _ = idx b.Run(name+"_Sparse", func(b *testing.B) { benchmarkSetValues(b, bitDepth, f, func(u uint64) uint64 { return (u + 70000) & (ShardWidth - 1) }) }) f.Clean(b) - f = mustOpenFragment("i", "f", viewBSIGroupPrefix+"foo", 0, "none") + f, idx = mustOpenFragment("i", "f", viewBSIGroupPrefix+"foo", 0, "none") + _ = idx b.Run(name+"_Dense", func(b *testing.B) { benchmarkSetValues(b, bitDepth, f, func(u uint64) uint64 { return (u + 1) & (ShardWidth - 1) }) }) @@ -854,7 +1003,8 @@ func BenchmarkFragment_SetValue(b *testing.B) { // of setting values using the special setter used for imports. func benchmarkImportValues(b *testing.B, bitDepth uint, f *fragment, cfunc func(uint64) uint64) { // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME column := uint64(0) b.StopTimer() @@ -877,12 +1027,14 @@ func BenchmarkFragment_ImportValue(b *testing.B) { depths := []uint{4, 8, 16} for _, bitDepth := range depths { name := fmt.Sprintf("Depth%d", bitDepth) - f := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + f, idx := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + _ = idx b.Run(name+"_Sparse", func(b *testing.B) { benchmarkImportValues(b, bitDepth, f, func(u uint64) uint64 { return (u + 70000) & (ShardWidth - 1) }) }) f.Clean(b) - f = mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + f, idx = mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + _ = idx b.Run(name+"_Dense", func(b *testing.B) { benchmarkImportValues(b, bitDepth, f, func(u uint64) uint64 { return (u + 1) & (ShardWidth - 1) }) }) @@ -916,14 +1068,16 @@ func BenchmarkFragment_RepeatedSmallImports(b *testing.B) { updateRows[i] = uint64(rand.Int63n(int64(numRows))) // row id updateCols[i] = uint64(rand.Int63n(ShardWidth)) // column id } - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx f.MaxOpN = opN defer f.Clean(b) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME - err := f.importRoaringT(getZipfRowsSliceRoaring(uint64(numRows), 1, 0, ShardWidth), false) + err := f.importRoaringT(tx, getZipfRowsSliceRoaring(uint64(numRows), 1, 0, ShardWidth), false) if err != nil { b.Fatalf("importing base data for benchmark: %v", err) } @@ -956,18 +1110,21 @@ func BenchmarkFragment_RepeatedSmallImportsRoaring(b *testing.B) { b.StopTimer() // build the update data set all at once - this will get applied // to a fragment in numUpdates batches - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx f.MaxOpN = opN defer f.Clean(b) + tx := f.txTestingOnly + defer tx.Rollback() - err := f.importRoaringT(getZipfRowsSliceRoaring(numRows, 1, 0, ShardWidth), false) + err := f.importRoaringT(tx, getZipfRowsSliceRoaring(numRows, 1, 0, ShardWidth), false) if err != nil { b.Fatalf("importing base data for benchmark: %v", err) } for i := 0; i < numUpdates; i++ { data := getUpdataRoaring(numRows, bitsPerUpdate, int64(i)) b.StartTimer() - err := f.importRoaringT(data, false) + err := f.importRoaringT(tx, data, false) b.StopTimer() if err != nil { b.Fatalf("doing small roaring import: %v", err) @@ -1006,11 +1163,13 @@ func BenchmarkFragment_RepeatedSmallValueImports(b *testing.B) { b.Run(fmt.Sprintf("Updates%dVals%dOpN%d", numUpdates, valsPerUpdate, opN), func(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() - f := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + f, idx := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + _ = idx f.MaxOpN = opN // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME err := f.importValue(tx, initialCols, initialVals, 21, false) if err != nil { @@ -1038,11 +1197,13 @@ func BenchmarkFragment_RepeatedSmallValueImports(b *testing.B) { // Ensure a fragment can snapshot correctly. func TestFragment_Snapshot(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set and then clear bits on the fragment. if _, err := f.setBit(tx, 1000, 1); err != nil { @@ -1070,11 +1231,13 @@ func TestFragment_Snapshot(t *testing.T) { // Ensure a fragment can iterate over all bits in order. func TestFragment_ForEachBit(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set bits on the fragment. if _, err := f.setBit(tx, 100, 20); err != nil { @@ -1102,11 +1265,13 @@ func TestFragment_ForEachBit(t *testing.T) { // Ensure a fragment can return the top n results. func TestFragment_Top(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set bits on the rows 100, 101, & 102. f.mustSetBits(tx, 100, 1, 3, 200) @@ -1128,11 +1293,13 @@ func TestFragment_Top(t *testing.T) { // Ensure a fragment can filter rows when retrieving the top n rows. func TestFragment_Top_Filter(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set bits on the rows 100, 101, & 102. f.mustSetBits(tx, 100, 1, 3, 200) @@ -1167,11 +1334,13 @@ func TestFragment_Top_Filter(t *testing.T) { // Ensure a fragment can return top rows that intersect with an input row. func TestFragment_TopN_Intersect(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Create an intersecting input row. src := NewRow(1, 2, 3) @@ -1201,11 +1370,13 @@ func TestFragment_TopN_Intersect_Large(t *testing.T) { t.Skip("short mode") } - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Create an intersecting input row. src := NewRow( @@ -1225,7 +1396,7 @@ func TestFragment_TopN_Intersect_Large(t *testing.T) { if err != nil { t.Fatalf("writing to bytes: %v", err) } - err = f.importRoaringT(b.Bytes(), false) + err = f.importRoaringT(tx, b.Bytes(), false) if err != nil { t.Fatalf("importing data: %v", err) } @@ -1252,11 +1423,13 @@ func TestFragment_TopN_Intersect_Large(t *testing.T) { // Ensure a fragment can return top rows when specified by ID. func TestFragment_TopN_IDs(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set bits on various rows. f.mustSetBits(tx, 100, 1, 2, 3) @@ -1276,11 +1449,13 @@ func TestFragment_TopN_IDs(t *testing.T) { // Ensure a fragment return none if CacheTypeNone is set func TestFragment_TopN_NopCache(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeNone) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeNone) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set bits on various rows. f.mustSetBits(tx, 100, 1, 2, 3) @@ -1331,7 +1506,10 @@ func TestFragment_TopN_CacheSize(t *testing.T) { defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + + tx := index.Txf.NewTx(Txo{Write: writable, Index: index, Fragment: f}) + f.txTestingOnly = tx + defer tx.Rollback() // okay to call 2x. f.Clean(t) will do Rollback() too. // Set bits on various rows. f.mustSetBits(tx, 100, 1, 2, 3) @@ -1363,11 +1541,13 @@ func TestFragment_TopN_CacheSize(t *testing.T) { // Ensure fragment can return a checksum for its blocks. func TestFragment_Checksum(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // Retrieve checksum and set bits. orig, err := f.Checksum() @@ -1379,6 +1559,7 @@ func TestFragment_Checksum(t *testing.T) { } else if _, err := f.setBit(tx, HashBlockSize*2, 200); err != nil { t.Fatal(err) } + panicOn(tx.Commit()) // Ensure new checksum is different. if chksum, err := f.Checksum(); err != nil { @@ -1390,11 +1571,12 @@ func TestFragment_Checksum(t *testing.T) { // Ensure fragment can return a checksum for a given block. func TestFragment_Blocks(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly // Retrieve initial checksum. var prev []FragmentBlock @@ -1403,7 +1585,8 @@ func TestFragment_Blocks(t *testing.T) { if _, err := f.setBit(tx, 0, 0); err != nil { t.Fatal(err) } - blocks, err := f.Blocks() + panicOn(tx.Commit()) + blocks, err := f.Blocks() // FAIL: TestFragment_Blocks b/c 0 blocks back if err != nil { t.Fatal(err) } else if blocks[0].Checksum == nil { @@ -1411,10 +1594,12 @@ func TestFragment_Blocks(t *testing.T) { } prev = blocks + tx = idx.Txf.NewTx(Txo{Write: true, Index: idx, Fragment: f}) // Set bit on different row. if _, err := f.setBit(tx, 20, 0); err != nil { t.Fatal(err) } + panicOn(tx.Commit()) blocks, err = f.Blocks() if err != nil { t.Fatal(err) @@ -1424,9 +1609,12 @@ func TestFragment_Blocks(t *testing.T) { prev = blocks // Set bit on different column. + tx = idx.Txf.NewTx(Txo{Write: true, Index: idx, Fragment: f}) + f.txTestingOnly = tx // let the Clean do the Rollback if _, err := f.setBit(tx, 20, 100); err != nil { t.Fatal(err) } + panicOn(tx.Commit()) blocks, err = f.Blocks() if err != nil { t.Fatal(err) @@ -1437,16 +1625,19 @@ func TestFragment_Blocks(t *testing.T) { // Ensure fragment returns an empty checksum if no data exists for a block. func TestFragment_Blocks_Empty(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly //&RoaringTx{fragment: f} + defer tx.Rollback() // Set bits on a different block. if _, err := f.setBit(tx, 100, 1); err != nil { t.Fatal(err) } + panicOn(tx.Commit()) // f.Blocks() will start a new Tx, so the SetBit needs to be visible before that. // Ensure checksum for block 1 is blank. if blocks, err := f.Blocks(); err != nil { @@ -1460,11 +1651,13 @@ func TestFragment_Blocks_Empty(t *testing.T) { // Ensure a fragment's cache can be persisted between restarts. func TestFragment_LRUCache_Persistence(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeLRU) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeLRU) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set bits on the fragment. for i := uint64(0); i < 1000; i++ { @@ -1517,7 +1710,7 @@ func TestFragment_RankCache_Persistence(t *testing.T) { } // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := index.Txf.NewTx(Txo{Write: writable, Index: index, Fragment: f}) // Set bits on the fragment. for i := uint64(0); i < 1000; i++ { @@ -1551,7 +1744,8 @@ func TestFragment_RankCache_Persistence(t *testing.T) { // Ensure a fragment can be copied to another fragment. func TestFragment_WriteTo_ReadFrom(t *testing.T) { - f0 := mustOpenFragment("i", "f", viewStandard, 0, "") + f0, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f0.Clean(t) // Obtain transaction. @@ -1579,7 +1773,8 @@ func TestFragment_WriteTo_ReadFrom(t *testing.T) { } // Read into another fragment. - f1 := mustOpenFragment("i", "f", viewStandard, 0, "") + f1, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f1.Clean(t) if rn, err := f1.ReadFrom(&buf); err != nil { @@ -1632,12 +1827,14 @@ func BenchmarkFragment_Blocks(b *testing.B) { } func BenchmarkFragment_IntersectionCount(b *testing.B) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(b) f.MaxOpN = math.MaxInt32 // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Generate some intersecting data. for i := 0; i < 10000; i += 2 { @@ -1666,11 +1863,13 @@ func BenchmarkFragment_IntersectionCount(b *testing.B) { } func TestFragment_Tanimoto(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME src := NewRow(1, 2, 3) @@ -1692,11 +1891,13 @@ func TestFragment_Tanimoto(t *testing.T) { } func TestFragment_Zero_Tanimoto(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME src := NewRow(1, 2, 3) @@ -1720,11 +1921,13 @@ func TestFragment_Zero_Tanimoto(t *testing.T) { } func TestFragment_Snapshot_Run(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set bits on the fragment. for i := uint64(1); i < 3; i++ { @@ -1750,11 +1953,13 @@ func TestFragment_Snapshot_Run(t *testing.T) { // Ensure a fragment can set mutually exclusive values. func TestFragment_SetMutex(t *testing.T) { - f := mustOpenMutexFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenMutexFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME var cols []uint64 @@ -1867,11 +2072,13 @@ func TestFragment_ImportSet(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("importset%d", i), func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set import. err := f.bulkImport(tx, test.setRowIDs, test.setColIDs, &ImportOptions{}) @@ -1906,11 +2113,13 @@ func TestFragment_ImportSet(t *testing.T) { func TestFragment_ConcurrentImport(t *testing.T) { t.Run("bulkImportStandard", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME eg := errgroup.Group{} eg.Go(func() error { return f.bulkImportStandard(tx, []uint64{1, 2}, []uint64{1, 2}, &ImportOptions{}) }) @@ -2006,11 +2215,13 @@ func TestFragment_ImportMutex(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("importmutex%d", i), func(t *testing.T) { - f := mustOpenMutexFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenMutexFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set import. err := f.bulkImport(tx, test.setRowIDs, test.setColIDs, &ImportOptions{}) @@ -2128,11 +2339,13 @@ func TestFragment_ImportBool(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("importmutex%d", i), func(t *testing.T) { - f := mustOpenBoolFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenBoolFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Set import. err := f.bulkImport(tx, test.setRowIDs, test.setColIDs, &ImportOptions{}) @@ -2191,11 +2404,13 @@ func BenchmarkFragment_Snapshot(b *testing.B) { } func BenchmarkFragment_FullSnapshot(b *testing.B) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(b) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // Generate some intersecting data. maxX := ShardWidth / 2 @@ -2258,9 +2473,10 @@ func BenchmarkFragment_Import(b *testing.B) { // since bulkImport modifies the input slices, we make new copies for each round copy(rowsUse, rows) copy(colsUse, cols) - f := mustOpenFragment("i", "f", viewStandard, 0, "") - // Obtain transaction. - tx := &RoaringTx{fragment: f} + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx // Obtain transaction. + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME b.StartTimer() if err := f.bulkImport(tx, rowsUse, colsUse, options); err != nil { b.Errorf("Error Building Sample: %s", err) @@ -2285,9 +2501,12 @@ func BenchmarkImportRoaring(b *testing.B) { b.Run(fmt.Sprintf("Rows%dCache_%s", numRows, cacheType), func(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { - f := mustOpenFragment("i", fmt.Sprintf("r%dc%s", numRows, cacheType), viewStandard, 0, cacheType) + f, _ := mustOpenFragment("i", fmt.Sprintf("r%dc%s", numRows, cacheType), viewStandard, 0, cacheType) b.StartTimer() - err := f.importRoaringT(data, false) + tx := f.txTestingOnly + defer tx.Rollback() + + err := f.importRoaringT(tx, data, false) if err != nil { // we don't actually particularly // care whether this succeeds, @@ -2324,14 +2543,17 @@ func BenchmarkImportRoaringConcurrent(b *testing.B) { frags := make([]*fragment, concurrency) for i := 0; i < b.N; i++ { for j := 0; j < concurrency; j++ { - frags[j] = mustOpenFragment("i", "f", viewStandard, uint64(j), cacheType) + frags[j], _ = mustOpenFragment("i", "f", viewStandard, uint64(j), cacheType) } eg := errgroup.Group{} b.StartTimer() for j := 0; j < concurrency; j++ { j := j eg.Go(func() error { - err := frags[j].importRoaringT(data[j], false) + tx := frags[j].txTestingOnly + defer tx.Rollback() + + err := frags[j].importRoaringT(tx, data[j], false) // error unimportant if it happened, but we want // any snapshots to have finished. _ = defaultSnapshotQueue.Await(frags[j]) @@ -2367,7 +2589,7 @@ func BenchmarkImportRoaringUpdateConcurrent(b *testing.B) { frags := make([]*fragment, concurrency) for i := 0; i < b.N; i++ { for j := 0; j < concurrency; j++ { - frags[j] = mustOpenFragment("i", "f", viewStandard, uint64(j), cacheType) + frags[j], _ = mustOpenFragment("i", "f", viewStandard, uint64(j), cacheType) // the cost of actually doing the op log for the large initial data set // is excessive. force storage into snapshotted state, then use import @@ -2386,7 +2608,10 @@ func BenchmarkImportRoaringUpdateConcurrent(b *testing.B) { for j := 0; j < concurrency; j++ { j := j eg.Go(func() error { - err := frags[j].importRoaringT(updata, false) + tx := frags[j].txTestingOnly + defer tx.Rollback() + + err := frags[j].importRoaringT(tx, updata, false) err2 := defaultSnapshotQueue.Await(frags[j]) if err == nil { err = err2 @@ -2420,10 +2645,11 @@ func BenchmarkImportStandard(b *testing.B) { for i := 0; i < b.N; i++ { copy(rowIDs, rowIDsOrig) copy(columnIDs, columnIDsOrig) - f := mustOpenFragment("i", fmt.Sprintf("r%dc%s", numRows, cacheType), viewStandard, 0, cacheType) - + f, idx := mustOpenFragment("i", fmt.Sprintf("r%dc%s", numRows, cacheType), viewStandard, 0, cacheType) + _ = idx // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME b.StartTimer() err := f.bulkImport(tx, rowIDs, columnIDs, &ImportOptions{}) @@ -2451,12 +2677,17 @@ func BenchmarkImportRoaringUpdate(b *testing.B) { b.Run(name, func(b *testing.B) { b.StopTimer() for i := 0; i < b.N; i++ { - f := mustOpenFragment("i", fmt.Sprintf("r%dc%dcache_%s", numRows, numCols, cacheType), viewStandard, 0, cacheType) + f, idx := mustOpenFragment("i", fmt.Sprintf("r%dc%dcache_%s", numRows, numCols, cacheType), viewStandard, 0, cacheType) + _ = idx + tx := f.txTestingOnly + defer tx.Rollback() // the cost of actually doing the op log for the large initial data set // is excessive. force storage into snapshotted state, then use import // to generate an op log and/or snapshot. - _, _, err := f.storage.ImportRoaringBits(data, false, false, 0) + itr, err := roaring.NewRoaringIterator(data) + panicOn(err) + _, _, err = tx.ImportRoaringBits(f.index, f.field, f.view, f.shard, itr, false, false, 0) if err != nil { b.Errorf("import error: %v", err) } @@ -2465,7 +2696,7 @@ func BenchmarkImportRoaringUpdate(b *testing.B) { b.Errorf("snapshot after import error: %v", err) } b.StartTimer() - err = f.importRoaringT(updata, false) + err = f.importRoaringT(tx, updata, false) if err != nil { f.Clean(b) b.Errorf("import error: %v", err) @@ -2512,13 +2743,16 @@ func BenchmarkUpdatePathological(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { b.StopTimer() - f := mustOpenFragment("i", "f", viewStandard, 0, DefaultCacheType) - err := f.importRoaringT(exists, false) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, DefaultCacheType) + _ = idx + tx := f.txTestingOnly + defer tx.Rollback() + err := f.importRoaringT(tx, exists, false) if err != nil { b.Fatalf("importing roaring: %v", err) } b.StartTimer() - err = f.importRoaringT(inc, false) + err = f.importRoaringT(tx, inc, false) if err != nil { b.Fatalf("importing second: %v", err) } @@ -2531,11 +2765,14 @@ var bigFrag string func initBigFrag() { if bigFrag == "" { - f := mustOpenFragment("i", "f", viewStandard, 0, DefaultCacheType) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, DefaultCacheType) + _ = idx + tx := f.txTestingOnly + defer tx.Rollback() for i := int64(0); i < 10; i++ { // 10 million rows, 1 bit per column, random seeded by i data := getZipfRowsSliceRoaring(10000000, i, 0, ShardWidth) - err := f.importRoaringT(data, false) + err := f.importRoaringT(tx, data, false) if err != nil { panic(fmt.Sprintf("setting up fragment data: %v", err)) } @@ -2545,6 +2782,7 @@ func initBigFrag() { panic(fmt.Sprintf("closing fragment: %v", err)) } bigFrag = f.path + panicOn(tx.Commit()) } } @@ -2610,13 +2848,24 @@ func BenchmarkImportRoaringIntoLargeFragment(b *testing.B) { } origF.Close() fi.Close() - nf := newFragment(NewHolder(DefaultPartitionN), fi.Name(), "i", "f", viewStandard, 0, 0) + + // want to do this, but no path argument. + //nf, idx := mustOpenFragmentFlags(index, field, view string, shard uint64, cacheType string, flags byte) + + th := newTestHolder() + idx := fragTestMustOpenIndex("i", th, IndexOptions{}) + nf := newFragment(th, fi.Name(), "i", "f", viewStandard, 0, 0) + + tx := idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: nf}) + defer tx.Rollback() + + //nf := newFragment(NewHolder(DefaultPartitionN), fi.Name(), "i", "f", viewStandard, 0, 0) err = nf.Open() if err != nil { b.Fatalf("opening fragment: %v", err) } b.StartTimer() - err = nf.importRoaringT(updata, false) + err = nf.importRoaringT(tx, updata, false) b.StopTimer() if err != nil { b.Fatalf("bulkImport: %v", err) @@ -2627,13 +2876,15 @@ func BenchmarkImportRoaringIntoLargeFragment(b *testing.B) { } func TestGetZipfRowsSliceRoaring(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, DefaultCacheType) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, DefaultCacheType) + _ = idx // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() data := getZipfRowsSliceRoaring(10, 1, 0, ShardWidth) - err := f.importRoaringT(data, false) + err := f.importRoaringT(tx, data, false) if err != nil { t.Fatalf("importing roaring: %v", err) } @@ -2813,6 +3064,9 @@ func (f *fragment) Clean(t testing.TB) { } } }() + if f.txTestingOnly != nil { + f.txTestingOnly.Rollback() + } errc := f.Close() // prevent double-closes of generation during testing. f.gen = nil @@ -2828,8 +3082,9 @@ func (f *fragment) Clean(t testing.TB) { } // importRoaringT calls importRoaring with context.Background() for convenience -func (f *fragment) importRoaringT(data []byte, clear bool) error { - return f.importRoaring(context.Background(), data, clear) +func (f *fragment) importRoaringT(tx Tx, data []byte, clear bool) error { + + return f.importRoaring(context.Background(), tx, data, clear) } // CleanKeep is just like Clean(), but it doesn't remove the @@ -2847,11 +3102,11 @@ func (f *fragment) CleanKeep(t testing.TB) { } // mustOpenFragment returns a new instance of Fragment with a temporary path. -func mustOpenFragment(index, field, view string, shard uint64, cacheType string) *fragment { +func mustOpenFragment(index, field, view string, shard uint64, cacheType string) (*fragment, *Index) { return mustOpenFragmentFlags(index, field, view, shard, cacheType, 0) } -func mustOpenBSIFragment(index, field, view string, shard uint64) *fragment { +func mustOpenBSIFragment(index, field, view string, shard uint64) (*fragment, *Index) { return mustOpenFragmentFlags(index, field, view, shard, "", 1) } @@ -2861,8 +3116,33 @@ func init() { testHolder.SnapshotQueue = newSnapshotQueue(1, 1, nil) } +func newTestHolder() *Holder { + h := NewHolder(DefaultPartitionN) + h.SnapshotQueue = newSnapshotQueue(1, 1, nil) + return h +} + +// 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 + idx, err := holder.createIndex(index, opt) + panicOn(err) + + idx.keys = opt.Keys + idx.trackExistence = opt.TrackExistence + + if err := idx.Open(); err != nil { + panic(err) + } + return idx +} + // mustOpenFragment returns a new instance of Fragment with a temporary path. -func mustOpenFragmentFlags(index, field, view string, shard uint64, cacheType string, flags byte) *fragment { +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) @@ -2873,7 +3153,15 @@ func mustOpenFragmentFlags(index, field, view string, shard uint64, cacheType st cacheType = DefaultCacheType } - f := newFragment(testHolder, file.Name(), index, field, view, shard, flags) + // new: + th := newTestHolder() + idx := fragTestMustOpenIndex(index, th, IndexOptions{}) + f := newFragment(th, file.Name(), index, field, view, shard, flags) + + tx := idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + f.txTestingOnly = tx + + //old: f := newFragment(testHolder, file.Name(), index, field, view, shard, flags) f.CacheType = cacheType f.RowAttrStore = &memAttrStore{ @@ -2883,21 +3171,21 @@ func mustOpenFragmentFlags(index, field, view string, shard uint64, cacheType st if err := f.Open(); err != nil { panic(err) } - return f + return f, idx } // mustOpenMutexFragment returns a new instance of Fragment for a mutex field. -func mustOpenMutexFragment(index, field, view string, shard uint64, cacheType string) *fragment { - frag := mustOpenFragment(index, field, view, shard, cacheType) +func mustOpenMutexFragment(index, field, view string, shard uint64, cacheType string) (*fragment, *Index) { + frag, idx := mustOpenFragment(index, field, view, shard, cacheType) frag.mutexVector = newRowsVector(frag) - return frag + return frag, idx } // mustOpenBoolFragment returns a new instance of Fragment for a bool field. -func mustOpenBoolFragment(index, field, view string, shard uint64, cacheType string) *fragment { - frag := mustOpenFragment(index, field, view, shard, cacheType) +func mustOpenBoolFragment(index, field, view string, shard uint64, cacheType string) (*fragment, *Index) { + frag, idx := mustOpenFragment(index, field, view, shard, cacheType) frag.mutexVector = newBoolVector(frag) - return frag + return frag, idx } // Reopen closes the fragment and reopens it as a new instance. @@ -2932,9 +3220,11 @@ func addToBitmap(bm *roaring.Bitmap, rowID uint64, columnIDs ...uint64) { // Test Various methods of retrieving RowIDs func TestFragment_RowsIteration(t *testing.T) { t.Run("firstContainer", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME expectedAll := make([]uint64, 0) expectedOdd := make([]uint64, 0) @@ -2964,9 +3254,11 @@ func TestFragment_RowsIteration(t *testing.T) { }) t.Run("secondRow", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME expected := []uint64{1, 2} if _, err := f.setBit(tx, 1, 66000); err != nil { @@ -2993,9 +3285,11 @@ func TestFragment_RowsIteration(t *testing.T) { }) t.Run("combinations", func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME expectedRows := make([]uint64, 0) for r := uint64(1); r < uint64(10000); r += 250 { @@ -3045,9 +3339,11 @@ func TestFragment_RoaringImport(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("importroaring%d", i), func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() for num, input := range test { buf := &bytes.Buffer{} @@ -3056,7 +3352,7 @@ func TestFragment_RoaringImport(t *testing.T) { if err != nil { t.Fatalf("writing to buffer: %v", err) } - err = f.importRoaringT(buf.Bytes(), false) + err = f.importRoaringT(tx, buf.Bytes(), false) if err != nil { t.Fatalf("importing roaring: %v", err) } @@ -3093,9 +3389,12 @@ func TestFragment_RoaringImportTopN(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("importroaring%d", i), func(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + + tx := f.txTestingOnly + defer tx.Rollback() options := &ImportOptions{} err := f.bulkImport(tx, test.rowIDs, test.colIDs, options) @@ -3132,7 +3431,7 @@ func TestFragment_RoaringImportTopN(t *testing.T) { if err != nil { t.Fatalf("writing to buffer: %v", err) } - err = f.importRoaringT(buf.Bytes(), false) + err = f.importRoaringT(tx, buf.Bytes(), false) if err != nil { t.Fatalf("importing roaring: %v", err) } @@ -3231,9 +3530,11 @@ func calcExpected(inputs ...[]uint64) [][]uint64 { func TestFragmentRowIterator(t *testing.T) { t.Run("basic", func(t *testing.T) { - f := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked) + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME f.mustSetBits(tx, 0, 0) f.mustSetBits(tx, 1, 0) @@ -3275,9 +3576,11 @@ func TestFragmentRowIterator(t *testing.T) { }) t.Run("skipped rows", func(t *testing.T) { - f := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked) + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME f.mustSetBits(tx, 1, 0) f.mustSetBits(tx, 3, 0) @@ -3319,9 +3622,11 @@ func TestFragmentRowIterator(t *testing.T) { }) t.Run("basic wrapped", func(t *testing.T) { - f := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked) + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME f.mustSetBits(tx, 0, 0) f.mustSetBits(tx, 1, 0) @@ -3352,9 +3657,11 @@ func TestFragmentRowIterator(t *testing.T) { }) t.Run("skipped rows wrapped", func(t *testing.T) { - f := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeRanked) + _ = idx defer f.Clean(t) - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME f.mustSetBits(tx, 1, 0) f.mustSetBits(tx, 3, 0) @@ -3386,10 +3693,11 @@ func TestFragmentRowIterator(t *testing.T) { } func TestUnionInPlaceMapped(t *testing.T) { - f := mustOpenFragment("i", "f", "v", 0, CacheTypeNone) + f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeNone) // note: clean has to be deferred first, because it has to run with // the lock *not* held, because it is sometimes so it has to grab the // lock... + _ = idx defer f.Clean(t) f.mu.Lock() @@ -3461,7 +3769,8 @@ func randPositions(n int, r *rand.Rand) []uint64 { } func TestFragmentPositionsForValue(t *testing.T) { - f := mustOpenFragment("i", "f", "v", 0, CacheTypeNone) + f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeNone) + _ = idx defer f.Clean(t) tests := []struct { @@ -3543,11 +3852,13 @@ func TestFragmentPositionsForValue(t *testing.T) { } func TestIntLTRegression(t *testing.T) { - f := mustOpenFragment("i", "f", "v", 0, CacheTypeNone) + f, idx := mustOpenFragment("i", "f", "v", 0, CacheTypeNone) + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME _, err := f.setValue(tx, 1, 6, 33) if err != nil { @@ -3611,11 +3922,13 @@ func TestImportClearRestart(t *testing.T) { } } - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx f.MaxOpN = maxOpN // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() err := f.bulkImport(tx, testrows, testcols, &ImportOptions{}) if err != nil { @@ -3624,14 +3937,17 @@ func TestImportClearRestart(t *testing.T) { if expOpN <= maxOpN && f.opN != expOpN { t.Errorf("unexpected opN - %d is not %d", f.opN, expOpN) } - check(t, f, exp) + check(t, tx, f, exp) err = f.Close() if err != nil { t.Fatalf("closing fragment: %v", err) } + panicOn(tx.Commit()) err = f.Open() + tx = idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() if err != nil { t.Fatalf("reopening fragment: %v", err) } @@ -3640,16 +3956,20 @@ func TestImportClearRestart(t *testing.T) { t.Errorf("unexpected opN after close/open %d is not %d", f.opN, expOpN) } - check(t, f, exp) + check(t, tx, f, exp) f2 := newFragment(NewHolder(DefaultPartitionN), f.path, "i", "f", viewStandard, 0, 0) f2.MaxOpN = maxOpN f2.CacheType = f.CacheType + tx2 := idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f2}) + defer tx2.Rollback() + err = f.closeStorage() if err != nil { t.Fatalf("closing storage: %v", err) } + panicOn(tx.Commit()) // match the f.closeStorage which overlaps the f2 creation. err = f2.Open() if err != nil { @@ -3660,11 +3980,11 @@ func TestImportClearRestart(t *testing.T) { t.Errorf("unexpected opN after close/open %d is not %d", f2.opN, expOpN) } - check(t, f2, exp) + check(t, tx2, f2, exp) copy(testrows, test.rows) copy(testcols, test.cols) - err = f2.bulkImport(tx, testrows, testcols, &ImportOptions{Clear: true}) + err = f2.bulkImport(tx2, testrows, testcols, &ImportOptions{Clear: true}) if err != nil { t.Fatalf("clearing imported data: %v", err) } @@ -3674,24 +3994,29 @@ func TestImportClearRestart(t *testing.T) { exp[row] = nil } - check(t, f2, exp) + check(t, tx2, f2, exp) f3 := newFragment(NewHolder(DefaultPartitionN), f2.path, "i", "f", viewStandard, 0, 0) f3.MaxOpN = maxOpN f3.CacheType = f.CacheType + tx3 := idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f3}) + defer tx3.Rollback() + err = f2.closeStorage() if err != nil { t.Fatalf("f2 closing storage: %v", err) } + panicOn(tx2.Commit()) 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) - check(t, f3, exp) + check(t, tx3, f3, exp) }) @@ -3699,8 +4024,7 @@ func TestImportClearRestart(t *testing.T) { } } -func check(t *testing.T, f *fragment, exp map[uint64]map[uint64]struct{}) { - tx := &RoaringTx{fragment: f} +func check(t *testing.T, tx Tx, f *fragment, exp map[uint64]map[uint64]struct{}) { for rowID, colsExp := range exp { colsAct := f.mustRow(tx, rowID).Columns() @@ -3729,13 +4053,29 @@ func check(t *testing.T, f *fragment, exp map[uint64]map[uint64]struct{}) { } func TestImportValueConcurrent(t *testing.T) { - f := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + + f, idx := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + switch idx.Txf.TxType() { + case blueGreenBadgerRoaring, blueGreenRoaringBadger: + t.Skip(fmt.Sprintf("skipping TestImportValueConcurrent under " + + "blueGreenTx because the lack of transactional consistency " + + "from Roaring-per-file will create false comparison " + + "failures.")) + } + + // Since eg.Go gets called multiple times below, each + // time needs its own Tx. So close the default one and + // make a new one each time. + tx := f.txTestingOnly + tx.Rollback() + defer f.Clean(t) eg := &errgroup.Group{} for i := 0; i < 4; i++ { i := i eg.Go(func() error { - tx := &RoaringTx{fragment: f} + tx := idx.Txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f}) + defer tx.Rollback() for j := uint64(0); j < 10; j++ { err := f.importValue(tx, []uint64{j}, []int64{int64(rand.Int63n(1000))}, 10, i%2 == 0) if err != nil { @@ -3771,12 +4111,14 @@ func TestImportMultipleValues(t *testing.T) { for i, test := range tests { for _, maxOpN := range []int{0, 10000} { // test small/large write t.Run(fmt.Sprintf("%dLowOpN", i), func(t *testing.T) { - f := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + f, idx := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + _ = idx f.MaxOpN = maxOpN defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME err := f.importValue(tx, test.cols, test.vals, test.depth, false) if err != nil { @@ -3832,12 +4174,14 @@ func TestImportValueRowCache(t *testing.T) { for i, test := range tests { for _, maxOpN := range []int{1, 10000} { t.Run(fmt.Sprintf("%dMaxOpN%d", i, maxOpN), func(t *testing.T) { - f := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) + f, idx := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0) f.MaxOpN = maxOpN + _ = idx defer f.Clean(t) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // First import (tc1) if err := f.importValue(tx, test.tc1.cols, test.tc1.vals, test.tc1.depth, false); err != nil { @@ -3866,13 +4210,15 @@ func TestImportValueRowCache(t *testing.T) { } func TestFragmentConcurrentReadWrite(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + f, idx := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) + _ = idx defer f.Clean(t) eg := &errgroup.Group{} eg.Go(func() error { // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME for i := uint64(0); i < 1000; i++ { _, err := f.setBit(tx, i%4, i) @@ -3884,7 +4230,8 @@ func TestFragmentConcurrentReadWrite(t *testing.T) { }) // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME acc := uint64(0) for i := uint64(0); i < 100; i++ { @@ -3899,7 +4246,9 @@ func TestFragmentConcurrentReadWrite(t *testing.T) { } func TestRemapCache(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx + // request a panic that doesn't kill the program on fault wouldFault := debug.SetPanicOnFault(true) defer func() { @@ -3917,7 +4266,8 @@ func TestRemapCache(t *testing.T) { }() // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() //LOOKATME // create a container _, err := f.storage.Add(65537) @@ -3957,10 +4307,11 @@ func TestRemapCache(t *testing.T) { } func TestFragment_Bug_Q2DoubleDelete(t *testing.T) { - f := mustOpenFragment("i", "f", viewStandard, 0, "") - + f, idx := mustOpenFragment("i", "f", viewStandard, 0, "") + _ = idx // Obtain transaction. - tx := &RoaringTx{fragment: f} + tx := f.txTestingOnly + defer tx.Rollback() // byShardWidth is a map of the same roaring (fragment) data generated // with different shard widths. @@ -3976,11 +4327,14 @@ func TestFragment_Bug_Q2DoubleDelete(t *testing.T) { b = data } + _ = idx defer f.Clean(t) - err := f.importRoaringT(b, false) + + err := f.importRoaringT(tx, b, false) if err != nil { t.Fatalf("importing roaring: %v", err) } + //check the bit res := f.mustRow(tx, 1).Columns() if len(res) < 1 || f.mustRow(tx, 1).Columns()[0] != 1 { @@ -3991,25 +4345,30 @@ func TestFragment_Bug_Q2DoubleDelete(t *testing.T) { if !changed { t.Fatalf("expected change got %v", changed) } + //check missing res = f.mustRow(tx, 1).Columns() if len(res) != 0 { t.Fatalf("expected nothing got %v", res) } + // import again - err = f.importRoaringT(b, false) + err = f.importRoaringT(tx, b, false) if err != nil { t.Fatalf("importing roaring: %v", err) } + //check res = f.mustRow(tx, 1).Columns() if len(res) < 1 || f.mustRow(tx, 1).Columns()[0] != 1 { t.Fatalf("again expecting 1 got: %v", res) } + changed, _ = f.clearBit(tx, 1, 1) if !changed { - t.Fatalf("again expected change got %v", changed) + t.Fatalf("again expected change got %v", changed) // again expected change got false } + //check missing res = f.mustRow(tx, 1).Columns() if len(res) != 0 { diff --git a/go.mod b/go.mod index 485e2a5be..acc83bb52 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,12 @@ require ( github.com/benbjohnson/immutable v0.2.0 github.com/boltdb/bolt v1.3.1 github.com/cespare/xxhash v1.1.0 - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/davecgh/go-spew v1.1.1 + github.com/dchest/blake2b v1.0.0 // indirect + github.com/dgraph-io/badger v1.6.1-0.20191025180844-32a2548a9d85 // indirect + github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200709123515-8e896a7af361 github.com/go-ole/go-ole v1.2.4 // indirect github.com/gogo/protobuf v1.2.0 github.com/golang/protobuf v1.3.3 @@ -31,17 +34,17 @@ require ( github.com/satori/go.uuid v1.2.0 github.com/shirou/gopsutil v2.18.12+incompatible github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect - github.com/spf13/cobra v0.0.3 + github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.3 - github.com/spf13/viper v1.3.1 + github.com/spf13/viper v1.3.2 github.com/uber-go/atomic v1.4.0 // indirect github.com/uber/jaeger-client-go v2.16.0+incompatible github.com/uber/jaeger-lib v2.2.0+incompatible // indirect + github.com/willoch/tago v0.0.0-20180311150625-8f2f8e8900dc // indirect + github.com/zeebo/blake3 v0.0.4 go.uber.org/atomic v1.4.0 // indirect golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 // indirect - golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 // indirect golang.org/x/sync v0.0.0-20190423024810-112230192c58 - golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 // indirect golang.org/x/text v0.3.2 // indirect google.golang.org/grpc v1.28.0 modernc.org/mathutil v1.0.0 diff --git a/go.sum b/go.sum index 997603c23..fbed4774f 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/CAFxX/gcnotifier v0.0.0-20190112062741-224a280d589d h1:n0G4ckjMEj7bWu github.com/CAFxX/gcnotifier v0.0.0-20190112062741-224a280d589d/go.mod h1:Rn2zM2MnHze07LwkneP48TWt6UiZhzQTwCvw6djVGfE= github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895 h1:dmc/C8bpE5VkQn65PNbbyACDC8xw8Hpp/NEurdPmQDQ= github.com/DataDog/datadog-go v0.0.0-20180822151419-281ae9f2d895/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= +github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= @@ -24,6 +26,7 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -33,9 +36,29 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dchest/blake2b v1.0.0 h1:KK9LimVmE0MjRl9095XJmKqZ+iLxWATvlcpVFRtaw6s= +github.com/dchest/blake2b v1.0.0/go.mod h1:U034kXgbJpCle2wSk5ybGIVhOSHCVLMDqOzcPEA0F7s= +github.com/dgraph-io/badger v1.6.1-0.20191025180844-32a2548a9d85 h1:oEqDRoxpep5ZlTxrAFc2yg+f0uBdUtkZE0uWsOru5bc= +github.com/dgraph-io/badger v1.6.1-0.20191025180844-32a2548a9d85/go.mod h1:cEjdIw+iaGXuQdsDymXPRcpp8yHXZ6PmwmDJajnVyJc= +github.com/dgraph-io/badger v1.6.1 h1:w9pSFNSdq/JPM1N12Fz/F/bzo993Is1W+Q7HjPzi7yg= +github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200709123515-8e896a7af361 h1:JBNM90aGLCiF9iJYvpvayMpYeW498v5ZDZqE2chqZ2A= +github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200709123515-8e896a7af361/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= +github.com/dgraph-io/badger/v2 v2.0.3 h1:inzdf6VF/NZ+tJ8RwwYMjJMvsOALTHYdozn0qSl6XJI= +github.com/dgraph-io/badger/v2 v2.0.3/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM= +github.com/dgraph-io/ristretto v0.0.0-20191010170704-2ba187ef9534/go.mod h1:edzKIzGvqUCMzhTVWbiTSe75zD9Xxq0GtSBtFmaUTZs= +github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3 h1:MQLRM35Pp0yAyBYksjbj1nZI/w6eyRY/mWoM1sFf4kU= +github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -61,6 +84,8 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= @@ -90,12 +115,16 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/molecula/apophenia v0.0.0-20190827192002-68b7a14a478b h1:cZADDaNYM7xn/nklO3g198JerGQjadFuA0ofxBJgK0Y= @@ -139,6 +168,7 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 h1:HQagqIiBmr8YXawX/le3+O26N+vPPC1PtjaF3mwnook= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= @@ -150,21 +180,30 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38= github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/uber/jaeger-client-go v2.16.0+incompatible h1:Q2Pp6v3QYiocMxomCaJuwQGFt7E53bPYqEgug/AoBtY= @@ -172,7 +211,16 @@ github.com/uber/jaeger-client-go v2.16.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMW github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/willoch/tago v0.0.0-20180311150625-8f2f8e8900dc h1:Jsemerl8qK30jGNdYlxGZpZk9RjB4pqvezJgxqUgy30= +github.com/willoch/tago v0.0.0-20180311150625-8f2f8e8900dc/go.mod h1:9WHA/f8A/TRK+WQQZhqx47In4pnIhMTH6UrsgqqgsVQ= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/zeebo/assert v0.0.0-20181109011804-10f827ce2ed6/go.mod h1:yssERNPivllc1yU3BvpjYI5BUW+zglcz6QWqeVRL5t0= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.0.4-0.20200428182842-252974700486 h1:yh0zEy8it58x/IPNtKKuvKUkxSIaq4s5XiRSd40JuYs= +github.com/zeebo/blake3 v0.0.4-0.20200428182842-252974700486/go.mod h1:YOZo8A49yNqM0X/Y+JmDUZshJWLt1laHsNSn5ny2i34= +github.com/zeebo/blake3 v0.0.4 h1:vtZ4X8B2lKXZFg2Xyg6Wo36mvmnJvc2VQYTtA4RDCkI= +github.com/zeebo/blake3 v0.0.4/go.mod h1:YOZo8A49yNqM0X/Y+JmDUZshJWLt1laHsNSn5ny2i34= +github.com/zeebo/pcg v0.0.0-20181207190024-3cdc6b625a05/go.mod h1:Gr+78ptB0MwXxm//LBaEvBiaXY7hXJ6KGe2V32X2F6E= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -197,6 +245,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU= golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -215,6 +265,10 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -238,6 +292,7 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/holder.go b/holder.go index 30fb7ede0..58dca7f2c 100644 --- a/holder.go +++ b/holder.go @@ -115,6 +115,10 @@ type HolderOpts struct { // If Inspect is set, we'll try to obtain additional information // about fragments when opening them. Inspect bool + + // Txsrc controls the tx/storage engine we instatiate. Set by + // server.go OptServerTxsrc + Txsrc string } func (h *Holder) StartTransaction(ctx context.Context, id string, timeout time.Duration, exclusive bool) (*Transaction, error) { @@ -507,6 +511,10 @@ func (h *Holder) Open() error { if !fi.IsDir() || strings.HasPrefix(fi.Name(), ".") { continue } + // Skip badgerdb files too. + if strings.HasSuffix(fi.Name(), "badgerdb") { + continue + } h.Logger.Printf("opening index: %s", filepath.Base(fi.Name())) @@ -614,8 +622,8 @@ func (h *Holder) Close() error { } // Begin starts a transaction on the holder. -func (h *Holder) Begin(writable bool) (Tx, error) { - return NewMultiTx(writable, h), nil +func (h *Holder) BeginTx(writable bool, index *Index) (Tx, error) { + return index.Txf.NewTx(Txo{Write: writable, Index: index}), nil } // HasData returns true if Holder contains at least one index. @@ -908,6 +916,11 @@ func (h *Holder) DeleteIndex(name string) error { return errors.Wrap(err, "closing") } + // remove any backing store. + if err := index.Txf.DeleteIndex(name); err != nil { + return errors.Wrap(err, "index.Txf.DeleteIndex") + } + // Delete index directory. if err := os.RemoveAll(h.IndexPath(name)); err != nil { return errors.Wrap(err, "removing directory") diff --git a/holder_internal_test.go b/holder_internal_test.go index 28a18fbe8..008770fbc 100644 --- a/holder_internal_test.go +++ b/holder_internal_test.go @@ -79,21 +79,22 @@ func makeHolder() (*Holder, string, error) { } h := NewHolder(DefaultPartitionN) h.Path = path - - return h, h.Path, nil + return h, path, nil } func testSetBit(t *testing.T, h *Holder, index, field string, rowID, columnID uint64) { - tx, err := h.Begin(true) - if err != nil { - t.Fatal(err) - } - defer func() { _ = tx.Rollback() }() idx, err := h.CreateIndexIfNotExists(index, IndexOptions{}) if err != nil { t.Fatalf("creating index: %v", err) } + + tx, err := h.BeginTx(writable, idx) + if err != nil { + t.Fatal(err) + } + defer tx.Rollback() + f, err := idx.CreateFieldIfNotExists(field, OptFieldTypeDefault()) if err != nil { t.Fatalf("setting bit: %v", err) diff --git a/holder_test.go b/holder_test.go index 45df98852..a527307ba 100644 --- a/holder_test.go +++ b/holder_test.go @@ -122,9 +122,14 @@ func TestHolder_Open(t *testing.T) { h := test.MustOpenHolder() defer h.Close() - if idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { + var idx *pilosa.Index + var err error + + if idx, err = h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { t.Fatal(err) - } else if _, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { + } + + if _, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) } else if err := h.Holder.Close(); err != nil { t.Fatal(err) @@ -140,9 +145,13 @@ func TestHolder_Open(t *testing.T) { h := test.MustOpenHolder() defer h.Close() - if idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { + var idx *pilosa.Index + var err error + if idx, err = h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { t.Fatal(err) - } else if _, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { + } + + if _, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) } else if err := h.Holder.Close(); err != nil { t.Fatal(err) @@ -162,15 +171,18 @@ func TestHolder_Open(t *testing.T) { h := test.MustOpenHolder() defer h.Close() - tx, err := h.Begin(true) + var idx *pilosa.Index + var err error + if idx, err = h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { + t.Fatal(err) + } + tx, err := h.BeginTx(writable, idx) if err != nil { t.Fatal(err) } - defer func() { _ = tx.Rollback() }() + defer tx.Rollback() - if idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { - t.Fatal(err) - } else if field, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { + if field, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) } else if _, err := field.SetBit(tx, 0, 0, nil); err != nil { t.Fatal(err) @@ -192,15 +204,19 @@ func TestHolder_Open(t *testing.T) { h := test.MustOpenHolder() defer h.Close() - tx, err := h.Begin(true) + var idx *pilosa.Index + var err error + if idx, err = h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { + t.Fatal(err) + } + + tx, err := h.BeginTx(writable, idx) if err != nil { t.Fatal(err) } - defer func() { _ = tx.Rollback() }() + defer tx.Rollback() - if idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { - t.Fatal(err) - } else if field, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { + if field, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) } else if _, err := field.SetBit(tx, 0, 0, nil); err != nil { t.Fatal(err) @@ -220,15 +236,17 @@ func TestHolder_Open(t *testing.T) { h := test.MustOpenHolder() defer h.Close() - tx, err := h.Begin(true) + idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}) if err != nil { t.Fatal(err) } - defer func() { _ = tx.Rollback() }() - - if idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil { + tx, err := h.BeginTx(writable, idx) + if err != nil { t.Fatal(err) - } else if field, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { + } + defer tx.Rollback() + + if field, err := idx.CreateField("bar", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) } else if _, err := field.SetBit(tx, 0, 0, nil); err != nil { t.Fatal(err) @@ -686,7 +704,10 @@ func TestHolderSyncer_IntField(t *testing.T) { } defer c.Close() - _, err = c[0].API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}) + var idx0 *pilosa.Index + _ = idx0 + idx0, err = c[0].API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}) + _ = idx0 if err != nil { t.Fatalf("creating index i: %v", err) } @@ -698,24 +719,40 @@ func TestHolderSyncer_IntField(t *testing.T) { hldr0 := &test.Holder{Holder: c[0].Server.Holder()} hldr1 := &test.Holder{Holder: c[1].Server.Holder()} - // Set data on the local holder for node0. + // Set data on the local holder for node0. columnID=1, value=1 hldr0.SetValue("i", "f", 1, 1) - // Set data on node1. - hldr1.SetValue("i", "f", 2, 2) + // in c0 expect the 1 bit + //idx0.Dump("in c0, before SyncData") + // Set data on node1. columnID=2, value=2 + idx1 := hldr1.SetValue("i", "f", 2, 2) + _ = idx1 + + //idx1.Dump("in c1, before SyncData") + + //vv("before c[0] SyncData") err = c[0].Server.SyncData() if err != nil { t.Fatalf("syncing node 0: %v", err) } + //vv("after c[0] SyncData") + + // expect 3 rows, the 1 bit + 2 rows for the 2 value as BSI. But, we only see that c0 overwrote c1. + //idx0.Dump("in c0, after syncData") + //idx1.Dump("in c1, after syncData") + + // Problem is: data at c1 was replaced by c0, instead of being merged with existing c1. + // Problem is: data at c0 did not receive and merge the c1 data. // Verify data is the same on both nodes. for i, hldr := range []*test.Holder{hldr0, hldr1} { if a, exists := hldr.Value("i", "f", 1); !exists || a != 1 { - t.Errorf("unexpected value(node%d/0): %d, exists: %v", i, a, exists) + // expects exists==true, a==1 + t.Errorf("unexpected value(node%d/0): a:%d, exists: %v", i, a, exists) // failing TestHolderSyncer_IntField under Badger, unexpected value(node1/0): a:0, exists: true } if a, exists := hldr.Value("i", "f", 2); exists { - t.Errorf("unexpected value(node%d/1): %d, exists: %v", i, a, exists) + t.Errorf("unexpected value(node%d/1): a:%d, exists: %v", i, a, exists) } } }) @@ -732,7 +769,10 @@ func TestHolderSyncer_IntField(t *testing.T) { } defer c.Close() - _, err = c[0].API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}) + var idx0 *pilosa.Index + _ = idx0 + idx0, err = c[0].API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}) + _ = idx0 if err != nil { t.Fatalf("creating index i: %v", err) } @@ -769,6 +809,10 @@ func TestHolderSyncer_IntField(t *testing.T) { t.Fatalf("syncing node 1: %v", err) } + // dump the badger keys for both c0 and c1 + //vv("in c0, allkeys = '%v'", idx0.StringifiedBadgerKeys(nil)) + //vv("in c1, allkeys = '%v'", c[1].index.StringifiedBadgerKeys()) + // Verify data is the same on both nodes. for i, hldr := range []*test.Holder{hldr0, hldr1} { if a := hldr.Range("i", "f", pql.GT, 0); !reflect.DeepEqual(a.Columns(), []uint64{2 * pilosa.ShardWidth, 3 * pilosa.ShardWidth, 4 * pilosa.ShardWidth}) { diff --git a/index.go b/index.go index 41dfeb645..b1e2a03b2 100644 --- a/index.go +++ b/index.go @@ -67,16 +67,32 @@ type Index struct { // Instantiates new translation stores OpenTranslateStore OpenTranslateStoreFunc + + // txf chooses the transaction and storage strategy + Txf *TxFactory } // NewIndex returns a new instance of Index. func NewIndex(holder *Holder, path, name string) (*Index, error) { - err := validateName(name) + + // Emulate what the spf13/cobra does, letting env vars override + // the defaults, because we may be under a simple "go test" run where + // not all that command line machinery has been spun up. + txsrc := os.Getenv("PILOSA_TXSRC") + if txsrc == "" { + txsrc = DefaultTxsrc + } + txf, err := newTxFactory(txsrc, path) + if err != nil { + return nil, errors.Wrap(err, "creating newTxFactory") + } + + err = validateName(name) if err != nil { return nil, errors.Wrap(err, "validating name") } - return &Index{ + idx := &Index{ path: path, name: name, fields: make(map[string]*Field), @@ -94,7 +110,11 @@ func NewIndex(holder *Holder, path, name string) (*Index, error) { translationSyncer: NopTranslationSyncer, OpenTranslateStore: OpenInMemTranslateStore, - }, nil + + Txf: txf, + } + idx.Txf.idx = idx + return idx, nil } // CreatedAt is an timestamp for a specific version of an index. @@ -326,6 +346,11 @@ func (i *Index) Close() error { i.mu.Lock() defer i.mu.Unlock() + err := i.Txf.CloseIndex(i) + if err != nil { + return errors.Wrap(err, "closing index") + } + // Close the attribute store. i.columnAttrs.Close() @@ -367,9 +392,8 @@ func (i *Index) AvailableShards() *roaring.Bitmap { } // Begin starts a transaction on a shard of the index. -func (i *Index) Begin(writable bool, shard uint64) (Tx, error) { - // TODO(bbj): Check for underlying storage as RBF or roaring. - return &RoaringTx{Index: i}, nil +func (i *Index) BeginTx(writable bool, shard uint64) (Tx, error) { + return i.Txf.NewTx(Txo{Write: writable, Index: i, Shard: shard}), nil } // fieldPath returns the path to a field in the index. diff --git a/license.exceptions b/license.exceptions index ef44dcacc..e703b7c2a 100644 --- a/license.exceptions +++ b/license.exceptions @@ -9,3 +9,4 @@ ./proto/pilosa.pb.go ./logger/filewriter.go ./logger/filewriter_test.go +./vprint.go diff --git a/mmap_test.go b/mmap_test.go index a41830fc5..20fbd7685 100644 --- a/mmap_test.go +++ b/mmap_test.go @@ -31,7 +31,8 @@ type cv struct { func forceSnapshotsCheckMapping(t *testing.T) { depth := uint(6) - f := mustOpenBSIFragment("i", "f", viewStandard, 0) + f, idx := mustOpenBSIFragment("i", "f", viewStandard, 0) + _ = idx f.Logger = logger.NewLogfLogger(t) defer f.Clean(t) diff --git a/pql/ast.go b/pql/ast.go index 9d65bb3c5..8ebfaf122 100644 --- a/pql/ast.go +++ b/pql/ast.go @@ -244,7 +244,7 @@ func (q *Query) WriteCallN() int { var n int for _, call := range q.Calls { switch call.Name { - case "Set", "Clear", "SetRowAttrs", "SetColumnAttrs": + case "Set", "Clear", "SetRowAttrs", "SetColumnAttrs", "ClearRow", "Store", "SetBit": n++ } } diff --git a/pql/decimal.go b/pql/decimal.go index c6a7f834c..46ef82eb7 100644 --- a/pql/decimal.go +++ b/pql/decimal.go @@ -68,6 +68,14 @@ type Decimal struct { Scale int64 } +func (d Decimal) Clone() (r *Decimal) { + r = &Decimal{ + Value: d.Value, + Scale: d.Scale, + } + return +} + // NewDecimal returns a Decimal based on the provided arguments. func NewDecimal(value, scale int64) Decimal { return Decimal{ diff --git a/roaring/containers_btree.go b/roaring/containers_btree.go index 257a756fa..72837dcb4 100644 --- a/roaring/containers_btree.go +++ b/roaring/containers_btree.go @@ -210,6 +210,8 @@ type btcIterator struct { val *Container } +func (i *btcIterator) Close() {} + func (i *btcIterator) Next() bool { k, v, err := i.e.Next() if err == io.EOF { diff --git a/roaring/containers_slice.go b/roaring/containers_slice.go index 1eaa65cf0..79798adb0 100644 --- a/roaring/containers_slice.go +++ b/roaring/containers_slice.go @@ -91,11 +91,16 @@ func (sc *sliceContainers) GetOrCreate(key uint64) *Container { } func (sc *sliceContainers) Clone() Containers { + other := newSliceContainers() other.keys = make([]uint64, len(sc.keys)) other.containers = make([]*Container, len(sc.containers)) copy(other.keys, sc.keys) for i, c := range sc.containers { + if c == nil { + other.containers[i] = nil + continue + } other.containers[i] = c.Clone() } return other @@ -234,6 +239,8 @@ type sliceIterator struct { value *Container // current value } +func (si *sliceIterator) Close() {} + func (si *sliceIterator) Next() bool { if si.e == nil { return false diff --git a/roaring/roaring.go b/roaring/roaring.go index 496bdffa6..7f755f995 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -174,6 +174,7 @@ type Containers interface { type ContainerIterator interface { Next() bool Value() (uint64, *Container) + Close() } // Bitmap represents a roaring bitmap. @@ -235,6 +236,7 @@ var NewFileBitmap = NewBTreeBitmap // Clone returns a heap allocated copy of the bitmap. // Note: The OpWriter IS NOT copied to the new bitmap. func (b *Bitmap) Clone() *Bitmap { + if b == nil { return nil } @@ -749,6 +751,8 @@ func (it *mutableContainersIterator) Value() (uint64, *Container) { return it.cit.Value() } +func (it *mutableContainersIterator) Close() {} + // IntersectInPlace returns the bitwise intersection of b and others, // modifying b in place. func (b *Bitmap) IntersectInPlace(others ...*Bitmap) { @@ -1716,10 +1720,10 @@ func (b *Bitmap) writeToUnoptimized(w io.Writer) (n int64, err error) { return n, nil } -// roaringIterator represents something which can iterate through a roaring +// RoaringIterator represents something which can iterate through a roaring // bitmap and yield information about containers, including type, size, and // the location of their data structures. -type roaringIterator interface { +type RoaringIterator interface { // Len reports the number of containers total. Len() (count int64) // Next yields the information about the next container @@ -1728,6 +1732,19 @@ type roaringIterator interface { // which is typically an ops log in our case, and also its offset in case // we need to talk about truncation. Remaining() ([]byte, int64) + + // NextContainer is a helper that is used in place of Next(). It will + // allocate a Container from the output of its internal call to Next(), + // and return the key and container rc. If Next returns an error, then + // NextContainer will return 0, nil. + NextContainer() (key uint64, rc *Container) + + // Data returns the underlying data, esp for the Ops log. + Data() []byte + + // Clone copies the iterator, preserving it at this point in the iteration. + // It may well share much underlying data. + Clone() RoaringIterator } // baseRoaringIterator holds values used by both Pilosa and official Roaring @@ -1763,6 +1780,16 @@ func (b *baseRoaringIterator) SilenceLint() { _ = b.prevOffset32 } +func (b *pilosaRoaringIterator) Clone() (clone RoaringIterator) { + cp := *b + return &cp +} + +func (b *officialRoaringIterator) Clone() (clone RoaringIterator) { + cp := *b + return &cp +} + type pilosaRoaringIterator struct { baseRoaringIterator } @@ -1859,7 +1886,7 @@ func newPilosaRoaringIterator(data []byte) (*pilosaRoaringIterator, error) { return r, nil } -func newRoaringIterator(data []byte) (roaringIterator, error) { +func NewRoaringIterator(data []byte) (RoaringIterator, error) { if len(data) < headerBaseSize { return nil, errors.New("invalid data: not long enough to be a roaring header") } @@ -1891,6 +1918,10 @@ func (r *baseRoaringIterator) Len() int64 { return r.keys } +func (r *baseRoaringIterator) Data() []byte { + return r.data +} + func (r *baseRoaringIterator) Remaining() ([]byte, int64) { if r.lastDataOffset == 0 { return nil, 0 @@ -1898,6 +1929,20 @@ func (r *baseRoaringIterator) Remaining() ([]byte, int64) { return r.data[r.lastDataOffset:], r.lastDataOffset } +func (r *pilosaRoaringIterator) NextContainer() (key uint64, rc *Container) { + itrKey, itrCType, itrN, itrLen, itrPointer, itrErr := r.Next() + if itrErr != nil { + return 0, nil + } + rc = &Container{} + rc.typeID = itrCType + rc.n = int32(itrN) + rc.len = int32(itrLen) + rc.cap = int32(itrLen) + rc.pointer = itrPointer + return itrKey, rc +} + func (r *pilosaRoaringIterator) Next() (key uint64, cType byte, n int, length int, pointer *uint16, err error) { if r.currentIdx >= r.keys { // we're already done @@ -1954,6 +1999,20 @@ func (r *pilosaRoaringIterator) Next() (key uint64, cType byte, n int, length in return r.Current() } +func (r *officialRoaringIterator) NextContainer() (key uint64, rc *Container) { + itrKey, itrCType, itrN, itrLen, itrPointer, itrErr := r.Next() + if itrErr != nil { + return 0, nil + } + rc = &Container{} + rc.typeID = itrCType + rc.n = int32(itrN) + rc.len = int32(itrLen) + rc.cap = int32(itrLen) + rc.pointer = itrPointer + return itrKey, rc +} + func (r *officialRoaringIterator) Next() (key uint64, cType byte, n int, length int, pointer *uint16, err error) { if r.currentIdx >= r.keys { // we're already done @@ -2066,7 +2125,7 @@ func (b *Bitmap) RemapRoaringStorage(data []byte) (mappedAny bool, returnErr err if b.Containers == nil { return false, nil } - var itr roaringIterator + var itr RoaringIterator var err error var itrKey uint64 var itrCType byte @@ -2079,7 +2138,7 @@ func (b *Bitmap) RemapRoaringStorage(data []byte) (mappedAny bool, returnErr err // map to the data. We still need to do the UpdateEvery loop, we // just won't have an iterator for it. if data != nil && b.preferMapping { - itr, err = newRoaringIterator(data) + itr, err = NewRoaringIterator(data) } // don't return early: we still have to do the unmapping if err != nil { @@ -2144,7 +2203,16 @@ func (b *Bitmap) ImportRoaringBits(data []byte, clear bool, log bool, rowSize ui if data == nil { return 0, nil, errors.New("no roaring bitmap provided") } - var itr roaringIterator + var itr RoaringIterator + + itr, err = NewRoaringIterator(data) + if err != nil { + return 0, nil, err + } + return b.ImportRoaringRawIterator(itr, clear, log, rowSize) +} + +func (b *Bitmap) ImportRoaringRawIterator(itr RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) { var itrKey uint64 var itrCType byte var itrN int @@ -2152,10 +2220,6 @@ func (b *Bitmap) ImportRoaringBits(data []byte, clear bool, log bool, rowSize ui var itrPointer *uint16 var itrErr error - itr, err = newRoaringIterator(data) - if err != nil { - return 0, nil, err - } if itr == nil { return 0, nil, errors.New("failed to create roaring iterator, but don't know why") } @@ -2225,7 +2289,7 @@ func (b *Bitmap) ImportRoaringBits(data []byte, clear bool, log bool, rowSize ui } err = nil if log && changed > 0 { - op := op{opN: changed, roaring: data} + op := op{opN: changed, roaring: itr.Data()} if clear { op.typ = opTypeRemoveRoaring } else { @@ -2257,13 +2321,13 @@ func (b *Bitmap) writeOp(op *op) error { // Iterator returns a new iterator for the bitmap. func (b *Bitmap) Iterator() *Iterator { - itr := &Iterator{bitmap: b} + itr := NewIterator(&BitmapIteratorFinder{b}) itr.Seek(0) return itr } func (b *Bitmap) IteratorAt(start uint64) *Iterator { - itr := &Iterator{bitmap: b} + itr := NewIterator(&BitmapIteratorFinder{b}) itr.Seek(start) return itr } @@ -2287,7 +2351,7 @@ func RoaringToBitmaps(data []byte, shardWidth uint64) ([]*Bitmap, []uint64) { if data == nil { return nil, nil } - var itr roaringIterator + var itr RoaringIterator var itrKey uint64 var itrCType byte var itrN int @@ -2300,7 +2364,7 @@ func RoaringToBitmaps(data []byte, shardWidth uint64) ([]*Bitmap, []uint64) { var shards []uint64 keysPerShard := shardWidth >> 16 - itr, err := newRoaringIterator(data) + itr, err := NewRoaringIterator(data) if err != nil || itr == nil { return nil, nil } @@ -2524,15 +2588,38 @@ type BitmapInfo struct { From, To uintptr // if set, indicates the address range used when unpacking } +type IteratorFinder interface { + FindIterator(uint64) (ContainerIterator, bool) + Close() +} +type BitmapIteratorFinder struct { + bitmap *Bitmap +} + +func (bif *BitmapIteratorFinder) FindIterator(seek uint64) (ContainerIterator, bool) { + return bif.bitmap.Containers.Iterator(seek) +} +func (bif *BitmapIteratorFinder) Close() {} + // Iterator represents an iterator over a Bitmap. type Iterator struct { - bitmap *Bitmap + finder IteratorFinder citer ContainerIterator key uint64 c *Container j, k int32 // i: container; j: array index, bit index, or run index; k: offset within the run } +// NewIterator requires f as an IteratorFinder, it will +// crash if f is nil. +func NewIterator(f IteratorFinder) *Iterator { + return &Iterator{finder: f} +} + +func (itr *Iterator) Close() { + itr.finder.Close() +} + // Seek moves to the first value equal to or greater than `seek`. func (itr *Iterator) Seek(seek uint64) { // k should always be -1 unless we're seeking into a run container. Then the @@ -2540,7 +2627,7 @@ func (itr *Iterator) Seek(seek uint64) { itr.k = -1 // Move to the correct container. - itr.citer, _ = itr.bitmap.Containers.Iterator(highbits(seek)) + itr.citer, _ = itr.finder.FindIterator(highbits(seek)) if !itr.citer.Next() { itr.c = nil return // eof @@ -6114,10 +6201,10 @@ func (b *Bitmap) BitwiseEqual(c *Bitmap) (bool, error) { cn = biter.Next() } if bn { - return false, fmt.Errorf("container mismatch: %d vs %d containers, first bitmap has extra container %d [%d bits]", bct, cct, bk, bc) + return false, fmt.Errorf("container mismatch: %d vs %d containers, first bitmap has extra container %d [%v bits]", bct, cct, bk, bc) } if cn { - return false, fmt.Errorf("container mismatch: %d vs %d containers, second bitmap has extra container %d [%d bits]", bct, cct, ck, cc) + return false, fmt.Errorf("container mismatch: %d vs %d containers, second bitmap has extra container %d [%v bits]", bct, cct, ck, cc) } return true, nil } @@ -6783,6 +6870,35 @@ func Optimize(c *Container) { func Union(a, b *Container) *Container { return union(a, b) } + func Difference(a, b *Container) *Container { return difference(a, b) } + +func (c *Container) Add(v uint16) (newC *Container, added bool) { + return c.add(v) +} + +func (c *Container) Remove(v uint16) (c2 *Container, removed bool) { + return c.remove(v) +} + +func (c *Container) Max() uint16 { + return c.max() +} + +func (c *Container) CountRange(start, end int32) (n int32) { + return c.countRange(start, end) +} + +func (c *Container) UnionInPlace(other *Container) *Container { + return c.unionInPlace(other) +} + +func (c *Container) Difference(other *Container) *Container { + return difference(c, other) +} + +func NewSliceContainers() *sliceContainers { + return newSliceContainers() +} diff --git a/roaring/unmarshal_binary.go b/roaring/unmarshal_binary.go index 53aff37ee..39a0762f9 100644 --- a/roaring/unmarshal_binary.go +++ b/roaring/unmarshal_binary.go @@ -27,7 +27,7 @@ func (b *Bitmap) UnmarshalBinary(data []byte) (err error) { if data == nil { return errors.New("no roaring bitmap provided") } - var itr roaringIterator + var itr RoaringIterator var itrKey uint64 var itrCType byte var itrN int @@ -35,7 +35,7 @@ func (b *Bitmap) UnmarshalBinary(data []byte) (err error) { var itrPointer *uint16 var itrErr error - itr, err = newRoaringIterator(data) + itr, err = NewRoaringIterator(data) if err != nil { return err } @@ -110,7 +110,7 @@ func InspectBinary(data []byte, mapped bool, info *BitmapInfo) (b *Bitmap, mappe if data == nil { return b, mappedAny, errors.New("no roaring bitmap provided") } - var itr roaringIterator + var itr RoaringIterator var itrKey uint64 var itrCType byte var itrN int @@ -118,7 +118,7 @@ func InspectBinary(data []byte, mapped bool, info *BitmapInfo) (b *Bitmap, mappe var itrPointer *uint16 var itrErr error - itr, err = newRoaringIterator(data) + itr, err = NewRoaringIterator(data) if err != nil { return b, mappedAny, err } diff --git a/row.go b/row.go index d76dcc73c..30c11d2fe 100644 --- a/row.go +++ b/row.go @@ -45,6 +45,40 @@ func NewRow(columns ...uint64) *Row { return r } +func (r *Row) Clone() (clone *Row) { + if r == nil { + return nil + } + var keyClone []string + if len(r.Keys) > 0 { + keyClone = make([]string, len(r.Keys)) + copy(keyClone, r.Keys) + } + + attrClone := make(map[string]interface{}) + for k, v := range r.Attrs { + attrClone[k] = v + } + clone = &Row{ + Keys: keyClone, + Attrs: attrClone, + } + + for _, seg := range r.segments { + segClone := rowSegment{ + shard: seg.shard, + writable: true, // we know it is safe; it is a copy. + n: seg.n, + } + if seg.data != nil { + segClone.data = seg.data.Clone() // *roaring.Bitmap + } + //segClone.InvalidateCount() // not needed? + clone.segments = append(clone.segments, segClone) + } + return clone +} + // NewRowFromBitmap divides a bitmap into rows, which it now calls shards. This // transposes; data that was in any shard for Row 0 is now considered shard 0, // etcetera. @@ -520,7 +554,7 @@ func (r *Row) MarshalJSON() ([]byte, error) { func (r *Row) Columns() []uint64 { a := make([]uint64, 0, r.Count()) for i := range r.segments { - a = append(a, r.segments[i].Columns()...) + a = append(a, r.segments[i].Columns()...) // Accessing Tx memory that is now invalid. } return a } diff --git a/server.go b/server.go index 0afd2fa43..f0974c0d5 100644 --- a/server.go +++ b/server.go @@ -326,6 +326,17 @@ func OptServerOpenTranslateReader(fn OpenTranslateReaderFunc) ServerOption { } } +// OptServerTxsrc is a functional option on Server +// used to specify the transactional-storage to use, +// resulting in RoaringTx, RbfTx, BadgerTx, or a blueGreen* Tx +// being used for all Tx interface calls. +func OptServerTxsrc(txsrc string) ServerOption { + return func(s *Server) error { + s.holder.Opts.Txsrc = txsrc + return nil + } +} + // NewServer returns a new instance of Server. func NewServer(opts ...ServerOption) (*Server, error) { cluster := newCluster() diff --git a/server/config.go b/server/config.go index 6d0181756..ca5817fd0 100644 --- a/server/config.go +++ b/server/config.go @@ -24,6 +24,7 @@ import ( "strings" "time" + "github.com/pilosa/pilosa/v2" "github.com/pilosa/pilosa/v2/gossip" "github.com/pilosa/pilosa/v2/toml" "github.com/pkg/errors" @@ -166,6 +167,19 @@ type Config struct { // MutexFraction is passed directly to runtime.SetMutexProfileFraction MutexFraction int `toml:"mutex-fraction"` } `toml:"profile"` + + // Txsrc determines which Tx implementation the holder/Index will use; one + // of the available transactional-storage engines. Choices are listed + // in the string constants below. Should be one of + // "roaring","badger", "rbf", "badger_roaring", "roaring_badger", "rbf_roaring", + // "roaring_rbf", "badger_rbf", "rbf_badger", or any later addition. The + // engines with _ underscore indicate use of a blueGreenTx with a comparison + // of values back from each Tx method, and a panic if they differ. This + // is an effective test for consistency. If "rbf_roaring" is specified, then + // the roaring values are the ones actually returned from the blueGreenTx. + // If "roaring_rbf" is chosen, then the RBF values are the ones actually + // returned from the blueGreenTx. + Txsrc string `toml:"txsrc"` } // NewConfig returns an instance of Config with default options. @@ -222,6 +236,8 @@ func NewConfig() *Config { c.Profile.BlockRate = 10000000 // 1 sample per 10 ms c.Profile.MutexFraction = 100 // 1% sampling + c.Txsrc = pilosa.DefaultTxsrc + return c } diff --git a/server/handler_test.go b/server/handler_test.go index c318056a1..d4648c3c3 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -173,29 +173,38 @@ func TestHandler_Endpoints(t *testing.T) { } }) - tx, err := holder.Begin(true) + i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) + tx0, err := holder.BeginTx(true, i0.Index) if err != nil { t.Fatal(err) } - defer func() { _ = tx.Rollback() }() + defer tx0.Rollback() - i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) + tx1, err := holder.BeginTx(true, i1.Index) + if err != nil { + t.Fatal(err) + } + defer tx1.Rollback() + if f, err := i0.CreateFieldIfNotExists("f1", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) - } else if _, err := f.SetBit(tx, 0, 0, nil); err != nil { + } else if _, err := f.SetBit(tx0, 0, 0, nil); err != nil { t.Fatal(err) } if f, err := i1.CreateFieldIfNotExists("f0", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) - } else if _, err := f.SetBit(tx, 0, 0, nil); err != nil { + } else if _, err := f.SetBit(tx1, 0, 0, nil); err != nil { t.Fatal(err) } if _, err := i0.CreateFieldIfNotExists("f0", pilosa.OptFieldTypeDefault()); err != nil { t.Fatal(err) } - if err := tx.Commit(); err != nil { + if err := tx0.Commit(); err != nil { + t.Fatal(err) + } + if err := tx1.Commit(); err != nil { t.Fatal(err) } @@ -638,7 +647,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Query empty", func(t *testing.T) { w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(""))) - if body := w.Body.String(); body != `{"results":[]}`+"\n" { + if body := w.Body.String(); body != `{"results":[]}`+"\n" && body != `{"results":null}`+"\n" { t.Fatalf("unexpected body: %q", body) } }) diff --git a/test/holder.go b/test/holder.go index b48016f9f..8db4af11c 100644 --- a/test/holder.go +++ b/test/holder.go @@ -25,6 +25,8 @@ import ( "github.com/pilosa/pilosa/v2/pql" ) +var panicOn = pilosa.PanicOn + // Holder is a test wrapper for pilosa.Holder. type Holder struct { *pilosa.Holder @@ -86,29 +88,40 @@ func (h *Holder) Row(index, field string, rowID uint64) *pilosa.Row { if err != nil { panic(err) } - tx := &pilosa.RoaringTx{Index: idx.Index} + tx := idx.Index.Txf.NewTx(pilosa.Txo{Write: false, Index: idx.Index}) + defer tx.Rollback() row, err := f.Row(tx, rowID) if err != nil { panic(err) } - return row + // clone it so that mmapped storage doesn't disappear from under it + // once the tx goes away. + return row.Clone() } // ReadRow returns a Row for a given field. If the field does not exist, // it panics rather than creating the field. func (h *Holder) ReadRow(index, field string, rowID uint64) *pilosa.Row { - f := h.Holder.Field(index, field) + idx := h.Holder.Index(index) + if idx == nil { + panic(pilosa.ErrIndexNotFound) + } + f := idx.Field(field) if f == nil { panic(pilosa.ErrFieldNotFound) } - tx := &pilosa.RoaringTx{Field: f} + tx := idx.Txf.NewTx(pilosa.Txo{Write: false, Field: f}) + defer tx.Rollback() row, err := f.Row(tx, rowID) if err != nil { panic(err) } - return row + + // clone it so that mmapped storage doesn't disappear from under it + // once the tx goes away. + return row.Clone() } func (h *Holder) RowAttrStore(index, field string) pilosa.AttrStore { @@ -126,13 +139,17 @@ func (h *Holder) RowTime(index, field string, rowID uint64, t time.Time, quantum if err != nil { panic(err) } - tx := &pilosa.RoaringTx{Index: idx.Index} + tx := idx.Index.Txf.NewTx(pilosa.Txo{Write: false, Index: idx.Index}) + defer tx.Rollback() row, err := f.RowTime(tx, rowID, t, quantum) if err != nil { panic(err) } - return row + + // clone it so that mmapped storage doesn't disappear from under it + // once the tx goes away. + return row.Clone() } // SetBit sets a bit on the given field. @@ -147,12 +164,15 @@ func (h *Holder) SetBitTime(index, field string, rowID, columnID uint64, t *time if err != nil { panic(err) } - tx := &pilosa.RoaringTx{Index: idx.Index} + + tx := idx.Index.Txf.NewTx(pilosa.Txo{Write: true, Index: idx.Index}) + defer tx.Rollback() _, err = f.SetBit(tx, rowID, columnID, t) if err != nil { panic(err) } + panicOn(tx.Commit()) } // ClearBit clears a bit on the given field. @@ -162,12 +182,14 @@ func (h *Holder) ClearBit(index, field string, rowID, columnID uint64) { if err != nil { panic(err) } - tx := &pilosa.RoaringTx{Index: idx.Index} + tx := idx.Index.Txf.NewTx(pilosa.Txo{Write: true, Index: idx.Index}) + defer tx.Rollback() _, err = f.ClearBit(tx, rowID, columnID) if err != nil { panic(err) } + panicOn(tx.Commit()) } // MustSetBits sets columns on a row. Panic on error. @@ -179,18 +201,23 @@ func (h *Holder) MustSetBits(index, field string, rowID uint64, columnIDs ...uin } // SetValue sets an integer value on the given field. -func (h *Holder) SetValue(index, field string, columnID uint64, value int64) { +func (h *Holder) SetValue(index, field string, columnID uint64, value int64) *Index { idx := h.MustCreateIndexIfNotExists(index, pilosa.IndexOptions{}) f, err := idx.CreateFieldIfNotExists(field, pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64)) if err != nil { panic(err) } - tx := &pilosa.RoaringTx{Index: idx.Index} + tx := idx.Index.Txf.NewTx(pilosa.Txo{Write: true, Index: idx.Index}) + defer tx.Rollback() _, err = f.SetValue(tx, columnID, value) if err != nil { panic(err) } + if err := tx.Commit(); err != nil { + panic(err) + } + return idx } // Value returns the integer value for a given column. @@ -200,7 +227,8 @@ func (h *Holder) Value(index, field string, columnID uint64) (int64, bool) { if err != nil { panic(err) } - tx := &pilosa.RoaringTx{Index: idx.Index} + tx := idx.Index.Txf.NewTx(pilosa.Txo{Write: false, Index: idx.Index}) + defer tx.Rollback() val, exists, err := f.Value(tx, columnID) if err != nil { @@ -217,11 +245,15 @@ func (h *Holder) Range(index, field string, op pql.Token, predicate int64) *pilo if err != nil { panic(err) } - tx := &pilosa.RoaringTx{Index: idx.Index} + tx := idx.Index.Txf.NewTx(pilosa.Txo{Write: false, Index: idx.Index}) + defer tx.Rollback() row, err := f.Range(tx, field, op, predicate) if err != nil { panic(err) } - return row + + // clone it so that mmapped storage doesn't disappear from under it + // once the tx goes away. + return row.Clone() } diff --git a/tx.go b/tx.go index b8b74d427..9fcf6fd99 100644 --- a/tx.go +++ b/tx.go @@ -22,30 +22,160 @@ import ( "github.com/pkg/errors" ) +// batch operations want Tx.Add(batched=doBatch), while bit-at-a-time want Tx.Add(batched=!doBatched) +// Used in Tx.Add() to get consistency between RoaringTx and other Tx implementations on +// the changeCount returned. +const doBatched = false // must be false, do not change this without adjusting the Add() implementations correspondingly. + +// writable initializes Tx that update, use !writable for read-only. +const writable = true + +// Tx providers offer transactional storage for high-level roaring.Bitmaps and +// low-level roaring.Containers. +// +// The common 4-tuple of (index, field, view, shard) jointly specify a fragment. +// A fragment conceptually holds one roaring.Bitmap. +// +// Within the fragment, the ckey or container-key is the uint64 that specifies +// the high 48-bits of the roaring.Bitmap 64-bit space. +// The ckey is used to retreive a specific roaring.Container that +// is either a run, array, or raw-bitmap. The roaring.Container is the +// low 16-bits of the roaring.Bitmap space. Its size is at most +// 8KB (2^16 bits / (8 bits / byte) == 8192 bytes). +// +// The grain of the transaction is guaranteed to be at least at the shard +// within one index. Therefore updates to the any of the fields within +// the same shard will be atomically visible only once the transaction commits. +// Reads from another, concurrently open, transaction will not see updates +// that have not been committed. type Tx interface { - Rollback() error + + // Rollback must be called the end of read-only transactions. Either + // Rollback or Commit must be called at the end of writable transactions. + // It is safe to call Rollback multiple times, but it must be + // called at least once to release resources. Any Rollback after + // a Commit is ignored, so 'defer tx.Rollback()' should be commonly + // written after starting a new transaction. + // + // If there is an error during internal Rollback processing, + // this would be quite serious, and the underlying storage is + // expected to panic. Hence there is no explicit error returned + // from Rollback that needs to be checked. + Rollback() + + // Commit makes the updates in the Tx visible to subsequent transactions. Commit() error + // Readonly returns the flag this transaction was created with + // during NewTx. If the transaction is writable, it will return false. + Readonly() bool + + // UseRowCache is used by fragment.go unprotectedRow() to determine + // dynamically at runtime if RoaringTx + // are in use, which for continuity want to continue to use the + // rowCache, or if other storage engines (RBF, Badger) are in + // use, which will mean that the bitmap data stored by the + // rowCache can disappear as it is un-mmap-ed, causing crashes. + UseRowCache() bool + + // IncrementOpN updates internal statistics with the changedN provided. + IncrementOpN(index, field, view string, shard uint64, changedN int) + + // Pointer gives us a memory address for the underlying + // transaction for debugging. + // It is public because we use it in roaring to report invalid + // container memory access outside of a transaction. + Pointer() string + + // NewTxIterator returns it, a *roaring.Iterator whose it.Next() will + // successively return each uint64 stored in the conceptual roaring.Bitmap + // for the specified fragment. + NewTxIterator(index, field, view string, shard uint64) (it *roaring.Iterator) + + // ContainerIterator loops over the containers in the conceptual + // roaring.Bitmap for the specified fragment. + // Calling Next() on the returned roaring.ContainerIterator gives + // you a roaring.Container that is either run, array, or raw bitmap. + // Return value 'found' is true when the ckey container was present. + ContainerIterator(index, field, view string, shard uint64, ckey uint64) (citer roaring.ContainerIterator, found bool, err error) + + // RoaringBitmap retreives the roaring.Bitmap for the entire shard. RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) - Container(index, field, view string, shard uint64, key uint64) (*roaring.Container, error) - PutContainer(index, field, view string, shard uint64, key uint64, c *roaring.Container) error - RemoveContainer(index, field, view string, shard uint64, key uint64) error + // Container returns the roaring.Container for the given ckey + // (container-key or highbits), in the chosen fragment. + Container(index, field, view string, shard uint64, ckey uint64) (*roaring.Container, error) - Add(index, field, view string, shard uint64, a ...uint64) (changed bool, err error) - Remove(index, field, view string, shard uint64, a ...uint64) (changed bool, err error) + // PutContainer stores c under the given ckey (container-key), in the specified fragment. + PutContainer(index, field, view string, shard uint64, ckey uint64, c *roaring.Container) error + + // RemoveContainer deletes the roaring.Container under the given ckey (container-key), + // in the specified fragment. + RemoveContainer(index, field, view string, shard uint64, ckey uint64) error + + // Add adds the 'a' bits to the specified fragment. + // + // Using batched=true allows efficient bulk-import. + // + // Notes on the RoaringTx implementation: + // If the batched flag is true, then the roaring.Bitmap.AddN() is used, which does oplog batches. + // If the batched flag is false, then the roaring.Bitmap.Add() is used, which does simple opTypeAdd single adds. + // + // Beware: if batched is true, then changeCount will only ever be 0 or 1, + // because it calls roaring.Add(). + // If batched is false, we call roaring.DirectAddN() and then changeCount + // will be accurate if the changeCount is greater than 0. + // + // Hence: only ever call Add(batched=false) if changeCount is expected to be 0 or 1. + // Or, must use Add(batched=true) if changeCount can be > 1. + // + Add(index, field, view string, shard uint64, batched bool, a ...uint64) (changeCount int, err error) + + // Remove removes the 'a' values from the Bitmap for the fragment. + Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) + + // Contains tests if the uint64 v is stored in the fragment's Bitmap. Contains(index, field, view string, shard uint64, v uint64) (exists bool, err error) - ContainerIterator(index, field, view string, shard uint64, key uint64) (citer roaring.ContainerIterator, found bool, err error) + // ForEach ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error + + // ForEachRange ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error + // Count Count(index, field, view string, shard uint64) (uint64, error) + + // Max Max(index, field, view string, shard uint64) (uint64, error) + + // Min Min(index, field, view string, shard uint64) (uint64, bool, error) + + // UnionInPlace UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error + + // CountRange CountRange(index, field, view string, shard uint64, start, end uint64) (uint64, error) + + // OffsetRange OffsetRange(index, field, view string, shard uint64, offset, start, end uint64) (*roaring.Bitmap, error) + + // ImportRoaringBits does efficient bulk import using rit, a roaring.RoaringIterator. + // See the roaring package for details of the RoaringIterator. + // If clear is true, the bits from rit are cleared, otherwise they are set in the + // specifed fragment. + ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) +} + +// RawRoaringData used by ImportRoaringBits. +// must be consumable by roaring.newRoaringIterator() +type RawRoaringData struct { + data []byte +} + +func (rr *RawRoaringData) Iterator() (roaring.RoaringIterator, error) { + return roaring.NewRoaringIterator(rr.data) } // MultiTx implements the transaction interface to combine multiple transactions. @@ -77,14 +207,40 @@ func NewMultiTxWithIndex(writable bool, index *Index) *MultiTx { var _ Tx = (*MultiTx)(nil) +func (mtx *MultiTx) UseRowCache() bool { + return true +} + +func (mtx *MultiTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator { + tx, err := mtx.tx(index, shard) + panicOn(err) + return tx.NewTxIterator(index, field, view, shard) +} + +// Readonly is true if the transaction is not read-and-write, but only doing reads. +func (mtx *MultiTx) Readonly() bool { + return !mtx.writable +} + +func (mtx *MultiTx) Pointer() string { + return fmt.Sprintf("%p", mtx) +} + +func (tx *MultiTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) { + panic("not done") +} + +func (mtx *MultiTx) IncrementOpN(index, field, view string, shard uint64, changedN int) { + tx, err := mtx.tx(index, shard) + panicOn(err) + tx.IncrementOpN(index, field, view, shard, changedN) +} + // Rollback rolls back all underlying transactions. -func (mtx *MultiTx) Rollback() (err error) { +func (mtx *MultiTx) Rollback() { for _, tx := range mtx.txs { - if e := tx.Rollback(); e != nil && err == nil { - err = e - } + tx.Rollback() } - return err } // Commit commits all underlying transactions. @@ -129,18 +285,18 @@ func (mtx *MultiTx) RemoveContainer(index, field, view string, shard uint64, key return tx.RemoveContainer(index, field, view, shard, key) } -func (mtx *MultiTx) Add(index, field, view string, shard uint64, a ...uint64) (changed bool, err error) { +func (mtx *MultiTx) Add(index, field, view string, shard uint64, batched bool, a ...uint64) (changeCount int, err error) { tx, err := mtx.tx(index, shard) if err != nil { - return false, err + return 0, err } - return tx.Add(index, field, view, shard, a...) + return tx.Add(index, field, view, shard, batched, a...) } -func (mtx *MultiTx) Remove(index, field, view string, shard uint64, a ...uint64) (changed bool, err error) { +func (mtx *MultiTx) Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) { tx, err := mtx.tx(index, shard) if err != nil { - return false, err + return 0, err } return tx.Remove(index, field, view, shard, a...) } @@ -231,8 +387,10 @@ func (mtx *MultiTx) tx(index string, shard uint64) (_ Tx, err error) { mtx.mu.Lock() defer mtx.mu.Unlock() + mkey := multiTxKey{index: index, shard: shard, write: mtx.writable} + // Lookup transaction from cache. - tx := mtx.txs[multiTxKey{index, shard}] + tx := mtx.txs[mkey] if tx != nil { return tx, nil } @@ -246,10 +404,10 @@ func (mtx *MultiTx) tx(index string, shard uint64) (_ Tx, err error) { } // Begin tranaction & cache it. - if tx, err = idx.Begin(mtx.writable, shard); err != nil { + if tx, err = idx.BeginTx(mtx.writable, shard); err != nil { return nil, err } - mtx.txs[multiTxKey{index, shard}] = tx + mtx.txs[mkey] = tx return tx, nil } @@ -257,31 +415,66 @@ func (mtx *MultiTx) tx(index string, shard uint64) (_ Tx, err error) { type multiTxKey struct { index string shard uint64 + write bool } // RoaringTx represents a fake transaction object for Roaring storage. type RoaringTx struct { + write bool Index *Index Field *Field fragment *fragment } -// Rollback is a no-op as Roaring does not support transactions. -func (tx *RoaringTx) Rollback() error { - return nil +func (tx *RoaringTx) UseRowCache() bool { + return true } +func (tx *RoaringTx) Pointer() string { + return fmt.Sprintf("%p", tx) +} + +// NewTxIterator returns a *roaring.Iterator that MUST have Close() called on it BEFORE +// the transaction Commits or Rollsback. +func (tx *RoaringTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator { + b, err := tx.bitmap(index, field, view, shard) + panicOn(err) + return b.Iterator() +} + +func (tx *RoaringTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) { + b, err := tx.bitmap(index, field, view, shard) + panicOn(err) + if err != nil { + return 0, nil, err + } + return b.ImportRoaringRawIterator(rit, clear, true, rowSize) +} + +func (tx *RoaringTx) Readonly() bool { + return !tx.write +} + +func (tx *RoaringTx) IncrementOpN(index, field, view string, shard uint64, changedN int) { + frag, err := tx.getFragment(index, field, view, shard) + panicOn(err) + frag.incrementOpN(changedN) +} + +// Rollback is a no-op as Roaring does not support transactions. +func (tx *RoaringTx) Rollback() {} + // Commit is a no-op as Roaring does not support transactions. func (tx *RoaringTx) Commit() error { return nil } func (tx *RoaringTx) RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) { - return tx.bitmap(field, view, shard) + return tx.bitmap(index, field, view, shard) } func (tx *RoaringTx) Container(index, field, view string, shard uint64, key uint64) (*roaring.Container, error) { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return nil, err } @@ -289,7 +482,7 @@ func (tx *RoaringTx) Container(index, field, view string, shard uint64, key uint } func (tx *RoaringTx) PutContainer(index, field, view string, shard uint64, key uint64, c *roaring.Container) error { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return err } @@ -298,7 +491,7 @@ func (tx *RoaringTx) PutContainer(index, field, view string, shard uint64, key u } func (tx *RoaringTx) RemoveContainer(index, field, view string, shard uint64, key uint64) error { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return err } @@ -306,24 +499,48 @@ func (tx *RoaringTx) RemoveContainer(index, field, view string, shard uint64, ke return nil } -func (tx *RoaringTx) Add(index, field, view string, shard uint64, a ...uint64) (changed bool, err error) { - b, err := tx.bitmap(field, view, shard) +func (tx *RoaringTx) Add(index, field, view string, shard uint64, batched bool, a ...uint64) (changeCount int, err error) { + b, err := tx.bitmap(index, field, view, shard) if err != nil { - return false, err + return 0, err } - return b.Add(a...) + if !batched { + changed, err := b.Add(a...) + if changed { + return 1, err + } + return 0, err + } + + // Note: do not replace b.AddN() with b.DirectAddN(). + // DirectAddN() does not do op-log operations inside roaring + // This creates a problem because RoaringTx needs the op-log + // to know when to flush the fragment to disk. + count, err := b.AddN(a...) // AddN does oplog batches. needed to keep op-log up to date. + + return count, err } -func (tx *RoaringTx) Remove(index, field, view string, shard uint64, a ...uint64) (changed bool, err error) { - b, err := tx.bitmap(field, view, shard) +func (tx *RoaringTx) Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) { + b, err := tx.bitmap(index, field, view, shard) if err != nil { - return false, err + return 0, err } - return b.Remove(a...) + changed, err := b.Remove(a...) // green TestFragment_Bug_Q2DoubleDelete + panicOn(err) + if changed { + return 1, err + } else { + return 0, err + } + + // Note: don't replace b.Remove(a...) with b.RemoveN(a...) or + // with b.DirectRemoveN(a...). If you do, you'll see + // TestFragment_Bug_Q2DoubleDelete go red. } func (tx *RoaringTx) Contains(index, field, view string, shard uint64, v uint64) (exists bool, err error) { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return false, err } @@ -331,7 +548,7 @@ func (tx *RoaringTx) Contains(index, field, view string, shard uint64, v uint64) } func (tx *RoaringTx) ContainerIterator(index, field, view string, shard uint64, key uint64) (citer roaring.ContainerIterator, found bool, err error) { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return nil, false, err } @@ -340,7 +557,7 @@ func (tx *RoaringTx) ContainerIterator(index, field, view string, shard uint64, } func (tx *RoaringTx) ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return err } @@ -348,7 +565,7 @@ func (tx *RoaringTx) ForEach(index, field, view string, shard uint64, fn func(i } func (tx *RoaringTx) ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return err } @@ -356,7 +573,7 @@ func (tx *RoaringTx) ForEachRange(index, field, view string, shard uint64, start } func (tx *RoaringTx) Count(index, field, view string, shard uint64) (uint64, error) { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return 0, err } @@ -364,7 +581,7 @@ func (tx *RoaringTx) Count(index, field, view string, shard uint64) (uint64, err } func (tx *RoaringTx) Max(index, field, view string, shard uint64) (uint64, error) { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return 0, err } @@ -372,7 +589,7 @@ func (tx *RoaringTx) Max(index, field, view string, shard uint64) (uint64, error } func (tx *RoaringTx) Min(index, field, view string, shard uint64) (uint64, bool, error) { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return 0, false, err } @@ -381,7 +598,7 @@ func (tx *RoaringTx) Min(index, field, view string, shard uint64) (uint64, bool, } func (tx *RoaringTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return err } @@ -390,7 +607,7 @@ func (tx *RoaringTx) UnionInPlace(index, field, view string, shard uint64, other } func (tx *RoaringTx) CountRange(index, field, view string, shard uint64, start, end uint64) (uint64, error) { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return 0, err } @@ -398,27 +615,43 @@ func (tx *RoaringTx) CountRange(index, field, view string, shard uint64, start, } func (tx *RoaringTx) OffsetRange(index, field, view string, shard uint64, offset, start, end uint64) (*roaring.Bitmap, error) { - b, err := tx.bitmap(field, view, shard) + b, err := tx.bitmap(index, field, view, shard) if err != nil { return nil, err } return b.OffsetRange(offset, start, end), nil } -func (tx *RoaringTx) bitmap(field, view string, shard uint64) (*roaring.Bitmap, error) { +// getFragment is used by IncrementOpN() and by bitmap() +func (tx *RoaringTx) getFragment(index, field, view string, shard uint64) (*fragment, error) { + // If a fragment is attached, always use it. if tx.fragment != nil { - return tx.fragment.storage, nil + return tx.fragment, nil } // If a field is attached, start from there. // Otherwise look up the field from the index. f := tx.Field + if f == nil { - if f = tx.Index.Field(field); f == nil { - return nil, ErrFieldNotFound + // we cannot assume that the tx.Index that we "started" on is the same + // as the index we are being queried; it might be foreign: TestExecutor_ForeignIndex + // So go through the holder + idx := tx.Index.holder.Index(index) + if idx == nil { + // only thing we can try is the cached index, and hope we aren't being asked for a foreign index. + f = tx.Index.Field(field) + if f == nil { + return nil, ErrFieldNotFound + } + } else { + if f = idx.Field(field); f == nil { + return nil, ErrFieldNotFound + } } } + // INVAR: f is not nil. v := f.view(view) if v == nil { @@ -429,5 +662,18 @@ func (tx *RoaringTx) bitmap(field, view string, shard uint64) (*roaring.Bitmap, if frag == nil { panic(fmt.Sprintf("fragment not found: %q / %q / %d", field, view, shard)) } + + // Note: we cannot cache frag into tx.fragment. + // Empirically, it breaks 245 top-level pilosa tests. + // tx.fragment = frag // breaks the world. + + return frag, nil +} + +func (tx *RoaringTx) bitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) { + frag, err := tx.getFragment(index, field, view, shard) + if err != nil { + return nil, err + } return frag.storage, nil } diff --git a/txfactory.go b/txfactory.go new file mode 100644 index 000000000..6d31f43ce --- /dev/null +++ b/txfactory.go @@ -0,0 +1,469 @@ +// 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 ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + + "github.com/pilosa/pilosa/v2/roaring" + "github.com/pkg/errors" +) + +// public strings that pilosa/server/config.go can reference +const ( + RoaringTxn string = "roaring" + BadgerTxn string = "badger" + RBFTxn string = "rbf" + // A is listed first, B is second. blueGreenTx returns the B output. + BlueGreenBadgerRoaring string = "badger_roaring" + BlueGreenRoaringBadger string = "roaring_badger" + + BlueGreenRBFRoaring string = "rbf_roaring" + BlueGreenRoaringRBF string = "roaring_rbf" + + BlueGreenBadgerRBF string = "badger_rbf" + BlueGreenRBFBadger string = "rbf_badger" +) + +// DefaultTxsrc is set here. pilosa/server/config.go references it +// to set the default for pilosa server exeutable. +// Can be overridden with env variable PILOSA_TXSRC for testing. +const DefaultTxsrc = RoaringTxn + +var sep = string(os.PathSeparator) + +// TxFactory abstracts the creation of Tx interface-level +// transactions so that RBF, or Badger, or Roaring-fragment-files, or several +// of these at once in parallel, is used as the storage and transction layer. +type TxFactory struct { + typeOfTx txtype + + bw *BadgerDBWrapper + + // could have more than one *Index, but for now keep it simple, + // and allow blueGreenTx to report badger contents via idx + idx *Index + + // TODO: put RBF database handle here. +} + +// integer types for fast switch{} +type txtype int + +const ( + noneTxn txtype = 0 + + roaringFragmentFilesTxn txtype = 1 // these don't really have any transactions + badgerTxn txtype = 2 + rbfTxn txtype = 3 + + // A is listed first, B is second. blueGreenTx returns the B output. + blueGreenBadgerRoaring txtype = 4 + blueGreenRoaringBadger txtype = 5 + + blueGreenRBFRoaring txtype = 6 + blueGreenRoaringRBF txtype = 7 + + blueGreenBadgerRBF txtype = 8 + blueGreenRBFBadger txtype = 9 +) + +func txsrcToTxtype(txsrc string) txtype { + switch txsrc { + case RoaringTxn: // "roaring" + return roaringFragmentFilesTxn + case BadgerTxn: // "badger" + return badgerTxn + case RBFTxn: // "rbf" + return rbfTxn + case BlueGreenBadgerRoaring: //"badger_roaring" + return blueGreenBadgerRoaring + case BlueGreenRoaringBadger: // "roaring_badger" + return blueGreenRoaringBadger + case BlueGreenRBFRoaring: //"rbf_roaring" + return blueGreenRBFRoaring + case BlueGreenRoaringRBF: //"roaring_rbf" + return blueGreenRoaringRBF + case BlueGreenBadgerRBF: // "badger_rbf" + return blueGreenBadgerRBF + case BlueGreenRBFBadger: // "rbf_badger" + return blueGreenRBFBadger + } + panic(fmt.Sprintf("unknown txsrc '%v'", txsrc)) +} + +func newTxFactory(txsrc string, path string) (f *TxFactory, err error) { + ty := txsrcToTxtype(txsrc) + if ty < 1 || ty > 9 { + panic(fmt.Sprintf("invalid txtype '%v'", int(ty))) + } + var bw *BadgerDBWrapper + if ty == badgerTxn || ty == 4 || ty == 5 || ty == 8 || ty == 9 { + bw, err = openBadgerDBWrapper(path) + // TODO(jea): figure out what the appropriate error path is here. + //fmt.Printf("warning: could not open badgerdb on path '%v': '%v'. For safety, we are opening a new '%v-fallback' instead\n", path, err, path+"-fallback") + if err != nil { + //bw, err = newBadgerDBWrapper(path + "-fallback") + bw, err = newBadgerDBWrapper(path) + } + panicOn(err) + + bw.doAllocZero = true + } + return &TxFactory{ + typeOfTx: ty, + bw: bw, + }, err +} + +// Txo holds the transaction options +type Txo struct { + Write bool + Field *Field + Index *Index + Fragment *fragment + Shard uint64 +} + +func (f *TxFactory) TxType() txtype { + return f.typeOfTx +} + +func (f *TxFactory) DeleteIndex(name string) error { + switch f.typeOfTx { + case roaringFragmentFilesTxn: + // from holder.go:955, by default is already done there with os.RemoveAll() + return nil + case badgerTxn: + return f.bw.DeleteIndex(name) + case rbfTxn: + panic("todo rbfTxn DeleteIndex(name)") + case blueGreenBadgerRoaring: + return f.bw.DeleteIndex(name) + case blueGreenRoaringBadger: + return f.bw.DeleteIndex(name) + } + panic(fmt.Sprintf("unknown f.typeOfTx type: '%v'", f.typeOfTx)) +} + +func (f *TxFactory) Close() error { + switch f.typeOfTx { + case roaringFragmentFilesTxn: + return nil + case badgerTxn: + // note cannot actually close Badger here. + // causes problems b/c tries holder.DeleteIndex tries to delete the index after db is closed. + //return f.bw.Close() + return nil + case rbfTxn: + panic("todo rbfTxn Close()") + case blueGreenBadgerRoaring: + return nil + case blueGreenRoaringBadger: + return nil + } + panic(fmt.Sprintf("unknown f.typeOfTx type: '%v'", f.typeOfTx)) +} + +func (f *TxFactory) CloseIndex(idx *Index) error { + switch f.typeOfTx { + case roaringFragmentFilesTxn: + return nil + case badgerTxn: + return nil + case rbfTxn: + panic("todo rbfTxn CloseIndex()") + + case blueGreenBadgerRoaring: + return nil + case blueGreenRoaringBadger: + return nil + } + panic(fmt.Sprintf("unknown f.typeOfTx type: '%v'", f.typeOfTx)) +} + +func (f *TxFactory) NewTx(o Txo) Tx { + + switch f.typeOfTx { + case roaringFragmentFilesTxn: + return &RoaringTx{write: o.Write, Field: o.Field, Index: o.Index, fragment: o.Fragment} + case badgerTxn: + btx := f.bw.NewBadgerTx(o.Write) + return btx + case rbfTxn: + panic("todo rbfTxn creation") + + case blueGreenBadgerRoaring: + btx := f.bw.NewBadgerTx(o.Write) + rtx := &RoaringTx{write: o.Write, Field: o.Field, Index: o.Index, fragment: o.Fragment} + return newBlueGreenTx(btx, rtx, f.idx) + case blueGreenRoaringBadger: + btx := f.bw.NewBadgerTx(o.Write) + rtx := &RoaringTx{write: o.Write, Field: o.Field, Index: o.Index, fragment: o.Fragment} + return newBlueGreenTx(rtx, btx, f.idx) + } + panic(fmt.Sprintf("unknown f.typeOfTx type: '%v'", f.typeOfTx)) +} + +func (ty txtype) String() string { + switch ty { + case noneTxn: + return "noneTxn" + case roaringFragmentFilesTxn: + return "roaringFragmentFilesTxn" + case badgerTxn: + return "badgerTxn" + case rbfTxn: + return "rbfTxn" + case blueGreenBadgerRoaring: + return "blueGreenBadgerRoaring" + case blueGreenRoaringBadger: + return "blueGreenRoaringBadger" + case blueGreenRBFRoaring: + return "blueGreenRBFRoaring" + case blueGreenRoaringRBF: + return "blueGreenRoaringRBF" + case blueGreenBadgerRBF: + return "blueGreenBadgerRBF" + case blueGreenRBFBadger: + return "blueGreenRBFBadger" + } + panic(fmt.Sprintf("unhandled ty '%v' in txtype.String()", int(ty))) +} + +// StringifiedBadgerKeys displays the keys visible in BadgerDB for the idx *Index. +// If optionalUseThisTx is nil, it will start a new read-only transaction to +// do this query. Otherwise it will piggy back on the provided transaction. +// Hence to view uncommited keys, you must provide in optionalUseThisTx the +// Tx in which they have been added. +func (idx *Index) StringifiedBadgerKeys(optionalUseThisTx Tx) string { + return idx.Txf.bw.StringifiedBadgerKeys(optionalUseThisTx) +} + +// fragmentSpecFromRoaringPath takes a path releative to the +// index directory, not including the name of the index itself. +// The path should not start with the path separator sep ('/' or '\\') rune. +func fragmentSpecFromRoaringPath(path string) (field, view string, shard uint64, err error) { + + if len(path) == 0 { + err = fmt.Errorf("fragmentSpecFromRoaringPath error: path '%v' too short", path) + return + } + if path[:1] == sep { + err = fmt.Errorf("fragmentSpecFromRoaringPath error: path '%v' cannot start with separator '%v'; must be relative to the index base directory", path, sep) + return + } + + // sample path: + // field view shard + // myfield/views/standard/fragments/0 + s := strings.Split(path, "/") + n := len(s) + if n != 5 { + err = fmt.Errorf("len(s)=%v, but expected 5. path='%v'", n, path) + return + } + field = s[0] + view = s[2] + shard, err = strconv.ParseUint(s[4], 10, 64) + if err != nil { + err = fmt.Errorf("fragmentSpecFromRoaringPath(path='%v') could not parse shard '%v' as uint: '%v'", path, s[4], err) + } + return +} + +func (idx *Index) StringifiedRoaringKeys() (r string) { + + paths, err := listFilesUnderDir(idx.path, false, "", true) + panicOn(err) + index := idx.name + + r = "allkeys:[\n" + 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) + panicOn(err) + //r += fmt.Sprintf("path:'%v' fragment contains:\n") + s + r += s + } + 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) { + + var info roaring.BitmapInfo + _ = info + var f *os.File + f, err = os.Open(path) + panicOn(err) + if err != nil { + return + } + + var fi os.FileInfo + fi, err = f.Stat() + panicOn(err) + if err != nil { + return + } + // Memory map the file. + data, err := syscall.Mmap(int(f.Fd()), 0, int(fi.Size()), syscall.PROT_READ, syscall.MAP_SHARED) + if err != nil { + err = errors.Wrap(err, "mmapping") + return + } + defer func() { + err := syscall.Munmap(data) + if err != nil { + panic(fmt.Errorf("loadRawRoaringContainer: munmap failed: %v", err)) + } + panicOn(f.Close()) + }() + + // Attach the mmap file to the bitmap. + var rbm *roaring.Bitmap + rbm, _, err = roaring.InspectBinary(data, true, &info) + if err != nil { + err = errors.Wrap(err, "inspecting") + return + } + + citer, found := rbm.Containers.Iterator(0) + _ = found // probably gonna use just the Ops log instead, so don't panic if !found. + + for citer.Next() { + ckey, ct := citer.Value() + by := containerToBytes(ct) + hash := blake3sum16(by) + + cts := roaring.NewSliceContainers() + cts.Put(ckey, ct) + rbm := &roaring.Bitmap{Containers: cts} + srbm := bitmapAsString(rbm) + panicOn(err) + + bkey := string(badgerKey(index, field, view, shard, ckey)) + + r += fmt.Sprintf("%v -> %v (%v hot)\n", bkey, hash, ct.N()) + r += " ......." + srbm + "\n" + } + + return +} + +// listFilesUnderDir returns the paths of files found under directory root. +// If includeRoot is true, it returns the full path, otherwise paths are relative to root. +// If requriedSuffix is supplied, the returned file paths will end in that, +// and any other files found during the walk of the directory tree will be ignored. +// If ignoreEmpty is true, files of size 0 will be excluded. +func listFilesUnderDir(root string, includeRoot bool, requiredSuffix string, ignoreEmpty bool) (files []string, err error) { + if !dirExists(root) { + return nil, fmt.Errorf("listFilesUnderDir error: root directory '%v' not found", root) + } + n := len(root) + 1 + if includeRoot { + n = 0 + } + err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if len(path) < n { + // ignore + } else { + if info == nil { + panic(fmt.Sprintf("info was nil for path = '%v'", path)) + } + if info.IsDir() { + // skip directories. + } else { + if ignoreEmpty && info.Size() == 0 { + return nil + } + if requiredSuffix == "" || strings.HasSuffix(path, requiredSuffix) { + files = append(files, path[n:]) + } + } + } + return nil + }) + return +} + +func dirExists(name string) bool { + fi, err := os.Stat(name) + if err != nil { + return false + } + if fi.IsDir() { + return true + } + return false +} + +func fileSize(name string) (int64, error) { + fi, err := os.Stat(name) + if err != nil { + return -1, err + } + return fi.Size(), nil +} + +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 { + case containerNil: + panic("nil container") + case containerArray: + return fromArray16(roaring.AsArray(ct)) + case containerBitmap: + return fromArray64(roaring.AsBitmap(ct)) + case containerRun: + return fromInterval16(roaring.AsRuns(ct)) + } + panic(fmt.Sprintf("unknown container type '%v'", int(ty))) +} diff --git a/utils_internal_test.go b/utils_internal_test.go index 386282adb..e9603b222 100644 --- a/utils_internal_test.go +++ b/utils_internal_test.go @@ -135,11 +135,13 @@ func (t *ClusterCluster) SetBit(index, field string, rowID, colID uint64, x *tim } if err := func() error { - tx, err := c.holder.Begin(true) + tx, err := c.holder.BeginTx(writable, c.holder.indexes[f.index]) + if tx != nil { + defer tx.Rollback() + } if err != nil { return err } - defer func() { _ = tx.Rollback() }() if _, err := f.SetBit(tx, rowID, colID, x); err != nil { return err diff --git a/vprint.go b/vprint.go new file mode 100644 index 000000000..bc40dfe45 --- /dev/null +++ b/vprint.go @@ -0,0 +1,113 @@ +// home: https://github.com/glyerine/vprint +// Copyright 2019 Jason E. Aten, Ph.D. All rights reserved. +// License: MIT +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package pilosa + +import ( + "fmt" + "io" + "os" + "path" + "runtime" + "runtime/debug" + "sync" + "time" +) + +const RFC3339MsecTz0 = "2006-01-02T15:04:05.000Z07:00" +const RFC3339UsecTz0 = "2006-01-02T15:04:05.000000Z07:00" + +// for tons of debug output +var VerboseVerbose bool = false + +// convience functions for . import +var pp = PP +var vv = VV + +var panicOn = PanicOn + +func init() { + // keeper linter happy + _ = pp + _ = vv +} + +func PanicOn(err error) { + if err != nil { + panic(err) + } +} + +func PP(format string, a ...interface{}) { + if VerboseVerbose { + TSPrintf(format, a...) + } +} + +func VV(format string, a ...interface{}) { + TSPrintf(format, a...) +} + +func AlwaysPrintf(format string, a ...interface{}) { + TSPrintf(format, a...) +} + +var tsPrintfMut sync.Mutex + +// time-stamped printf +func TSPrintf(format string, a ...interface{}) { + tsPrintfMut.Lock() + Printf("\n%s %s ", FileLine(3), ts()) + Printf(format+"\n", a...) + tsPrintfMut.Unlock() +} + +// get timestamp for logging purposes +func ts() string { + return time.Now().Format(RFC3339UsecTz0) +} + +// so we can multi write easily, use our own printf +var OurStdout io.Writer = os.Stdout + +// Printf formats according to a format specifier and writes to standard output. +// It returns the number of bytes written and any write error encountered. +func Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(OurStdout, format, a...) +} + +func FileLine(depth int) string { + _, fileName, fileLine, ok := runtime.Caller(depth) + var s string + if ok { + s = fmt.Sprintf("%s:%d", path.Base(fileName), fileLine) + } else { + s = "" + } + return s +} + +func stack() string { + return string(debug.Stack()) +} diff --git a/xrbrsupport.go b/xrbrsupport.go index b06d94cc5..ebfd9db27 100644 --- a/xrbrsupport.go +++ b/xrbrsupport.go @@ -59,7 +59,7 @@ func (rbc *RBFConverter) Convert(index, field, view string, shard uint64, rb *ro if err != nil { return err } - tx, err := db.Begin(true) + tx, err := db.Begin(writable) if err != nil { return err }