mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
Giant Commit: drop a bunch of stuff we don't use.
These commits are hard to disentagle, and doing them separately means re-modifying the same chunks of code several times before removing it, and similar things. Basically: (1) Drop the bolt backend storage. (2) Drop the blue-green wrapper that compares two backends. (3) Drop unused or barely-used Tx API components from all the remaining backends. (4) Minor related cleanup to simplify things related to these. The boltdb backend existed only to verify RBF. The blue-green wrapper was mostly used to verify RBF, but in practice we had to do a lot of working around that, and it introduced a lot of special cases. Types removed: IteratorFinder: Used only to implement the roaring iterator on top of boltdb, and to complicate the way it worked in roaring. Reverted the complications. Also unexport NewSliceContainers which is used only for that outside of roaring's internals. PortMapper from cluster_internal_test.go: Used only for a test we removed early this year. Never used for anything else. RawRoaringData: Totally unused. TxStore: Totally unused. Functions removed from Tx API, and sometimes corresponding members were removed from structs: * Dump: debugging code, I don't think I found any actually reachable paths to it. * Group: only used for debugging TxGroup stuff * IncrementOpN: only used by fragment, fragment can increment its own opN. * Options: unused? * Pointer: debugging only * Readonly: used only to decide how to handle Tx in a TxGrp, but we never add a non-readonly Tx to a TxGrp. Removed also all the corresponding write-aware stuff. * RoaringBitmapReader: Used exactly once, can just be a bm.WriteTo. * Sn (and OpenSnList): Unused * UnionInPlace: unused and conceptually-invalid; it didn't write to storage and shouldn't have, and was just "create a bitmap then call union-in-place", which we can do directly. * UseRowCache: just checked storage.UseRowCache. Other things removed: The SetRequiredForAtomicWriteTx and ClearRequiredForAtomicWriteTx functions go away, since nothing now seems to be using them? Same for holder_internal_test's `testHasBit` and `testMustNotHaveBit`, which were unused. The DBPerShard "DeleteDBPath" and "HasData" functions and related parts were mostly unused; took out the parts that were never actually being reached. Changed the API of one function to simplify special cases and remove things: * ImportRoaringBits had a special "data" argument which gave it subtly different semantics for RBF and roaring (for roaring, it could produce a roaring bitmap *with ops log*), didn't seem to be adding much. Removed corresponding "readStorageFromArchive" which is not otherwise used. Also took out various debugging/dumping functions that were unused and may have bitrotted. Dropped a test from txfactory_internal_test, and the "pjobs" code, because those two were the only things that needed Barrier and thus idem, which lets us drop two more dependencies. We already have errgroup for grouping things which want to terminate as soon as one of them errors, approximately. To do better we'd have to have context-threading, really. Unbroke the WriteFragment test for non-roaring tests and made it not roaring-only.
This commit is contained in:
parent
ecd0ecc6d1
commit
ad30a926f4
44 changed files with 223 additions and 7481 deletions
12
api.go
12
api.go
|
|
@ -1510,9 +1510,7 @@ func addClearToImportOptions(opts []ImportOption) []ImportOption {
|
|||
return append(opts, OptImportOptionsClear(true))
|
||||
}
|
||||
|
||||
// Import avoids re-writing a bajillion tests to be transaction-aware by allowing a nil pQcx.
|
||||
// It is convenient for some tests, particularly those in loops, to pass a nil qcx and
|
||||
// treat the Import as having been commited when we return without error. We make it so.
|
||||
// Import does the top-level importing.
|
||||
func (api *API) Import(ctx context.Context, qcx *Qcx, req *ImportRequest, opts ...ImportOption) (err error) {
|
||||
if req.Clear {
|
||||
opts = addClearToImportOptions(opts)
|
||||
|
|
@ -1648,8 +1646,8 @@ func (api *API) ImportWithTx(ctx context.Context, qcx *Qcx, req *ImportRequest,
|
|||
return errors.Wrap(err, "committing")
|
||||
}
|
||||
|
||||
// ImportValue avoids re-writing a bajillion tests by allowing a nil pQcx.
|
||||
// Then we will commit before returning.
|
||||
// ImportValue is a wrapper around the common code in ImportValueWithTx, which
|
||||
// currently just translates req.Clear into a clear ImportOption.
|
||||
func (api *API) ImportValue(ctx context.Context, qcx *Qcx, req *ImportValueRequest, opts ...ImportOption) error {
|
||||
if req.Clear {
|
||||
opts = addClearToImportOptions(opts)
|
||||
|
|
@ -2654,9 +2652,7 @@ func (api *API) RestoreShard(ctx context.Context, indexName string, shard uint64
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//need to find the path to the db
|
||||
//will not work on blue green
|
||||
db := dbs.W[0]
|
||||
db := dbs.W
|
||||
finalPath := db.Path() + "/data"
|
||||
tempPath := finalPath + ".tmp"
|
||||
o, err := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
|
||||
|
|
|
|||
219
barrier.go
219
barrier.go
|
|
@ -1,219 +0,0 @@
|
|||
// home https://github.com/glycerine/lmdb-go
|
||||
// Copyright (c) 2020, the lmdb-go authors
|
||||
// Copyright (c) 2015, Bryan Matsuo
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
|
||||
// Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
|
||||
// Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
|
||||
// Neither the name of the author nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package pilosa
|
||||
|
||||
import (
|
||||
"github.com/glycerine/idem"
|
||||
)
|
||||
|
||||
// Barrier allows us to temporarily halt all readers, so that
|
||||
// a writer can commit alone and thus compact the db.
|
||||
// The Barrier starts unblocked, alllowing passage to any
|
||||
// caller of WaitAtGate().
|
||||
type Barrier struct {
|
||||
wait chan *appointment // send upon entering the waiting room.
|
||||
halt *idem.Halter
|
||||
blockReqCh chan *blockReq
|
||||
unblockCh chan *unblock
|
||||
}
|
||||
|
||||
type blockReq struct {
|
||||
count int
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newBlockReq(count int) *blockReq {
|
||||
return &blockReq{
|
||||
count: count,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type appointment struct {
|
||||
id int
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newAppointment(id int) *appointment {
|
||||
return &appointment{
|
||||
id: id,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// NewBarrier is either open, allowing immediate passage,
|
||||
// or blocked, halting all callers at WaitAtGate()
|
||||
// until the barrier is opened. By default it is open.
|
||||
//
|
||||
// Barrier.Close() must be called when the barrier
|
||||
// is no longer needed to avoid a goroutine leak.
|
||||
func NewBarrier() (b *Barrier) {
|
||||
b = &Barrier{
|
||||
wait: make(chan *appointment), // waiters indicate they are waiting for the gate by sending here.
|
||||
halt: idem.NewHalter(),
|
||||
blockReqCh: make(chan *blockReq),
|
||||
unblockCh: make(chan *unblock),
|
||||
}
|
||||
go func() {
|
||||
defer b.halt.Done.Close()
|
||||
|
||||
var waitlist []*appointment
|
||||
var curBlockReq *blockReq
|
||||
|
||||
for {
|
||||
select {
|
||||
case br := <-b.blockReqCh:
|
||||
if br.count == 0 {
|
||||
close(br.done)
|
||||
continue
|
||||
}
|
||||
if curBlockReq == nil {
|
||||
// good, changing state from open to closed barrier.
|
||||
} else {
|
||||
panic("got 2nd block request atop of first")
|
||||
}
|
||||
curBlockReq = br
|
||||
//vv("barrier: request to block for %v waiters", br.count)
|
||||
if len(waitlist) != 0 {
|
||||
panic("had waiters when we were open, internal/client bug")
|
||||
}
|
||||
case appt := <-b.wait:
|
||||
//vv("barrier.wait sees appt = '%#v' and curBlockReq = '%#v'", appt, curBlockReq)
|
||||
if curBlockReq == nil {
|
||||
close(appt.done)
|
||||
continue
|
||||
}
|
||||
waitlist = append(waitlist, appt)
|
||||
n := len(waitlist)
|
||||
th := curBlockReq.count
|
||||
if th < 0 {
|
||||
// infinite waiters. we block everybody until we
|
||||
// see an unblock request.
|
||||
continue
|
||||
}
|
||||
if n >= th {
|
||||
close(curBlockReq.done)
|
||||
curBlockReq = nil
|
||||
}
|
||||
case ub := <-b.unblockCh:
|
||||
for _, appt := range waitlist {
|
||||
close(appt.done)
|
||||
}
|
||||
waitlist = nil
|
||||
curBlockReq = nil
|
||||
close(ub.done)
|
||||
case <-b.halt.ReqStop.Chan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
// WaitAtGate will return immediately
|
||||
// if the barrier is unblocked. Otherwise
|
||||
// it will not return until another
|
||||
// goroutine unblocks the barrier.
|
||||
func (b *Barrier) WaitAtGate(id int) {
|
||||
appt := newAppointment(id)
|
||||
select {
|
||||
case b.wait <- appt:
|
||||
select {
|
||||
case <-appt.done:
|
||||
case <-b.halt.ReqStop.Chan:
|
||||
}
|
||||
case <-b.halt.ReqStop.Chan:
|
||||
}
|
||||
}
|
||||
|
||||
// Close should be called to stop the
|
||||
// barrier's background goroutine when
|
||||
// you are done using the barrier.
|
||||
func (b *Barrier) Close() {
|
||||
b.halt.ReqStop.Close()
|
||||
<-b.halt.Done.Chan
|
||||
}
|
||||
|
||||
type unblock struct {
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newUnblock() *unblock {
|
||||
return &unblock{
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Unblock lets all waiting goroutines resume execution.
|
||||
func (b *Barrier) UnblockReaders() {
|
||||
ub := newUnblock()
|
||||
select {
|
||||
case b.unblockCh <- ub:
|
||||
select {
|
||||
case <-ub.done:
|
||||
case <-b.halt.ReqStop.Chan:
|
||||
}
|
||||
case <-b.halt.ReqStop.Chan:
|
||||
}
|
||||
}
|
||||
|
||||
// BlockUntil is called with a count, the
|
||||
// number of waiters required to be present and waiting
|
||||
// at the gate before call returns.
|
||||
// A count of < 0 will return immediately and raise
|
||||
// the barrier to any number of arriving readers.
|
||||
// A count of 0 is a no-op.
|
||||
//
|
||||
// Otherwise we raise the barrier
|
||||
// and wait until we have seen count other goroutines waiting
|
||||
// on it.
|
||||
//
|
||||
// We return without releasing the waiters. Call
|
||||
// Open when you want them to resume.
|
||||
func (b *Barrier) BlockUntil(count int) {
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
req := newBlockReq(count)
|
||||
b.blockReqCh <- req
|
||||
if count > 0 {
|
||||
<-req.done
|
||||
}
|
||||
}
|
||||
|
||||
// BlockAllReadersNoWait raises the barrier to
|
||||
// an infinite number of waiters and returns immediately
|
||||
// to the caller.
|
||||
func (b *Barrier) BlockAllReadersNoWait() {
|
||||
req := newBlockReq(-1) // -1 means block any number of readers.
|
||||
b.blockReqCh <- req
|
||||
// don't wait. <-req.done
|
||||
}
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
// home https://github.com/glycerine/lmdb-go
|
||||
// Copyright (c) 2020, the lmdb-go authors
|
||||
// Copyright (c) 2015, Bryan Matsuo
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
|
||||
// Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
|
||||
// Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
|
||||
// Neither the name of the author nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package pilosa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBarrierHolds(t *testing.T) {
|
||||
b := NewBarrier()
|
||||
defer b.Close()
|
||||
|
||||
released := int64(0)
|
||||
|
||||
waiter := func(i int) {
|
||||
b.WaitAtGate(i)
|
||||
//vv("goro %v is released", i)
|
||||
atomic.AddInt64(&released, 1)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
go waiter(i)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
r := atomic.SwapInt64(&released, 0)
|
||||
if r != 3 {
|
||||
panic("open barrier held back goro")
|
||||
}
|
||||
//vv("good: barrier started open")
|
||||
|
||||
//seenAll := make(chan bool)
|
||||
b.BlockAllReadersNoWait()
|
||||
for i := 0; i < 3; i++ {
|
||||
go waiter(i)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second)
|
||||
r = atomic.SwapInt64(&released, 0)
|
||||
if r != 0 {
|
||||
panic("bad: barrier did not hold back goro")
|
||||
}
|
||||
//vv("good: barrier of 4 did not release on 3")
|
||||
go waiter(4)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
r = atomic.SwapInt64(&released, 0)
|
||||
if r != 0 {
|
||||
panic(fmt.Sprintf("bad: barrier did not hold back goro, should wait for unblock. r = %v", r))
|
||||
}
|
||||
|
||||
b.UnblockReaders()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
r = atomic.SwapInt64(&released, 0)
|
||||
if r != 4 {
|
||||
panic("bad: unblock should have released 4 goro")
|
||||
}
|
||||
|
||||
}
|
||||
1028
bluegreentx.go
1028
bluegreentx.go
File diff suppressed because it is too large
Load diff
|
|
@ -1,100 +0,0 @@
|
|||
// Copyright 2020 Pilosa Corp.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pilosa
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
cryrand "crypto/rand"
|
||||
|
||||
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
||||
)
|
||||
|
||||
var _ = context.Background
|
||||
var _ = os.Open
|
||||
var _ = strings.Split
|
||||
|
||||
func TestMultiReaderB(t *testing.T) {
|
||||
// MultiReaderB should read identical chunks of bytes from both its "a" and "b"
|
||||
// member io.Readers, else it should panic. This should hold for
|
||||
// varying sizes of inputs.
|
||||
|
||||
for n := 1 << 5; n < (1 << 18); n = n*2 - 13 {
|
||||
src := io.LimitReader(cryrand.Reader, int64(n))
|
||||
|
||||
a := make([]byte, n)
|
||||
nr := 0
|
||||
for nr < n {
|
||||
na, err := src.Read(a)
|
||||
PanicOn(err)
|
||||
nr += na
|
||||
}
|
||||
if nr != n {
|
||||
panic("short read")
|
||||
}
|
||||
|
||||
b := make([]byte, n)
|
||||
copy(b, a)
|
||||
if !bytes.Equal(a, b) {
|
||||
panic("test prep failed")
|
||||
}
|
||||
|
||||
m := &MultiReaderB{
|
||||
a: ioutil.NopCloser(bytes.NewBuffer(a)),
|
||||
b: ioutil.NopCloser(bytes.NewBuffer(b)),
|
||||
}
|
||||
|
||||
// should not trigger the internal panic of MultiReadB
|
||||
ncp, err := io.Copy(ioutil.Discard, m)
|
||||
PanicOn(err)
|
||||
if ncp != int64(n) {
|
||||
panic("short copy")
|
||||
}
|
||||
|
||||
for victim := 0; victim < n; victim += 7 {
|
||||
|
||||
copy(b, a)
|
||||
if victim%2 == 0 {
|
||||
// corrupt b
|
||||
b[victim] = (b[victim] + 1) % 255
|
||||
} else {
|
||||
// corrupt a
|
||||
a[victim] = (a[victim] + 1) % 255
|
||||
}
|
||||
m = &MultiReaderB{
|
||||
a: ioutil.NopCloser(bytes.NewBuffer(a)),
|
||||
b: ioutil.NopCloser(bytes.NewBuffer(b)),
|
||||
}
|
||||
helperShouldPanicOnCopy(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func helperShouldPanicOnCopy(m *MultiReaderB) {
|
||||
// differences in bytes read should be noticed
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
panic("expected panic on byte difference but didn't see it")
|
||||
}
|
||||
}()
|
||||
_, _ = io.Copy(ioutil.Discard, m)
|
||||
}
|
||||
1270
bolt_test.go
1270
bolt_test.go
File diff suppressed because it is too large
Load diff
71
catcher.go
71
catcher.go
|
|
@ -15,9 +15,6 @@
|
|||
package pilosa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/molecula/featurebase/v2/roaring"
|
||||
txkey "github.com/molecula/featurebase/v2/short_txkey"
|
||||
. "github.com/molecula/featurebase/v2/vprint"
|
||||
|
|
@ -42,40 +39,18 @@ func init() {
|
|||
|
||||
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) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
|
||||
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() PanicOn '%v' at '%v'", r, Stack())
|
||||
PanicOn(r)
|
||||
}
|
||||
}()
|
||||
return c.b.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize, data)
|
||||
}
|
||||
|
||||
func (c *catcherTx) Dump(short bool, shard uint64) {
|
||||
c.b.Dump(short, shard)
|
||||
}
|
||||
|
||||
func (c *catcherTx) Readonly() bool {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see Readonly() PanicOn '%v' at '%v'", r, Stack())
|
||||
PanicOn(r)
|
||||
}
|
||||
}()
|
||||
return c.b.Readonly()
|
||||
}
|
||||
|
||||
func (tx *catcherTx) Pointer() string {
|
||||
return fmt.Sprintf("%p", tx)
|
||||
return c.b.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize)
|
||||
}
|
||||
|
||||
func (c *catcherTx) Rollback() {
|
||||
|
|
@ -143,14 +118,6 @@ func (c *catcherTx) RemoveContainer(index, field, view string, shard uint64, key
|
|||
return c.b.RemoveContainer(index, field, view, shard, key)
|
||||
}
|
||||
|
||||
func (c *catcherTx) UseRowCache() bool {
|
||||
return c.b.UseRowCache()
|
||||
}
|
||||
|
||||
func (c *catcherTx) IsDone() bool {
|
||||
return c.b.IsDone()
|
||||
}
|
||||
|
||||
func (c *catcherTx) Add(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) {
|
||||
|
||||
defer func() {
|
||||
|
|
@ -250,17 +217,6 @@ func (c *catcherTx) Min(index, field, view string, shard uint64) (uint64, bool,
|
|||
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() PanicOn '%v' at '%v'", r, Stack())
|
||||
PanicOn(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() {
|
||||
|
|
@ -283,33 +239,10 @@ func (c *catcherTx) OffsetRange(index, field, view string, shard, offset, start,
|
|||
return c.b.OffsetRange(index, field, view, shard, offset, start, end)
|
||||
}
|
||||
|
||||
func (c *catcherTx) RoaringBitmapReader(index, field, view string, shard uint64, fragmentPathForRoaring string) (r io.ReadCloser, sz int64, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see RoaringBitmapReader() PanicOn '%v' at '%v'", r, Stack())
|
||||
PanicOn(r)
|
||||
}
|
||||
}()
|
||||
return c.b.RoaringBitmapReader(index, field, view, shard, fragmentPathForRoaring)
|
||||
}
|
||||
|
||||
func (c *catcherTx) Type() string {
|
||||
return c.b.Type()
|
||||
}
|
||||
|
||||
func (c *catcherTx) Group() *TxGroup {
|
||||
return c.b.Group()
|
||||
}
|
||||
|
||||
func (c *catcherTx) Options() Txo {
|
||||
return c.b.Options()
|
||||
}
|
||||
|
||||
// Sn retreives the serial number of the Tx.
|
||||
func (c *catcherTx) Sn() int64 {
|
||||
return c.b.Sn()
|
||||
}
|
||||
|
||||
func (c *catcherTx) ApplyFilter(index, field, view string, shard uint64, ckey uint64, filter roaring.BitmapFilter) (err error) {
|
||||
return GenericApplyFilter(c, index, field, view, shard, ckey, filter)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ package pilosa
|
|||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -30,57 +29,8 @@ import (
|
|||
"github.com/molecula/featurebase/v2/testhook"
|
||||
"github.com/molecula/featurebase/v2/topology"
|
||||
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GlobalPortMap avoids many races and port conflicts when setting
|
||||
// up ports for test clusters. Used for tests only.
|
||||
var globalPortMap *GlobalPortMapper
|
||||
|
||||
func init() {
|
||||
globalPortMap = NewGlobalPortMapper(300)
|
||||
}
|
||||
|
||||
// GlobalPortMapper maintains a pool of available ports by
|
||||
// holding them open until GetPort() is called.
|
||||
type GlobalPortMapper struct {
|
||||
availPorts map[int]net.Listener
|
||||
}
|
||||
|
||||
// reserve n ports
|
||||
func NewGlobalPortMapper(n int) (pm *GlobalPortMapper) {
|
||||
|
||||
pm = &GlobalPortMapper{
|
||||
availPorts: make(map[int]net.Listener),
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
lsn, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "trying to listen on ephemeral port"))
|
||||
}
|
||||
r := lsn.Addr()
|
||||
port := r.(*net.TCPAddr).Port
|
||||
pm.availPorts[port] = lsn
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (pm *GlobalPortMapper) GetPort() (port int, err error) {
|
||||
for port, lsn := range pm.availPorts {
|
||||
lsn.Close()
|
||||
return port, nil
|
||||
}
|
||||
return -1, fmt.Errorf("no more ports available")
|
||||
}
|
||||
|
||||
func (pm *GlobalPortMapper) MustGetPort() int {
|
||||
port, err := pm.GetPort()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
// Ensure that fragCombos creates the correct fragment mapping.
|
||||
func TestFragCombos(t *testing.T) {
|
||||
uri0, err := pnet.NewURIFromAddress("host0")
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ func Migrate(dataDir, backupPath string) error {
|
|||
return err
|
||||
}
|
||||
key := string(txkey.Prefix(index, field, view, shard))
|
||||
_, _, err = tx.ImportRoaringBits(key, itr, clear, log, rowSize, nil)
|
||||
_, _, err = tx.ImportRoaringBits(key, itr, clear, log, rowSize)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ func BuildServerFlags(cmd *cobra.Command, srv *server.Command) {
|
|||
// over-ride.
|
||||
// TODO: the comment above was carried over from the PILOSA_TXSRC flag, but
|
||||
// we should confirm that this still applies.
|
||||
flags.StringVar(&srv.Config.Storage.Backend, "storage.backend", storage.DefaultBackend, fmt.Sprintf("transaction/storage to use: one of roaring, rbf, bolt, or a blue-green setup: rbf_roaring, roaring_rbf, bolt_roaring, roaring_bolt, bolt_rbf, etc. The default is: %v. The env var PILOSA_STORAGE_BACKEND is over-ridden by --storage.backend option on the command line.", storage.DefaultBackend))
|
||||
flags.StringVar(&srv.Config.Storage.Backend, "storage.backend", storage.DefaultBackend, fmt.Sprintf("transaction/storage to use: one of roaring or rbf. The default is: %v. The env var PILOSA_STORAGE_BACKEND is over-ridden by --storage.backend option on the command line.", storage.DefaultBackend))
|
||||
flags.BoolVar(&srv.Config.Storage.FsyncEnabled, "storage.fsync", true, "enable fsync fully safe flush-to-disk")
|
||||
|
||||
// RowcacheOn
|
||||
|
|
|
|||
638
dbshard.go
638
dbshard.go
|
|
@ -57,12 +57,10 @@ type DBIndex struct {
|
|||
|
||||
type DBWrapper interface {
|
||||
NewTx(write bool, initialIndexName string, o Txo) (tx Tx, err error)
|
||||
DeleteDBPath(dbs *DBShard) error
|
||||
Close() error
|
||||
DeleteFragment(index, field, view string, shard uint64, frag interface{}) error
|
||||
DeleteField(index, field, fieldPath string) error
|
||||
OpenListString() string
|
||||
OpenSnList() (sns []int64)
|
||||
Path() string
|
||||
HasData() (has bool, err error)
|
||||
SetHolder(h *Holder)
|
||||
|
|
@ -82,134 +80,52 @@ type DBShard struct {
|
|||
Shard uint64
|
||||
Open bool
|
||||
|
||||
// With RWMutex, the blue-green Tx can start and commit
|
||||
// atomically.
|
||||
mut sync.RWMutex
|
||||
|
||||
types []txtype
|
||||
stypes []string
|
||||
typ txtype
|
||||
styp string
|
||||
hasRoaring bool // if either of the types is roaringTxn
|
||||
|
||||
W []DBWrapper
|
||||
W DBWrapper
|
||||
ParentDBIndex *DBIndex
|
||||
|
||||
idx *Index
|
||||
per *DBPerShard
|
||||
|
||||
useOpenList int
|
||||
closed bool
|
||||
|
||||
isBlueGreen bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (dbs *DBShard) DeleteFragment(index, field, view string, shard uint64, frag interface{}) (err error) {
|
||||
for _, w := range dbs.W {
|
||||
err = w.DeleteFragment(index, field, view, shard, frag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if index != dbs.Index {
|
||||
return fmt.Errorf("DeleteFragment called on DBShard for %q with index %q", dbs.Index, index)
|
||||
}
|
||||
return
|
||||
if shard != dbs.Shard {
|
||||
return fmt.Errorf("DeleteFragment called on DBShard for %d with shard %d", dbs.Shard, shard)
|
||||
}
|
||||
return dbs.W.DeleteFragment(index, field, view, shard, frag)
|
||||
}
|
||||
|
||||
func (dbs *DBShard) DeleteFieldFromStore(index, field, fieldPath string) (err error) {
|
||||
for _, w := range dbs.W {
|
||||
err = w.DeleteField(index, field, fieldPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if index != dbs.Index {
|
||||
return fmt.Errorf("DeleteFieldFromStore called on DBShard for %q with index %q", dbs.Index, index)
|
||||
}
|
||||
return
|
||||
return dbs.W.DeleteField(index, field, fieldPath)
|
||||
}
|
||||
|
||||
func (dbs *DBShard) Close() (err error) {
|
||||
for _, w := range dbs.W {
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
dbs.closed = true
|
||||
return
|
||||
}
|
||||
|
||||
// Cleanup must be called at every commit/rollback of a Tx, in
|
||||
// order to release the read-write mutex that guarantees a single
|
||||
// writer at a time. Each tx must take care to call cleanup()
|
||||
// exactly once. examples:
|
||||
// tx.o.dbs.Cleanup(tx)
|
||||
// tx.Options().dbs.Cleanup(tx)
|
||||
//
|
||||
func (dbs *DBShard) Cleanup(tx Tx) {
|
||||
if dbs == nil {
|
||||
return // some tests are using Tx only, no dbs available.
|
||||
}
|
||||
//vv("gid %v top of DBShard %v Cleanup for tx.Sn = %v; dbs=%p; is 2nd: %v; type='%v'; dbs.stypes='%#v'", curGID(), dbs.Shard, tx.Sn(), dbs, tx.Type() == dbs.stypes[1], tx.Type(), dbs.stypes)
|
||||
if !dbs.hasRoaring {
|
||||
if dbs.isBlueGreen {
|
||||
// only release on the 2nd Tx's cleanup
|
||||
if tx.Type() == dbs.stypes[1] {
|
||||
if tx.Readonly() {
|
||||
dbs.mut.RUnlock()
|
||||
//vv("gid %v released read-lock on shard %v", curGID(), dbs.Shard)
|
||||
} else {
|
||||
dbs.mut.Unlock()
|
||||
//vv("gid %v released write-lock on shard %v", curGID(), dbs.Shard)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbs.W.Close()
|
||||
}
|
||||
|
||||
func (dbs *DBShard) NewTx(write bool, initialIndexName string, o Txo) (tx Tx, err error) {
|
||||
|
||||
if dbs.isBlueGreen {
|
||||
// enforce only one writer at a time. The dbs.mut is held until
|
||||
// the Tx finishes. This makes the two Tx in the blue-green Tx atomic.
|
||||
if !dbs.hasRoaring {
|
||||
if write {
|
||||
//vv("shard %v about to write lock by gid %v; stack =\n%v", dbs.Shard, curGID(), stack())
|
||||
dbs.mut.Lock()
|
||||
//vv("shard %v was write locked by gid %v; stack =\n%v", dbs.Shard, curGID(), stack())
|
||||
} else {
|
||||
//vv("shard %v about to be read locked by gid %v; stack=\n%v", dbs.Shard, curGID(), stack())
|
||||
dbs.mut.RLock()
|
||||
//vv("shard %v was read locked by gid %v; stack=\n%v", dbs.Shard, curGID(), stack())
|
||||
}
|
||||
}
|
||||
if initialIndexName != dbs.Index {
|
||||
return nil, fmt.Errorf("NewTx called on DBShard for %q with index %q", dbs.Index, initialIndexName)
|
||||
}
|
||||
if o.dbs != dbs {
|
||||
PanicOn(fmt.Sprintf("TxFactory.NewTx() should have set o.dbs(%p) to equal dbs(%p)", o.dbs, dbs))
|
||||
return nil, fmt.Errorf("dbs mismatch: TxFactory.NewTx() should have set o.dbs(%p) to equal dbs(%p)", o.dbs, dbs)
|
||||
}
|
||||
if o.Shard != dbs.Shard {
|
||||
PanicOn(fmt.Sprintf("shard disagreement! o.Shard='%v' but dbs.Shard='%v'", int(o.Shard), int(dbs.Shard)))
|
||||
return nil, fmt.Errorf("shard disagreement: o.Shard='%v' but dbs.Shard='%v'", int(o.Shard), int(dbs.Shard))
|
||||
}
|
||||
var txns []Tx
|
||||
|
||||
for _, w := range dbs.W {
|
||||
tx, err = w.NewTx(write, initialIndexName, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txns = append(txns, tx)
|
||||
}
|
||||
if len(txns) == 1 {
|
||||
return
|
||||
}
|
||||
// blue green
|
||||
tx, err = dbs.per.txf.newBlueGreenTx(txns[0], txns[1], o.Index, o), nil
|
||||
//vv("dbshard returning blue-green tx sn %v", tx.Sn())
|
||||
return
|
||||
}
|
||||
|
||||
func (dbs *DBShard) DeleteDBPath() (err error) {
|
||||
for _, w := range dbs.W {
|
||||
err = w.DeleteDBPath(dbs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
return dbs.W.NewTx(write, initialIndexName, o)
|
||||
}
|
||||
|
||||
type flatkey struct {
|
||||
|
|
@ -228,34 +144,26 @@ type DBPerShard struct {
|
|||
// Easily see how many we have.
|
||||
Flatmap map[flatkey]*DBShard
|
||||
|
||||
types []txtype
|
||||
typ txtype
|
||||
hasRoaring bool
|
||||
|
||||
txf *TxFactory
|
||||
holder *Holder
|
||||
|
||||
// which of our types is not-roaring, since
|
||||
// roaring doesn't keep a list of open Tx sn.
|
||||
// or default to the 2nd.
|
||||
useOpenList int
|
||||
|
||||
// cache the shards per index to avoid excessive
|
||||
// directory scans of the index directory. Keep per
|
||||
// txtype to allow blue-green migrate open to be fast too.
|
||||
// directory scans of the index directory.
|
||||
// Keep it up-to-date as we add shards to avoid doing
|
||||
// a filesystem rescan on new shard creation.
|
||||
//
|
||||
// txtype -> index -> *shardSet
|
||||
index2shards map[txtype]map[string]*shardSet
|
||||
|
||||
isBlueGreen bool
|
||||
// index -> *shardSet
|
||||
index2shards map[string]*shardSet
|
||||
|
||||
StorageConfig *storage.Config
|
||||
RBFConfig *rbfcfg.Config
|
||||
}
|
||||
|
||||
func newIndex2Shards() (r map[txtype]map[string]*shardSet) {
|
||||
r = make(map[txtype]map[string]*shardSet)
|
||||
func newIndex2Shards() (r map[string]*shardSet) {
|
||||
r = make(map[string]*shardSet)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -350,51 +258,6 @@ func newShardSetFromMap(m map[uint64]bool) *shardSet {
|
|||
}
|
||||
}
|
||||
|
||||
// HasData returns true if the database has at least one key.
|
||||
// For roaring it returns true if we a fragment stored.
|
||||
// The `which` argument is the index into the per.W slice. 0 for blue, 1 for green.
|
||||
// If you pass 1, be sure you have a blue-green configuration.
|
||||
func (per *DBPerShard) HasData(which int) (hasData bool, err error) {
|
||||
// has to aggregate across all available DBShard for each index and shard.
|
||||
|
||||
if per.types[which] == roaringTxn {
|
||||
return per.RoaringHasData() // this needs to be made accurate
|
||||
}
|
||||
|
||||
for _, v := range per.Flatmap {
|
||||
hasData, err = v.W[which].HasData()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hasData {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (per *DBPerShard) RoaringHasData() (bool, error) {
|
||||
idxs := per.holder.Indexes()
|
||||
const requireData = true
|
||||
for _, idx := range idxs {
|
||||
shards, err := per.TypedDBPerShardGetShardsForIndex(roaringTxn, idx, "", requireData)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(shards) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (per *DBPerShard) ListOpenString() (r string) {
|
||||
for _, v := range per.Flatmap {
|
||||
r += v.HolderPath + " -> " + v.W[per.useOpenList].OpenListString() + "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (per *DBPerShard) LoadExistingDBs() (err error) {
|
||||
idxs := per.holder.Indexes()
|
||||
|
||||
|
|
@ -414,37 +277,24 @@ func (per *DBPerShard) LoadExistingDBs() (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (txf *TxFactory) NewDBPerShard(types []txtype, holderDir string, holder *Holder) (d *DBPerShard) {
|
||||
func (txf *TxFactory) NewDBPerShard(typ txtype, holderDir string, holder *Holder) (d *DBPerShard) {
|
||||
if holder.cfg == nil || holder.cfg.RBFConfig == nil || holder.cfg.StorageConfig == nil {
|
||||
PanicOn("must have holder.cfg.RBFConfig and holder.cfg.StorageConfig set here")
|
||||
}
|
||||
|
||||
useOpenList := 0
|
||||
hasRoaring := false
|
||||
if types[0] == roaringTxn {
|
||||
if typ == roaringTxn {
|
||||
hasRoaring = true
|
||||
}
|
||||
if len(types) == 2 {
|
||||
// blue-green, avoid the empty roaring Tx open list.
|
||||
// Prefer B's open list if neither is roaring.
|
||||
if types[0] == roaringTxn || types[1] != roaringTxn {
|
||||
useOpenList = 1
|
||||
}
|
||||
if types[1] == roaringTxn {
|
||||
hasRoaring = true
|
||||
}
|
||||
}
|
||||
|
||||
d = &DBPerShard{
|
||||
types: types,
|
||||
typ: typ,
|
||||
HolderDir: holderDir,
|
||||
holder: holder,
|
||||
dbh: NewDBHolder(),
|
||||
Flatmap: make(map[flatkey]*DBShard),
|
||||
txf: txf,
|
||||
useOpenList: useOpenList,
|
||||
hasRoaring: hasRoaring,
|
||||
isBlueGreen: len(types) > 1,
|
||||
index2shards: newIndex2Shards(),
|
||||
StorageConfig: holder.cfg.StorageConfig,
|
||||
RBFConfig: holder.cfg.RBFConfig,
|
||||
|
|
@ -469,14 +319,12 @@ func (per *DBPerShard) DeleteIndex(index string) (err error) {
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "DBPerShard.DeleteIndex dbs.Close()")
|
||||
}
|
||||
for _, ty := range per.types {
|
||||
path := dbs.pathForType(ty)
|
||||
err = os.RemoveAll(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("DBPerShard.DeleteIndex os.RemoveAll('%v')", path))
|
||||
}
|
||||
delete(per.index2shards[ty], index)
|
||||
path := dbs.pathForType(per.typ)
|
||||
err = os.RemoveAll(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("DBPerShard.DeleteIndex os.RemoveAll('%v')", path))
|
||||
}
|
||||
delete(per.index2shards, index)
|
||||
}
|
||||
|
||||
// allow the index to be created again anew.
|
||||
|
|
@ -502,10 +350,8 @@ func (per *DBPerShard) DeleteFieldFromStore(index, field, fieldPath string) (err
|
|||
return nil
|
||||
}
|
||||
for _, dbs := range dbi.Shard {
|
||||
for _, w := range dbs.W {
|
||||
if e := w.DeleteField(index, field, fieldPath); e != nil && err == nil {
|
||||
err = errors.Wrap(e, "DeleteFieldFromStore()")
|
||||
}
|
||||
if e := dbs.W.DeleteField(index, field, fieldPath); e != nil && err == nil {
|
||||
err = errors.Wrap(e, "DeleteFieldFromStore()")
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
|
@ -521,46 +367,6 @@ func (per *DBPerShard) DeleteFragment(index, field, view string, shard uint64, f
|
|||
return dbs.DeleteFragment(index, field, view, shard, frag)
|
||||
}
|
||||
|
||||
func (dbs *DBShard) DumpAll() {
|
||||
short := false
|
||||
fmt.Printf("\n============= begin DumpAll dbs=%p index='%v', shard=%v ========\n", dbs, dbs.Index, int(dbs.Shard))
|
||||
for i, ty := range dbs.types {
|
||||
_ = i
|
||||
tx, err := dbs.W[i].NewTx(!writable, "", Txo{Index: dbs.idx})
|
||||
PanicOn(err)
|
||||
defer tx.Rollback()
|
||||
fmt.Printf("\n============= dumping dbs.W[%v] %v ========\n", i, ty)
|
||||
tx.Dump(short, dbs.Shard)
|
||||
|
||||
switch ty {
|
||||
case roaringTxn:
|
||||
case rbfTxn:
|
||||
case boltTxn:
|
||||
default:
|
||||
PanicOn(fmt.Sprintf("unknown txtyp: '%v'", ty))
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n============= end of DumpAll index='%v', shard=%v ========\n", dbs.Index, int(dbs.Shard))
|
||||
}
|
||||
|
||||
func (per *DBPerShard) DumpAll() {
|
||||
per.Mu.Lock()
|
||||
defer per.Mu.Unlock()
|
||||
|
||||
found1 := false
|
||||
for _, dbi := range per.dbh.Index {
|
||||
for _, dbs := range dbi.Shard {
|
||||
if dbs.Open {
|
||||
found1 = true
|
||||
dbs.DumpAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found1 {
|
||||
AlwaysPrintf("DBPerShard.DumpAll() sees no databases. dir='%v'", per.HolderDir)
|
||||
}
|
||||
}
|
||||
|
||||
// if you know the shard, you can use this
|
||||
// pathForType and prefixForType must be kept in sync!
|
||||
func (dbs *DBShard) pathForType(ty txtype) string {
|
||||
|
|
@ -570,11 +376,6 @@ func (dbs *DBShard) pathForType(ty txtype) string {
|
|||
// is a no-op anyhow. so doesn't need to be correct atm.
|
||||
|
||||
path := dbs.HolderPath + sep + dbs.Index + sep + backendsDir + sep + ty.DirectoryName() + sep + fmt.Sprintf("shard.%04v", dbs.Shard)
|
||||
if ty == boltTxn {
|
||||
// special case:
|
||||
// bolt doesn't use a directory like the others, just a direct path.
|
||||
path += sep + "bolt.db"
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
|
|
@ -593,23 +394,13 @@ var ErrNoData = fmt.Errorf("no data")
|
|||
//
|
||||
// Caller must hold per.Mu.Lock() already.
|
||||
func (per *DBPerShard) updateIndex2ShardCacheWithNewShard(dbs *DBShard) {
|
||||
|
||||
for _, ty := range dbs.types {
|
||||
mapIndex2shardSet, ok := per.index2shards[ty]
|
||||
if !ok {
|
||||
mapIndex2shardSet = make(map[string]*shardSet)
|
||||
per.index2shards[ty] = mapIndex2shardSet
|
||||
}
|
||||
// INVAR: mapIndex2shardSet is good, but may be an empty map
|
||||
|
||||
shardset, ok := mapIndex2shardSet[dbs.Index]
|
||||
if !ok {
|
||||
shardset = newShardSet()
|
||||
mapIndex2shardSet[dbs.Index] = shardset
|
||||
}
|
||||
// INVAR: shardset is present, not nil; a map that can be added to.
|
||||
shardset.add(dbs.Shard)
|
||||
shardset, ok := per.index2shards[dbs.Index]
|
||||
if !ok {
|
||||
shardset = newShardSet()
|
||||
per.index2shards[dbs.Index] = shardset
|
||||
}
|
||||
// INVAR: shardset is present, not nil; a map that can be added to.
|
||||
shardset.add(dbs.Shard)
|
||||
}
|
||||
|
||||
func (per *DBPerShard) GetDBShard(index string, shard uint64, idx *Index) (dbs *DBShard, err error) {
|
||||
|
|
@ -629,76 +420,47 @@ func (per *DBPerShard) unprotectedGetDBShard(index string, shard uint64, idx *In
|
|||
}
|
||||
dbs, ok = dbi.Shard[shard]
|
||||
if dbs != nil && dbs.closed {
|
||||
if len(per.types) == 1 && per.types[0] == roaringTxn {
|
||||
// roaring txn are nil/fake anyway. Don't freak out.
|
||||
} else {
|
||||
PanicOn(fmt.Sprintf("cannot retain closed dbs across holder ReOpen dbs='%p'; per.types[0]='%v'; len(per.types)=%v", dbs, per.types[0], len(per.types)))
|
||||
// roaring txn are nil/fake anyway. Don't freak out.
|
||||
if per.typ != roaringTxn {
|
||||
PanicOn(fmt.Sprintf("cannot retain closed dbs across holder ReOpen dbs='%p'; per.typ='%v'", dbs, per.typ))
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
dbs = &DBShard{
|
||||
types: per.types,
|
||||
typ: per.typ,
|
||||
ParentDBIndex: dbi,
|
||||
Index: index,
|
||||
Shard: shard,
|
||||
HolderPath: per.HolderDir,
|
||||
idx: idx,
|
||||
per: per,
|
||||
useOpenList: per.useOpenList,
|
||||
hasRoaring: per.hasRoaring,
|
||||
isBlueGreen: len(per.types) > 1,
|
||||
}
|
||||
dbs.stypes = make([]string, len(per.types))
|
||||
for i, ty := range per.types {
|
||||
dbs.stypes[i] = ty.String()
|
||||
}
|
||||
|
||||
dbs.styp = per.typ.String()
|
||||
dbi.Shard[shard] = dbs
|
||||
per.updateIndex2ShardCacheWithNewShard(dbs)
|
||||
}
|
||||
if !dbs.Open {
|
||||
var registry DBRegistry
|
||||
for _, ty := range dbs.types {
|
||||
switch ty {
|
||||
case roaringTxn:
|
||||
registry = globalRoaringReg
|
||||
case rbfTxn:
|
||||
registry = globalRbfDBReg
|
||||
registry.(*rbfDBRegistrar).SetRBFConfig(per.RBFConfig)
|
||||
case boltTxn:
|
||||
registry = globalBoltReg
|
||||
default:
|
||||
PanicOn(fmt.Sprintf("unknown txtyp: '%v'", ty))
|
||||
}
|
||||
path := dbs.pathForType(ty)
|
||||
w, err := registry.OpenDBWrapper(path, DetectMemAccessPastTx, per.StorageConfig)
|
||||
PanicOn(err)
|
||||
h := idx.Holder()
|
||||
w.SetHolder(h)
|
||||
dbs.Open = true
|
||||
if w != nil && len(dbs.W) == 0 {
|
||||
per.Flatmap[flatkey{index: index, shard: shard}] = dbs
|
||||
}
|
||||
dbs.W = append(dbs.W, w)
|
||||
switch dbs.typ {
|
||||
case roaringTxn:
|
||||
registry = globalRoaringReg
|
||||
case rbfTxn:
|
||||
registry = globalRbfDBReg
|
||||
registry.(*rbfDBRegistrar).SetRBFConfig(per.RBFConfig)
|
||||
default:
|
||||
PanicOn(fmt.Sprintf("unknown txtyp: '%v'", dbs.typ))
|
||||
}
|
||||
path := dbs.pathForType(dbs.typ)
|
||||
w, err := registry.OpenDBWrapper(path, DetectMemAccessPastTx, per.StorageConfig)
|
||||
PanicOn(err)
|
||||
h := idx.Holder()
|
||||
w.SetHolder(h)
|
||||
dbs.Open = true
|
||||
per.Flatmap[flatkey{index: index, shard: shard}] = dbs
|
||||
dbs.W = w
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (per *DBPerShard) Del(dbs *DBShard) (err error) {
|
||||
per.Mu.Lock()
|
||||
defer per.Mu.Unlock()
|
||||
|
||||
err = dbs.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
PanicOn(dbs.DeleteDBPath())
|
||||
delete(per.Flatmap, flatkey{index: dbs.Index, shard: dbs.Shard})
|
||||
|
||||
// delete from the heirarchy
|
||||
delete(dbs.ParentDBIndex.Shard, dbs.Shard)
|
||||
return nil
|
||||
return dbs, nil
|
||||
}
|
||||
|
||||
func (per *DBPerShard) Close() (err error) {
|
||||
|
|
@ -718,28 +480,7 @@ func (per *DBPerShard) Close() (err error) {
|
|||
// If requireData, we open the database and see that it has a key, rather
|
||||
// than assume that the database file presence is enough.
|
||||
func (f *TxFactory) GetShardsForIndex(idx *Index, roaringViewPath string, requireData bool) (map[uint64]bool, error) {
|
||||
|
||||
n := len(f.types)
|
||||
if n != 1 && n != 2 {
|
||||
PanicOn(fmt.Sprintf("internal error. only green or blue/green supported. we see types len %v", n))
|
||||
}
|
||||
|
||||
var shards []map[uint64]bool
|
||||
for _, ty := range f.types {
|
||||
ss, err := f.dbPerShard.TypedDBPerShardGetShardsForIndex(ty, idx, roaringViewPath, requireData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shards = append(shards, ss)
|
||||
}
|
||||
|
||||
// Note: we don't actually know when the blue call and when the green call comes
|
||||
// through here. So if we are deleting a shard, we will see a difference earlier
|
||||
// in one than the other. TestAPI_ClearFlagForImportAndImportValues for example.
|
||||
// Hence we cannot do a blue-green check here for matching shards.
|
||||
|
||||
// If we are populating blue from green, it does matter that we return green.
|
||||
return shards[n-1], nil
|
||||
return f.dbPerShard.TypedDBPerShardGetShardsForIndex(f.typ, idx, roaringViewPath, requireData)
|
||||
}
|
||||
|
||||
// if roaringViewPath is "" then for ty == roaringTxn we go to disk to discover
|
||||
|
|
@ -751,10 +492,6 @@ func (f *TxFactory) GetShardsForIndex(idx *Index, roaringViewPath string, requir
|
|||
// when a new DBShard is made, we will update the list of shards then. Thus
|
||||
// the per.index2shard should always be up to date AFTER the first call here.
|
||||
//
|
||||
// Note: we cannot here call GetView2ShardsMapForIndex() because that only ever
|
||||
// returns the green data and we are used during migration for both blue
|
||||
// and green.
|
||||
//
|
||||
func (per *DBPerShard) TypedDBPerShardGetShardsForIndex(ty txtype, idx *Index, roaringViewPath string, requireData bool) (shardMap map[uint64]bool, err error) {
|
||||
|
||||
// use the cache, always
|
||||
|
|
@ -769,13 +506,7 @@ func (per *DBPerShard) TypedDBPerShardGetShardsForIndex(ty txtype, idx *Index, r
|
|||
return shardMap, nil
|
||||
}
|
||||
|
||||
i2ss, ok := per.index2shards[ty]
|
||||
if !ok {
|
||||
// index -> shardSet
|
||||
i2ss = make(map[string]*shardSet)
|
||||
per.index2shards[ty] = i2ss
|
||||
}
|
||||
// INVAR: i2ss is good, but may be an empty map
|
||||
i2ss := per.index2shards
|
||||
|
||||
ss, ok := i2ss[idx.name]
|
||||
if ok {
|
||||
|
|
@ -785,7 +516,7 @@ func (per *DBPerShard) TypedDBPerShardGetShardsForIndex(ty txtype, idx *Index, r
|
|||
|
||||
// gotta read shards from disk directory layout.
|
||||
setOfShards := newShardSet()
|
||||
per.index2shards[ty][idx.name] = setOfShards
|
||||
per.index2shards[idx.name] = setOfShards
|
||||
|
||||
// Upon return, cache the setOfShards value and reuse it next time
|
||||
|
||||
|
|
@ -854,13 +585,7 @@ func (per *DBPerShard) TypedDBPerShardGetShardsForIndex(ty txtype, idx *Index, r
|
|||
}
|
||||
|
||||
func (per *DBPerShard) unprotectedTypedIndexShardHasData(ty txtype, idx *Index, shard uint64) (hasData bool, err error) {
|
||||
whichty := 0
|
||||
if len(per.types) == 2 {
|
||||
if ty == per.types[1] {
|
||||
whichty = 1
|
||||
}
|
||||
}
|
||||
if ty != per.types[whichty] {
|
||||
if ty != per.typ {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -871,7 +596,7 @@ func (per *DBPerShard) unprotectedTypedIndexShardHasData(ty txtype, idx *Index,
|
|||
"per.GetDBShard(index='%v', shard='%v', ty='%v')", idx.name, shard, ty.String()))
|
||||
}
|
||||
|
||||
return dbs.W[whichty].HasData()
|
||||
return dbs.W.HasData()
|
||||
}
|
||||
|
||||
func listDirUnderDir(root string, includeRoot bool, ignoreEmpty bool) (files []string, err error) {
|
||||
|
|
@ -906,224 +631,6 @@ func listDirUnderDir(root string, includeRoot bool, ignoreEmpty bool) (files []s
|
|||
return
|
||||
}
|
||||
|
||||
// populateBlueFromGreen prepares for a blue_green run at startup time.
|
||||
//
|
||||
// It is called at the end of Holder.Open(). This allows the application
|
||||
// of blue-green checking to pilosa instances that
|
||||
// were previously run only with a single (solo) backend.
|
||||
//
|
||||
// PRE: This operation requires, at its start, either:
|
||||
//
|
||||
// (1) an empty blue database -- this allows transitioning from
|
||||
// a solo database to blue_green checking where the solo
|
||||
// becomes the green; or
|
||||
//
|
||||
// (2) that the blue data, if present, be logically
|
||||
// identical to the green data -- this allows one to restart
|
||||
// a pilosa that was already running in blue_green mode
|
||||
// and remain in blue_green mode.
|
||||
//
|
||||
// In either case, the goal to to finish populateBlueFromGreen()
|
||||
// and have the exact same logical set of data in both backends.
|
||||
//
|
||||
// Why must the data be identical after Holder.Open() finishes?
|
||||
// Otherwise subsequent blue-green checks have no hope of
|
||||
// being accurate.
|
||||
//
|
||||
// The blue is the destination -- this is always types[0].
|
||||
// The green source is always types[1]. The mnemonic is blue_geen.
|
||||
// The blue is first, so it is in types[0]. The green
|
||||
// is second, in types[1]. For example, with PILOSA_STORAGE_BACKEND=bolt_roaring
|
||||
// we have bolt as blue, and roaring as green. The contents of
|
||||
// bolt must be empty or exactly match roaring. If bolt
|
||||
// starts empty, it will be populated from roaring by
|
||||
// populateBlueFromGreen().
|
||||
//
|
||||
func (dbs *DBShard) populateBlueFromGreen() (err error) {
|
||||
|
||||
n := len(dbs.W)
|
||||
if n != 2 {
|
||||
PanicOn(fmt.Sprintf("populateBlueFromGreen did not find 2 open DBs: have %v", n))
|
||||
}
|
||||
|
||||
dest := dbs.W[0] // blue
|
||||
src := dbs.W[1] // green
|
||||
|
||||
// copy all the key/container pairs.
|
||||
// Since a shard is fairly small, we think one Tx will suffice.
|
||||
|
||||
readtx, err := src.NewTx(!writable, dbs.Index, Txo{Write: !writable, Index: dbs.idx, Shard: dbs.Shard})
|
||||
PanicOn(err)
|
||||
defer readtx.Rollback()
|
||||
|
||||
writetx, err := dest.NewTx(writable, dbs.Index, Txo{Write: writable, Index: dbs.idx, Shard: dbs.Shard})
|
||||
PanicOn(err)
|
||||
defer writetx.Rollback()
|
||||
|
||||
ctWriteCount := 0
|
||||
|
||||
for _, fld := range dbs.idx.Fields() {
|
||||
field := fld.Name()
|
||||
for _, vw := range fld.views() {
|
||||
view := vw.name
|
||||
citer, _, err := readtx.ContainerIterator(dbs.Index, field, view, dbs.Shard, 0)
|
||||
if err != nil {
|
||||
// might be an empty fragment. If so, let's not freak out.
|
||||
if strings.Contains(err.Error(), "fragment not found") {
|
||||
continue
|
||||
} else {
|
||||
writetx.Rollback()
|
||||
return errors.Wrap(err, "DBShard.populateBlueFromGreen readtx.ContainerIterator")
|
||||
}
|
||||
}
|
||||
|
||||
for citer.Next() {
|
||||
ckey, rc := citer.Value()
|
||||
err := writetx.PutContainer(dbs.Index, field, view, dbs.Shard, ckey, rc)
|
||||
if err != nil {
|
||||
citer.Close()
|
||||
writetx.Rollback()
|
||||
return errors.Wrap(err, "DBShard.populateBlueFromGreen writetx.PutContainer")
|
||||
}
|
||||
|
||||
ctWriteCount++
|
||||
if ctWriteCount%1000 == 1 {
|
||||
|
||||
// regularly commiting smaller batches and the first batch as soon as
|
||||
// possible massively speeds up writing to bolt.
|
||||
//
|
||||
// reference: https://github.com/boltdb/bolt/issues/94
|
||||
//
|
||||
// benbjohnson commented on Mar 25, 2014
|
||||
// "Bulk loading more than 1000 items at a time is very slow. This is because nodes
|
||||
// are not splitting before commit which causes large memmove() operations during insertion."
|
||||
// runtime.memmove is taking all of the time in our pprof profile, when copying rbf to bolt, so we suspect it is this.
|
||||
//
|
||||
err = writetx.Commit()
|
||||
if err != nil {
|
||||
citer.Close()
|
||||
writetx.Rollback()
|
||||
return errors.Wrap(err, "DBShard.populateBlueFromGreen writetx.Commit")
|
||||
}
|
||||
writetx, err = dest.NewTx(writable, dbs.Index, Txo{Write: writable, Index: dbs.idx, Shard: dbs.Shard})
|
||||
if err != nil {
|
||||
citer.Close()
|
||||
writetx.Rollback()
|
||||
return errors.Wrap(err, "DBShard.populateBlueFromGreen writetx.NewTx inside citer.Next() loop")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
citer.Close()
|
||||
}
|
||||
}
|
||||
err = writetx.Commit()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writetx.Commit()")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyBlueEqualsGreen checks that blue and green are identical.
|
||||
func (dbs *DBShard) verifyBlueEqualsGreen() (err error) {
|
||||
|
||||
n := len(dbs.W)
|
||||
if n != 2 {
|
||||
PanicOn(fmt.Sprintf("verifyBlueEqualsGreen did not find 2 open DBs: have %v", n))
|
||||
}
|
||||
|
||||
blue := dbs.W[0]
|
||||
green := dbs.W[1]
|
||||
|
||||
greentx, err := green.NewTx(!writable, dbs.Index, Txo{Write: !writable, Index: dbs.idx, Shard: dbs.Shard})
|
||||
PanicOn(err)
|
||||
defer greentx.Rollback()
|
||||
|
||||
bluetx, err := blue.NewTx(!writable, dbs.Index, Txo{Write: !writable, Index: dbs.idx, Shard: dbs.Shard})
|
||||
PanicOn(err)
|
||||
defer bluetx.Rollback()
|
||||
|
||||
for _, fld := range dbs.idx.Fields() {
|
||||
field := fld.Name()
|
||||
for _, vw := range fld.views() {
|
||||
|
||||
view := vw.name
|
||||
gCiter, _, err := greentx.ContainerIterator(dbs.Index, field, view, dbs.Shard, 0)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "fragment not found") {
|
||||
continue
|
||||
} else {
|
||||
return errors.Wrap(err, "DBShard.verifyBlueEqualsGreen greentx.ContainerIterator")
|
||||
}
|
||||
}
|
||||
|
||||
bCiter, _, err := bluetx.ContainerIterator(dbs.Index, field, view, dbs.Shard, 0)
|
||||
if err != nil {
|
||||
gCiter.Close()
|
||||
if bCiter != nil {
|
||||
bCiter.Close()
|
||||
}
|
||||
return errors.Wrap(err, "DBShard.verifyBlueEqualsGreen bluetx.ContainerIterator")
|
||||
}
|
||||
|
||||
for gCiter.Next() {
|
||||
greenCkey, greenc := gCiter.Value()
|
||||
|
||||
if !bCiter.Next() {
|
||||
bCiter.Close()
|
||||
gCiter.Close()
|
||||
return errors.Wrap(err, fmt.Sprintf("DBShard.verifyBlueEqualsGreen "+
|
||||
"sees missing blue container at index: '%v' field: '%v' view: '%v' "+
|
||||
"shard: '%v' the greenCkey: '%v'",
|
||||
dbs.Index, field, view, dbs.Shard, greenCkey))
|
||||
}
|
||||
blueCkey, bluec := bCiter.Value()
|
||||
|
||||
if blueCkey != greenCkey {
|
||||
bCiter.Close()
|
||||
gCiter.Close()
|
||||
return fmt.Errorf("DBShard.verifyBlueEqualsGreen sees sequence-of-ckey "+
|
||||
"difference: blueCkey %v not equal to greenCkey %v at index: '%v' field: '%v' view: '%v' "+
|
||||
"shard: '%v'",
|
||||
blueCkey, greenCkey, dbs.Index, field, view, dbs.Shard)
|
||||
}
|
||||
nGreen := greenc.N()
|
||||
nBlue := bluec.N()
|
||||
if nBlue != nGreen {
|
||||
bCiter.Close()
|
||||
gCiter.Close()
|
||||
return errors.Wrap(err, fmt.Sprintf("DBShard.verifyBlueEqualsGreen "+
|
||||
"sees variation in blue at index: '%v' field: '%v' view: '%v' "+
|
||||
"shard: '%v' ckey: '%v' nHotGreen= %v nHotBlue= %v",
|
||||
dbs.Index, field, view, dbs.Shard, greenCkey, nGreen, nBlue))
|
||||
}
|
||||
err = bluec.BitwiseCompare(greenc)
|
||||
if err != nil {
|
||||
bCiter.Close()
|
||||
gCiter.Close()
|
||||
return errors.Wrap(err, fmt.Sprintf("DBShard.verifyBlueEqualsGreen "+
|
||||
"sees variation in blue at index: '%v' field: '%v' view: '%v' "+
|
||||
"shard: '%v' ckey: '%v' nHotGreen= %v nHotBlue= %v ; BitwiseCompare response: '%v'",
|
||||
dbs.Index, field, view, dbs.Shard, greenCkey, nGreen, nBlue, err))
|
||||
}
|
||||
}
|
||||
if bCiter.Next() {
|
||||
blueCkey, _ := bCiter.Value()
|
||||
bCiter.Close()
|
||||
gCiter.Close()
|
||||
return errors.Wrap(err, fmt.Sprintf("DBShard.verifyBlueEqualsGreen "+
|
||||
"sees extra blue container (not present in green) at index: '%v' field: '%v' view: '%v' "+
|
||||
"shard: '%v' the ckey: '%v'",
|
||||
dbs.Index, field, view, dbs.Shard, blueCkey))
|
||||
}
|
||||
bCiter.Close()
|
||||
gCiter.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type FieldView2Shards struct {
|
||||
// field -> view -> *shardSet
|
||||
m map[string]map[string]*shardSet
|
||||
|
|
@ -1232,15 +739,8 @@ func (vs *FieldView2Shards) removeField(name string) {
|
|||
delete(vs.m, name)
|
||||
}
|
||||
|
||||
// Note: cannot call this during migration, because
|
||||
// it only ever returns the green shards if we are in blue-green.
|
||||
func (per *DBPerShard) GetFieldView2ShardsMapForIndex(idx *Index) (vs *FieldView2Shards, err error) {
|
||||
|
||||
// for blue-green, it does matter that we return green, so we can migrate from it.
|
||||
ty := per.types[0]
|
||||
if per.isBlueGreen {
|
||||
ty = per.types[1]
|
||||
}
|
||||
ty := per.typ
|
||||
|
||||
switch ty {
|
||||
case roaringTxn:
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ func Test_DBPerShard_GetShardsForIndex_LocalOnly(t *testing.T) {
|
|||
v2s.addViewShardSet(txkey.FieldView{Field: field, View: "standard"}, stdShardSet)
|
||||
}
|
||||
|
||||
for _, src := range []string{"roaring", "bolt", "rbf"} {
|
||||
for _, src := range []string{"roaring", "rbf"} {
|
||||
cfg := mustHolderConfig()
|
||||
cfg.StorageConfig.Backend = src
|
||||
holder := NewHolder(tmpdir, cfg)
|
||||
|
|
@ -145,7 +145,7 @@ func Test_DBPerShard_GetShardsForIndex_LocalOnly(t *testing.T) {
|
|||
tx.Rollback()
|
||||
}
|
||||
} else {
|
||||
// non-roaring: rbf, bolt
|
||||
// non-roaring: rbf
|
||||
|
||||
for _, shard := range []uint64{93, 223, 221, 215, 219, 217} {
|
||||
tx := idx.holder.txf.NewTx(Txo{Write: !writable, Index: idx, Shard: shard})
|
||||
|
|
@ -191,14 +191,6 @@ rick/fields/_exists/views/standard/fragments/217
|
|||
rick/fields/_exists/views/standard/fragments/93
|
||||
rick/fields/_exists/views/standard/fragments/219
|
||||
rick/fields/_exists/views/standard/fragments/223
|
||||
`,
|
||||
"bolt": `
|
||||
rick/backends/backend-boltdb/shard.0093-bolt/bolt.db
|
||||
rick/backends/backend-boltdb/shard.0215-bolt/bolt.db
|
||||
rick/backends/backend-boltdb/shard.0217-bolt/bolt.db
|
||||
rick/backends/backend-boltdb/shard.0219-bolt/bolt.db
|
||||
rick/backends/backend-boltdb/shard.0221-bolt/bolt.db
|
||||
rick/backends/backend-boltdb/shard.0223-bolt/bolt.db
|
||||
`,
|
||||
"rbf": `
|
||||
rick/backends/backend-rbf/shard.0093-rbf
|
||||
|
|
@ -225,7 +217,7 @@ func makeSampleRoaringDir(t *testing.T, root, index, backend string, minBytes in
|
|||
}
|
||||
var shard uint64
|
||||
switch backend {
|
||||
case "bolt", "rbf":
|
||||
case "rbf":
|
||||
shard = shards[i]
|
||||
|
||||
idx = helperCreateDBShard(h, index, shard)
|
||||
|
|
@ -266,16 +258,8 @@ func helperCreateDBShard(h *Holder, index string, shard uint64) *Index {
|
|||
}
|
||||
|
||||
// keep the ocd linter happy
|
||||
var _ = makeBolttestDB
|
||||
var _ = makeRBFtestDB
|
||||
|
||||
func makeBolttestDB(path string, h *Holder, shard uint64) {
|
||||
i := uint64(1)
|
||||
w, _ := mustOpenEmptyBoltWrapper(path)
|
||||
BoltMustSetBitvalue(w, "index", "field", "view", shard, i)
|
||||
w.Close()
|
||||
}
|
||||
|
||||
func makeRBFtestDB(path string, h *Holder, shard uint64) {
|
||||
i := uint64(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import (
|
|||
)
|
||||
|
||||
func TestExecutor_DeleteRecords(t *testing.T) {
|
||||
pilosa.NotBlueGreenTest(t)
|
||||
indexName := "i"
|
||||
setup := func(t *testing.T, r *require.Assertions, c *test.Cluster) {
|
||||
t.Helper()
|
||||
|
|
|
|||
|
|
@ -3843,7 +3843,6 @@ func TestExecutor_Execute_Existence(t *testing.T) {
|
|||
hldr2 := c.GetHolder(0)
|
||||
index2 := hldr2.Index("i")
|
||||
_ = index2
|
||||
//index2.Dump("after reopen")
|
||||
|
||||
if res, err := c.GetNode(0).API.Query(context.Background(), &pilosa.QueryRequest{Index: "i", Query: `Not(Row(f=10))`}); err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
91
fragment.go
91
fragment.go
|
|
@ -47,6 +47,7 @@ import (
|
|||
"github.com/molecula/featurebase/v2/roaring"
|
||||
"github.com/molecula/featurebase/v2/shardwidth"
|
||||
"github.com/molecula/featurebase/v2/stats"
|
||||
"github.com/molecula/featurebase/v2/storage"
|
||||
"github.com/molecula/featurebase/v2/testhook"
|
||||
"github.com/molecula/featurebase/v2/topology"
|
||||
"github.com/molecula/featurebase/v2/tracing"
|
||||
|
|
@ -75,9 +76,6 @@ const (
|
|||
// snapshotExt is the file extension used for an in-process snapshot.
|
||||
snapshotExt = ".snapshotting"
|
||||
|
||||
// copyExt is the file extension used for the temp file used while copying.
|
||||
copyExt = ".copying"
|
||||
|
||||
// cacheExt is the file extension for persisted cache ids.
|
||||
cacheExt = ".cache"
|
||||
|
||||
|
|
@ -441,7 +439,7 @@ func (f *fragment) inspectStorage(data []byte, file *os.File, newGen generation,
|
|||
// (remapping an existing bitmap to match a new backing store).
|
||||
func (f *fragment) openStorage(unmarshalData bool) error {
|
||||
|
||||
useRowCache := f.idx.Txf().UseRowCache()
|
||||
useRowCache := storage.RowCacheEnabled()
|
||||
if !f.idx.NeedsSnapshot() {
|
||||
f.gen = &NopGeneration{}
|
||||
if useRowCache {
|
||||
|
|
@ -626,7 +624,7 @@ func (f *fragment) mustRow(tx Tx, rowID uint64) *Row {
|
|||
// (updating the cache).
|
||||
func (f *fragment) unprotectedRow(tx Tx, rowID uint64) (*Row, error) {
|
||||
|
||||
useRowCache := tx.UseRowCache()
|
||||
useRowCache := storage.RowCacheEnabled()
|
||||
if useRowCache {
|
||||
if f.rowCache == nil {
|
||||
f.rowCache = newSimpleCache()
|
||||
|
|
@ -699,7 +697,7 @@ func (f *fragment) setBit(tx Tx, rowID, columnID uint64) (changed bool, err erro
|
|||
if tx.Type() == RoaringTxn {
|
||||
return changed, errors.New("internal error: f.gen was nil and tx.Type is RoaringTxn - should never happen under roaring b/c storage should be open")
|
||||
}
|
||||
// else blue green or transactional backend. Just do it.
|
||||
// else transactional backend. Just do it.
|
||||
err = doSetFunc()
|
||||
}
|
||||
return changed, err
|
||||
|
|
@ -744,7 +742,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.
|
||||
tx.IncrementOpN(f.index(), f.field(), f.view(), f.shard, 1)
|
||||
f.incrementOpN(1)
|
||||
|
||||
// If we're using a cache, update it. Otherwise skip the
|
||||
// possibly-expensive count operation.
|
||||
|
|
@ -757,7 +755,7 @@ func (f *fragment) unprotectedSetBit(tx Tx, rowID, columnID uint64) (changed boo
|
|||
}
|
||||
// Drop the rowCache entry; it's wrong, and we don't want to force
|
||||
// a new copy if no one's reading it.
|
||||
if tx.UseRowCache() && f.rowCache != nil {
|
||||
if storage.RowCacheEnabled() && f.rowCache != nil {
|
||||
f.rowCache.Add(rowID, nil)
|
||||
}
|
||||
|
||||
|
|
@ -809,7 +807,7 @@ func (f *fragment) unprotectedClearBit(tx Tx, rowID, columnID uint64) (changed b
|
|||
delete(f.checksums, int(rowID/HashBlockSize))
|
||||
|
||||
// Increment number of operations until snapshot is required.
|
||||
tx.IncrementOpN(f.index(), f.field(), f.view(), f.shard, 1)
|
||||
f.incrementOpN(1)
|
||||
|
||||
// If we're using a cache, update it. Otherwise skip the
|
||||
// possibly-expensive count operation.
|
||||
|
|
@ -822,7 +820,7 @@ func (f *fragment) unprotectedClearBit(tx Tx, rowID, columnID uint64) (changed b
|
|||
}
|
||||
// Drop the rowCache entry; it's wrong, and we don't want to force
|
||||
// a new copy if no one's reading it.
|
||||
if tx.UseRowCache() && f.rowCache != nil {
|
||||
if storage.RowCacheEnabled() && f.rowCache != nil {
|
||||
f.rowCache.Add(rowID, nil)
|
||||
}
|
||||
|
||||
|
|
@ -891,7 +889,7 @@ func (f *fragment) unprotectedSetRow(tx Tx, row *Row, rowID uint64) (changed boo
|
|||
}
|
||||
|
||||
// invalidate rowCache for this row.
|
||||
if tx.UseRowCache() && f.rowCache != nil {
|
||||
if storage.RowCacheEnabled() && f.rowCache != nil {
|
||||
f.rowCache.Add(rowID, nil)
|
||||
}
|
||||
|
||||
|
|
@ -942,7 +940,7 @@ func (f *fragment) unprotectedClearRow(tx Tx, rowID uint64) (changed bool, err e
|
|||
|
||||
// Clear the row in cache.
|
||||
f.cache.Add(rowID, 0)
|
||||
if tx.UseRowCache() && f.rowCache != nil {
|
||||
if storage.RowCacheEnabled() && f.rowCache != nil {
|
||||
f.rowCache.Add(rowID, nil)
|
||||
}
|
||||
|
||||
|
|
@ -2426,7 +2424,7 @@ func (f *fragment) importPositions(tx Tx, set, clear []uint64, rowSet map[uint64
|
|||
if f.storage != nil {
|
||||
wp = &f.storage.OpWriter
|
||||
}
|
||||
useRowCache := tx.UseRowCache()
|
||||
useRowCache := storage.RowCacheEnabled()
|
||||
|
||||
doFunc := func() error {
|
||||
if len(set) > 0 {
|
||||
|
|
@ -2438,7 +2436,7 @@ func (f *fragment) importPositions(tx Tx, set, clear []uint64, rowSet map[uint64
|
|||
return errors.Wrap(err, "adding positions")
|
||||
}
|
||||
f.stats.Count(MetricImportedN, int64(changedN), 1)
|
||||
tx.IncrementOpN(f.index(), f.field(), f.view(), f.shard, changedN)
|
||||
f.incrementOpN(changedN)
|
||||
}
|
||||
|
||||
if len(clear) > 0 {
|
||||
|
|
@ -2448,7 +2446,7 @@ func (f *fragment) importPositions(tx Tx, set, clear []uint64, rowSet map[uint64
|
|||
return errors.Wrap(err, "clearing positions")
|
||||
}
|
||||
f.stats.Count(MetricClearedN, int64(changedN), 1)
|
||||
tx.IncrementOpN(f.index(), f.field(), f.view(), f.shard, changedN)
|
||||
f.incrementOpN(changedN)
|
||||
}
|
||||
|
||||
// Update cache counts for all affected rows.
|
||||
|
|
@ -2735,7 +2733,7 @@ func (f *fragment) unprotectedImportRoaring(ctx context.Context, tx Tx, data []b
|
|||
rowSize := uint64(1 << shardVsContainerExponent)
|
||||
span, ctx := tracing.StartSpanFromContext(ctx, "importRoaring.ImportRoaringBits")
|
||||
|
||||
useRowCache := tx.UseRowCache()
|
||||
useRowCache := storage.RowCacheEnabled()
|
||||
var changed int
|
||||
var rowSet map[uint64]int
|
||||
var wp *io.Writer
|
||||
|
|
@ -2749,7 +2747,7 @@ func (f *fragment) unprotectedImportRoaring(ctx context.Context, tx Tx, data []b
|
|||
return err
|
||||
}
|
||||
|
||||
changed, rowSet, err = tx.ImportRoaringBits(f.index(), f.field(), f.view(), f.shard, rit, clear, true, rowSize, nil)
|
||||
changed, rowSet, err = tx.ImportRoaringBits(f.index(), f.field(), f.view(), f.shard, rit, clear, true, rowSize)
|
||||
return err
|
||||
})
|
||||
|
||||
|
|
@ -2789,7 +2787,7 @@ func (f *fragment) unprotectedImportRoaring(ctx context.Context, tx Tx, data []b
|
|||
|
||||
span, _ = tracing.StartSpanFromContext(ctx, "importRoaring.incrementOpN")
|
||||
|
||||
tx.IncrementOpN(f.index(), f.field(), f.view(), f.shard, changed)
|
||||
f.incrementOpN(changed)
|
||||
|
||||
span.Finish()
|
||||
return nil
|
||||
|
|
@ -2815,6 +2813,10 @@ func (f *fragment) incrementOpN(changed int) {
|
|||
if changed <= 0 {
|
||||
return
|
||||
}
|
||||
// don't count opN or ops if our index doesn't want snapshots
|
||||
if !f.idx.NeedsSnapshot() {
|
||||
return
|
||||
}
|
||||
f.opN += changed
|
||||
f.ops++
|
||||
if f.opN > f.MaxOpN {
|
||||
|
|
@ -3015,11 +3017,15 @@ func (f *fragment) writeStorageToArchive(tw *tar.Writer) error {
|
|||
|
||||
tx := f.idx.holder.txf.NewTx(Txo{Write: !writable, Index: f.idx, Shard: f.shard})
|
||||
defer tx.Rollback()
|
||||
file, sz, err := tx.RoaringBitmapReader(f.index(), f.field(), f.view(), f.shard, f.path())
|
||||
rbm, err := tx.RoaringBitmap(f.index(), f.field(), f.view(), f.shard)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "RoaringBitmapReader RoaringBitmap")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
sz, err := rbm.WriteTo(&buf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "RoaringBitmapReader rbm.WriteTo(buf)")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Write archive header.
|
||||
if err := tw.WriteHeader(&tar.Header{
|
||||
|
|
@ -3033,7 +3039,7 @@ func (f *fragment) writeStorageToArchive(tw *tar.Writer) error {
|
|||
|
||||
// Copy the file up to the last known size.
|
||||
// This is done outside the lock because the storage format is append-only.
|
||||
if _, err := io.CopyN(tw, file, sz); err != nil {
|
||||
if _, err := io.CopyN(tw, &buf, sz); err != nil {
|
||||
return errors.Wrap(err, "copying")
|
||||
}
|
||||
return nil
|
||||
|
|
@ -3086,8 +3092,7 @@ func (f *fragment) ReadFrom(r io.Reader) (n int64, err error) {
|
|||
// Process file based on file name.
|
||||
switch hdr.Name {
|
||||
case "data":
|
||||
idx := f.holder.Index(f.index())
|
||||
tx := idx.holder.txf.NewTx(Txo{Write: writable, Index: idx, Fragment: f, Shard: f.shard})
|
||||
tx := f.holder.txf.NewTx(Txo{Write: writable, Index: f.idx, Fragment: f, Shard: f.shard})
|
||||
defer tx.Rollback()
|
||||
if err := f.fillFragmentFromArchive(tx, tr); err != nil {
|
||||
return 0, errors.Wrap(err, "reading storage")
|
||||
|
|
@ -3131,7 +3136,7 @@ func (f *fragment) fillFragmentFromArchive(tx Tx, r io.Reader) error {
|
|||
if err != nil {
|
||||
return errors.Wrap(err, "fillFragmentFromArchive NewRoaringIterator")
|
||||
}
|
||||
changed, rowSet, err := tx.ImportRoaringBits(f.index(), f.field(), f.view(), f.shard, itr, clear, log, rowSize, data)
|
||||
changed, rowSet, err := tx.ImportRoaringBits(f.index(), f.field(), f.view(), f.shard, itr, clear, log, rowSize)
|
||||
_, _ = changed, rowSet
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fillFragmentFromArchive ImportRoaringBits")
|
||||
|
|
@ -3139,40 +3144,6 @@ func (f *fragment) fillFragmentFromArchive(tx Tx, r io.Reader) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *fragment) readStorageFromArchive(r io.Reader) error {
|
||||
|
||||
// Create a temporary file to copy into.
|
||||
path := f.path() + copyExt
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating directory")
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Copy reader into temporary path.
|
||||
if _, err = io.Copy(file, r); err != nil {
|
||||
return errors.Wrap(err, "copying")
|
||||
}
|
||||
|
||||
// TODO(jea): isn't this next Rename a file handle leak?
|
||||
// try closing first
|
||||
if err := f.closeStorage(); err != nil {
|
||||
return errors.Wrap(err, "closeStorage-prior-to-Rename-and-openStorage")
|
||||
}
|
||||
|
||||
// Move snapshot to data file location.
|
||||
if err := os.Rename(path, f.path()); err != nil {
|
||||
return errors.Wrap(err, "renaming")
|
||||
}
|
||||
|
||||
// Reopen storage.
|
||||
if err := f.openStorage(true); err != nil {
|
||||
return errors.Wrap(err, "opening")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fragment) readCacheFromArchive(r io.Reader) error {
|
||||
// Slurp data from reader and write to disk.
|
||||
buf, err := ioutil.ReadAll(r)
|
||||
|
|
@ -3369,7 +3340,7 @@ func (f *fragment) intRowIterator(tx Tx, wrap bool, filters ...roaring.BitmapFil
|
|||
// accumulator [column ID] -> [int value]
|
||||
acc := make(map[uint64]int64)
|
||||
|
||||
if tx.UseRowCache() {
|
||||
if storage.RowCacheEnabled() {
|
||||
// needs a write lock since it will update the f.rowCache
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
|
|
|||
|
|
@ -183,7 +183,6 @@ func TestFragment_RowcacheMap(t *testing.T) {
|
|||
|
||||
// Ensure a fragment can clear a row.
|
||||
func TestFragment_ClearRow(t *testing.T) {
|
||||
NotBlueGreenTest(t)
|
||||
f, idx, tx := mustOpenFragment(t, "i", "f", viewStandard, 0, "")
|
||||
_ = idx
|
||||
defer f.Clean(t)
|
||||
|
|
@ -215,7 +214,6 @@ func TestFragment_ClearRow(t *testing.T) {
|
|||
|
||||
// Ensure a fragment can set a row.
|
||||
func TestFragment_SetRow(t *testing.T) {
|
||||
NotBlueGreenTest(t)
|
||||
f, idx, tx := mustOpenFragment(t, "i", "f", viewStandard, 7, "")
|
||||
_ = idx
|
||||
defer f.Clean(t)
|
||||
|
|
@ -1728,7 +1726,7 @@ func roaringOnlyBenchmark(b *testing.B) {
|
|||
|
||||
// Ensure a fragment can be copied to another fragment.
|
||||
func TestFragment_WriteTo_ReadFrom(t *testing.T) {
|
||||
roaringOnlyTest(t)
|
||||
// roaringOnlyTest(t)
|
||||
|
||||
f0, _, tx := mustOpenFragment(t, "i", "f", viewStandard, 0, "")
|
||||
defer f0.Clean(t)
|
||||
|
|
@ -1741,6 +1739,10 @@ func TestFragment_WriteTo_ReadFrom(t *testing.T) {
|
|||
} else if _, err := f0.clearBit(tx, 1000, 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err := tx.Commit()
|
||||
if err != nil {
|
||||
t.Fatalf("committing write: %v", err)
|
||||
}
|
||||
|
||||
// Verify cache is populated.
|
||||
if n := f0.cache.Len(); n != 1 {
|
||||
|
|
@ -1755,7 +1757,9 @@ func TestFragment_WriteTo_ReadFrom(t *testing.T) {
|
|||
}
|
||||
|
||||
// Read into another fragment.
|
||||
f1, _, tx := mustOpenFragment(t, "i", "f", viewStandard, 0, "")
|
||||
f1, idx, tx := mustOpenFragment(t, "i", "f", viewStandard, 0, "")
|
||||
tx.Rollback()
|
||||
|
||||
defer f1.Clean(t)
|
||||
|
||||
if rn, err := f1.ReadFrom(&buf); err != nil { // eventually calls fragment.fillFragmentFromArchive
|
||||
|
|
@ -1763,6 +1767,8 @@ func TestFragment_WriteTo_ReadFrom(t *testing.T) {
|
|||
} else if wn != rn {
|
||||
t.Fatalf("read/write byte count mismatch: wn=%d, rn=%d", wn, rn)
|
||||
}
|
||||
// make a read-only Tx after ReadFrom has committed.
|
||||
tx = idx.holder.txf.NewTx(Txo{Write: !writable, Index: idx, Fragment: f1, Shard: f1.shard})
|
||||
|
||||
// Verify cache is in other fragment.
|
||||
if n := f1.cache.Len(); n != 1 {
|
||||
|
|
@ -3049,7 +3055,7 @@ func BenchmarkImportRoaringUpdate(b *testing.B) {
|
|||
// to generate an op log and/or snapshot.
|
||||
itr, err := roaring.NewRoaringIterator(data)
|
||||
PanicOn(err)
|
||||
_, _, err = tx.ImportRoaringBits(f.index(), f.field(), f.view(), f.shard, itr, false, false, 0, nil)
|
||||
_, _, err = tx.ImportRoaringBits(f.index(), f.field(), f.view(), f.shard, itr, false, false, 0)
|
||||
if err != nil {
|
||||
b.Errorf("import error: %v", err)
|
||||
}
|
||||
|
|
@ -5215,15 +5221,13 @@ func TestImportValueConcurrent(t *testing.T) {
|
|||
// we will be making a new Tx each time, so we can rollback the default provided one.
|
||||
tx.Rollback()
|
||||
|
||||
types := idx.holder.txf.TxTypes()
|
||||
for _, ty := range types {
|
||||
switch ty {
|
||||
case roaringTxn:
|
||||
t.Skip(fmt.Sprintf("skipping TestImportValueConcurrent under " +
|
||||
"blueGreenTx because the lack of transactional consistency " +
|
||||
"from Roaring-per-file will create false comparison " +
|
||||
"failures."))
|
||||
}
|
||||
ty := idx.holder.txf.TxTyp()
|
||||
switch ty {
|
||||
case roaringTxn:
|
||||
t.Skip(fmt.Sprintf("skipping TestImportValueConcurrent under " +
|
||||
"roaring because the lack of transactional consistency " +
|
||||
"from Roaring-per-file will create false comparison " +
|
||||
"failures."))
|
||||
}
|
||||
|
||||
eg := &errgroup.Group{}
|
||||
|
|
@ -5364,11 +5368,6 @@ func TestImportValueRowCache(t *testing.T) {
|
|||
// do we see races/corruption around concurrent read/write.
|
||||
// especially on writes to the row cache.
|
||||
func TestFragmentConcurrentReadWrite(t *testing.T) {
|
||||
// actual transaction backends, there won't be any
|
||||
// data, and in particular, the blue-green tests will
|
||||
// note this and fire a false-positive.
|
||||
NotBlueGreenTest(t)
|
||||
|
||||
f, idx, tx := mustOpenFragment(t, "i", "f", viewStandard, 0, CacheTypeRanked)
|
||||
defer f.Clean(t)
|
||||
tx.Rollback()
|
||||
|
|
@ -5528,12 +5527,6 @@ func TestFragment_Bug_Q2DoubleDelete(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func NotBlueGreenTest(t *testing.T) {
|
||||
if strings.Contains(CurrentBackend(), "_") {
|
||||
t.Skip("skip under blue green")
|
||||
}
|
||||
}
|
||||
|
||||
var mutexSamplesPrepared sync.Once
|
||||
|
||||
func requireMutexSampleData(tb testing.TB) {
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -16,8 +16,6 @@ require (
|
|||
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 // indirect
|
||||
github.com/glycerine/idem v0.0.0-20190127113923-7a8083893311
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/protobuf v1.3.3
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -86,10 +86,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8=
|
||||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||
github.com/glycerine/idem v0.0.0-20190127113923-7a8083893311 h1:AAXH0ZvYIHHqU06ASy0H2tYAkAGrQlZvEy2QZrrtt4E=
|
||||
github.com/glycerine/idem v0.0.0-20190127113923-7a8083893311/go.mod h1:B72P/ZM99sNiCmaQJflpmMAF5LsDzStpLdWzn0+Vr2Y=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
|
|
|
|||
19
holder.go
19
holder.go
|
|
@ -302,7 +302,6 @@ func NewHolder(path string, cfg *HolderConfig) *Holder {
|
|||
txf, err := NewTxFactory(cfg.StorageConfig.Backend, h.IndexesPath(), h)
|
||||
PanicOn(err)
|
||||
h.txf = txf
|
||||
h.txf.blueGreenOffIfRunningBlueGreen()
|
||||
|
||||
_ = testhook.Created(h.Auditor, h, nil)
|
||||
return h
|
||||
|
|
@ -605,8 +604,6 @@ func (h *Holder) Open() error {
|
|||
h.txf = txf
|
||||
}
|
||||
|
||||
h.txf.blueGreenOffIfRunningBlueGreen()
|
||||
|
||||
// Reset closing in case Holder is being reopened.
|
||||
h.closing = make(chan struct{})
|
||||
|
||||
|
|
@ -713,13 +710,6 @@ func (h *Holder) Open() error {
|
|||
return errors.Wrap(err, "Holder.Open h.txf.Open()")
|
||||
}
|
||||
|
||||
// under blue_green, we must sync blue from green before we turn on checking.
|
||||
if err := h.txf.green2blue(h); err != nil {
|
||||
return errors.Wrap(err, "Holder.Open h.txf.green2blue(h)")
|
||||
}
|
||||
|
||||
h.txf.blueGreenOnIfRunningBlueGreen()
|
||||
|
||||
if h.cfg.LookupDBDSN != "" {
|
||||
h.Logger.Printf("connecting to lookup database")
|
||||
|
||||
|
|
@ -814,9 +804,6 @@ func (h *Holder) Close() error {
|
|||
if globalUseStatTx {
|
||||
fmt.Printf("%v\n", globalCallStats.report())
|
||||
}
|
||||
if h.txf != nil && h.txf.blueGreenReg != nil {
|
||||
h.txf.blueGreenReg.Close()
|
||||
}
|
||||
|
||||
h.Stats.Close()
|
||||
|
||||
|
|
@ -2131,12 +2118,6 @@ func (h *Holder) addIndex(idx *Index) {
|
|||
h.imu.Unlock()
|
||||
}
|
||||
|
||||
func (h *Holder) DumpAllShards() {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
h.txf.dbPerShard.DumpAll()
|
||||
}
|
||||
|
||||
func (h *Holder) Txf() *TxFactory {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
|
||||
"github.com/molecula/featurebase/v2/disco"
|
||||
"github.com/molecula/featurebase/v2/testhook"
|
||||
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
||||
)
|
||||
|
||||
var _ = fmt.Printf
|
||||
|
|
@ -109,73 +108,6 @@ func testSetBit(t *testing.T, h *Holder, index, field string, rowID, columnID ui
|
|||
}
|
||||
}
|
||||
|
||||
func testMustHaveBit(t *testing.T, h *Holder, index, field string, rowID, columnID uint64) {
|
||||
|
||||
//shard := columnID / ShardWidth
|
||||
|
||||
// hmm... if its a new holder, meta data isn't there, so ask for it.
|
||||
idx, err := h.CreateIndexIfNotExists(index, IndexOptions{})
|
||||
PanicOn(err)
|
||||
|
||||
f := idx.Field(field)
|
||||
if f == nil {
|
||||
t.Fatalf("no such field '%v'", field)
|
||||
}
|
||||
|
||||
row, err := f.Row(nil, rowID)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting field.Row(rowID=%v): %v", rowID, err)
|
||||
}
|
||||
|
||||
cols := row.Columns()
|
||||
if len(cols) == 0 {
|
||||
t.Fatalf("error getting field.Row().Columns(): empty columns, colID %v bit was not hot", columnID)
|
||||
}
|
||||
|
||||
for _, c := range cols {
|
||||
if c == columnID {
|
||||
return // ok, found it.
|
||||
}
|
||||
}
|
||||
t.Fatalf("error getting field.Row().Columns(): colID %v bit was not hot", columnID)
|
||||
}
|
||||
|
||||
func testMustNotHaveBit(t *testing.T, h *Holder, index, field string, rowID, columnID uint64) {
|
||||
if testHasBit(t, h, index, field, rowID, columnID) {
|
||||
t.Fatalf("error, expected no bit but this bit was hot: index='%v', field='%v', rowID='%v', columnID='%v'", index, field, rowID, columnID)
|
||||
}
|
||||
}
|
||||
|
||||
func testHasBit(t *testing.T, h *Holder, index, field string, rowID, columnID uint64) bool {
|
||||
|
||||
idx := h.Index(index)
|
||||
if idx == nil {
|
||||
return false // not even an index by this name. Obviously no hot bits either.
|
||||
}
|
||||
|
||||
f := idx.Field(field)
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
row, err := f.Row(nil, rowID)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cols := row.Columns()
|
||||
if len(cols) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, c := range cols {
|
||||
if c == columnID {
|
||||
return true // ok, found it.
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestHolderOperatorProcess(t *testing.T) {
|
||||
h, path, err := makeHolder(t, "")
|
||||
if err != nil {
|
||||
|
|
|
|||
10
index.go
10
index.go
|
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/molecula/featurebase/v2/roaring"
|
||||
"github.com/molecula/featurebase/v2/stats"
|
||||
"github.com/molecula/featurebase/v2/testhook"
|
||||
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
|
@ -464,7 +463,6 @@ func (i *Index) Close() error {
|
|||
|
||||
// make it clear what the Index.AvailableShards() calls are trying to obtain.
|
||||
const includeRemote = false
|
||||
const localOnly = true
|
||||
|
||||
// AvailableShards returns a bitmap of all shards with data in the index.
|
||||
func (i *Index) AvailableShards(localOnly bool) *roaring.Bitmap {
|
||||
|
|
@ -853,14 +851,6 @@ func FormatQualifiedIndexName(index string) string {
|
|||
return fmt.Sprintf("%s\x00", index)
|
||||
}
|
||||
|
||||
// Dump prints to stdout the contents of the roaring Containers
|
||||
// stored in idx. Mostly for debugging.
|
||||
func (i *Index) Dump(label string) {
|
||||
fileline := FileLine(2)
|
||||
fmt.Printf("\n%v Dump: %v\n\n", fileline, label)
|
||||
i.holder.txf.dbPerShard.DumpAll()
|
||||
}
|
||||
|
||||
func (i *Index) Txf() *TxFactory {
|
||||
return i.holder.txf
|
||||
}
|
||||
|
|
|
|||
115
pjobs.go
115
pjobs.go
|
|
@ -1,115 +0,0 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pilosa
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/glycerine/idem"
|
||||
)
|
||||
|
||||
// parallelJobs runs functions in parallel on a goroutine
|
||||
// pool that has nGoro goroutines.
|
||||
type parallelJobs struct {
|
||||
nGoro int
|
||||
|
||||
jobQ chan func(worker int) error
|
||||
halters []*idem.Halter
|
||||
|
||||
// err is protected by errmu
|
||||
err error
|
||||
errmu sync.Mutex
|
||||
}
|
||||
|
||||
func newParallelJobs(nGoro int) (p *parallelJobs) {
|
||||
if nGoro < 1 {
|
||||
// 0 really means,
|
||||
// "turn it up to 11".
|
||||
// same for negative.
|
||||
nGoro = 10000
|
||||
}
|
||||
// maximum 10K goroutines
|
||||
if nGoro > 10000 {
|
||||
nGoro = 10000
|
||||
}
|
||||
|
||||
p = ¶llelJobs{
|
||||
nGoro: nGoro,
|
||||
jobQ: make(chan func(worker int) error, 10000),
|
||||
halters: make([]*idem.Halter, nGoro),
|
||||
}
|
||||
|
||||
for j := 0; j < nGoro; j++ {
|
||||
h := idem.NewHalter()
|
||||
p.halters[j] = h
|
||||
}
|
||||
|
||||
for i, h := range p.halters {
|
||||
go func(h *idem.Halter, worker int) {
|
||||
defer h.MarkDone()
|
||||
for {
|
||||
select {
|
||||
case <-h.ReqStop.Chan:
|
||||
return
|
||||
case f, ok := <-p.jobQ:
|
||||
if !ok {
|
||||
// channel closed, finish up
|
||||
return
|
||||
}
|
||||
|
||||
err1 := f(worker)
|
||||
if err1 != nil {
|
||||
p.errmu.Lock()
|
||||
if p.err == nil {
|
||||
p.err = err1
|
||||
}
|
||||
p.errmu.Unlock()
|
||||
// an error occurred, tell everyone to stop
|
||||
for _, h2 := range p.halters {
|
||||
h2.RequestStop()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(h, i)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// return value accepted will be false if we are shutting down
|
||||
// due to an error.
|
||||
func (p *parallelJobs) run(fun func(worker int) error) (accepted bool) {
|
||||
select {
|
||||
case <-p.halters[0].ReqStop.Chan:
|
||||
return false
|
||||
case p.jobQ <- fun:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parallelJobs) waitForFinish() error {
|
||||
|
||||
// tell the workers no more jobs.
|
||||
close(p.jobQ)
|
||||
|
||||
// wait for everyone to finish
|
||||
for i, h := range p.halters {
|
||||
_ = i
|
||||
<-h.Done.Chan
|
||||
}
|
||||
|
||||
return p.err
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright 2017 Pilosa Corp.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pilosa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_ParallelJobs_EarlyShutdown_WaitsForAllGoro(t *testing.T) {
|
||||
const n = 10000 // total jobs to run
|
||||
|
||||
var errLastOne = fmt.Errorf("the last job has run, and returned this error")
|
||||
pj := newParallelJobs(100)
|
||||
nTotal := int64(0)
|
||||
for i := 0; i < n; i++ {
|
||||
accepted := pj.run(func(worker int) error {
|
||||
highpoint := atomic.AddInt64(&nTotal, 1)
|
||||
switch int(highpoint) {
|
||||
case n - 1:
|
||||
return errLastOne
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !accepted {
|
||||
panic("should have been accepted")
|
||||
}
|
||||
}
|
||||
err := pj.waitForFinish()
|
||||
tot := atomic.LoadInt64(&nTotal)
|
||||
if int(tot) != n {
|
||||
panic(fmt.Sprintf("We didn't run them all? tot=%v, n=%v; pj.jobQ len %v; err='%v'", tot, n, len(pj.jobQ), err))
|
||||
}
|
||||
if err != errLastOne {
|
||||
panic("expected to see errLastOne")
|
||||
}
|
||||
// good: finished cleanly.
|
||||
}
|
||||
94
rbf.go
94
rbf.go
|
|
@ -15,15 +15,12 @@
|
|||
package pilosa
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/molecula/featurebase/v2/rbf"
|
||||
rbfcfg "github.com/molecula/featurebase/v2/rbf/cfg"
|
||||
|
|
@ -76,8 +73,6 @@ func (w *RbfDBWrapper) CleanupTx(tx Tx) {
|
|||
w.muDb.Lock()
|
||||
|
||||
delete(w.openTx, r)
|
||||
//vv("rbf CleanupTx gid %v about to call r.o.dbs.Cleanup(tx.Sn=%v)", curGID(), tx.Sn())
|
||||
r.o.dbs.Cleanup(tx) // release the read/write lock.
|
||||
|
||||
w.muDb.Unlock()
|
||||
}
|
||||
|
|
@ -186,20 +181,12 @@ type RBFTx struct {
|
|||
initialIndex string
|
||||
tx *rbf.Tx
|
||||
o Txo
|
||||
sn int64 // serial number
|
||||
Db *RbfDBWrapper
|
||||
|
||||
done bool
|
||||
mu sync.Mutex // protect done as it changes state
|
||||
}
|
||||
|
||||
func (tx *RBFTx) IsDone() (done bool) {
|
||||
tx.mu.Lock()
|
||||
done = tx.done
|
||||
tx.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *RBFTx) DBPath() string {
|
||||
return tx.tx.DBPath()
|
||||
}
|
||||
|
|
@ -210,17 +197,13 @@ func (tx *RBFTx) Type() string {
|
|||
|
||||
func (tx *RBFTx) Rollback() {
|
||||
tx.tx.Rollback()
|
||||
|
||||
// must happen after actual rollback
|
||||
tx.Db.CleanupTx(tx)
|
||||
}
|
||||
|
||||
func (tx *RBFTx) Commit() (err error) {
|
||||
err = tx.tx.Commit()
|
||||
|
||||
// must happen after actual commit
|
||||
tx.Db.CleanupTx(tx)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
func (tx *RBFTx) RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) {
|
||||
|
|
@ -412,10 +395,6 @@ func (tx *RBFTx) Min(index, field, view string, shard uint64) (uint64, bool, err
|
|||
return tx.tx.Min(rbfName(index, field, view, shard))
|
||||
}
|
||||
|
||||
func (tx *RBFTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error {
|
||||
return tx.tx.UnionInPlace(rbfName(index, field, view, shard), others...)
|
||||
}
|
||||
|
||||
// CountRange returns the count of hot bits in the start, end range on the fragment.
|
||||
// roaring.countRange counts the number of bits set between [start, end).
|
||||
func (tx *RBFTx) CountRange(index, field, view string, shard uint64, start, end uint64) (n uint64, err error) {
|
||||
|
|
@ -426,24 +405,8 @@ func (tx *RBFTx) OffsetRange(index, field, view string, shard uint64, offset, st
|
|||
return tx.tx.OffsetRange(rbfName(index, field, view, shard), offset, start, end)
|
||||
}
|
||||
|
||||
func (tx *RBFTx) IncrementOpN(index, field, view string, shard uint64, changedN int) {}
|
||||
|
||||
func (tx *RBFTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
|
||||
return tx.tx.ImportRoaringBits(rbfName(index, field, view, shard), rit, clear, log, rowSize, data)
|
||||
}
|
||||
|
||||
func (tx *RBFTx) RoaringBitmapReader(index, field, view string, shard uint64, fragmentPathForRoaring string) (r io.ReadCloser, sz int64, err error) {
|
||||
|
||||
rbm, err := tx.RoaringBitmap(index, field, view, shard)
|
||||
if err != nil {
|
||||
return nil, -1, errors.Wrap(err, "RoaringBitmapReader RoaringBitmap")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
sz, err = rbm.WriteTo(&buf)
|
||||
if err != nil {
|
||||
return nil, -1, errors.Wrap(err, "RoaringBitmapReader rbm.WriteTo(buf)")
|
||||
}
|
||||
return ioutil.NopCloser(&buf), sz, err
|
||||
func (tx *RBFTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) {
|
||||
return tx.tx.ImportRoaringBits(rbfName(index, field, view, shard), rit, clear, log, rowSize)
|
||||
}
|
||||
|
||||
func (tx *RBFTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator {
|
||||
|
|
@ -452,39 +415,6 @@ func (tx *RBFTx) NewTxIterator(index, field, view string, shard uint64) *roaring
|
|||
return b.Iterator()
|
||||
}
|
||||
|
||||
func (tx *RBFTx) Pointer() string {
|
||||
return fmt.Sprintf("%p", tx)
|
||||
}
|
||||
|
||||
func (tx *RBFTx) Dump(short bool, shard uint64) {
|
||||
tx.tx.Dump(short, shard)
|
||||
}
|
||||
|
||||
// Readonly is true if the transaction is not read-and-write, but only doing reads.
|
||||
func (tx *RBFTx) Readonly() bool {
|
||||
return !tx.tx.Writable()
|
||||
}
|
||||
|
||||
func (tx *RBFTx) Group() *TxGroup {
|
||||
return tx.o.Group
|
||||
}
|
||||
|
||||
func (tx *RBFTx) Options() Txo {
|
||||
return tx.o
|
||||
}
|
||||
|
||||
func (tx *RBFTx) Sn() int64 {
|
||||
return tx.sn
|
||||
}
|
||||
|
||||
func (tx *RBFTx) UseRowCache() bool {
|
||||
// since RFB returns memory mapped data, we can't use
|
||||
// the rowCache without first making a copy.
|
||||
// So we only use the rowCache if the copy is
|
||||
// enabled.
|
||||
return storage.EnableRowCache()
|
||||
}
|
||||
|
||||
func (tx *RBFTx) ApplyFilter(index, field, view string, shard uint64, ckey uint64, filter roaring.BitmapFilter) (err error) {
|
||||
return tx.tx.ApplyFilter(rbfName(index, field, view, shard), ckey, filter)
|
||||
}
|
||||
|
|
@ -591,20 +521,16 @@ func (w *RbfDBWrapper) OpenDB() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var globalNextTxSnRBFTx int64
|
||||
|
||||
func (w *RbfDBWrapper) NewTx(write bool, initialIndex string, o Txo) (_ Tx, err error) {
|
||||
tx, err := w.db.Begin(write)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sn := atomic.AddInt64(&globalNextTxSnRBFTx, 1)
|
||||
|
||||
rtx := &RBFTx{
|
||||
tx: tx,
|
||||
initialIndex: initialIndex,
|
||||
o: o,
|
||||
sn: sn,
|
||||
Db: w,
|
||||
}
|
||||
|
||||
|
|
@ -629,20 +555,6 @@ func (w *RbfDBWrapper) DeleteFragment(index, field, view string, shard uint64, f
|
|||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (w *RbfDBWrapper) DeleteDBPath(dbs *DBShard) error {
|
||||
path := dbs.pathForType(rbfTxn)
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
func (w *RbfDBWrapper) OpenListString() (r string) {
|
||||
return "rbf OpenListString not implemented yet"
|
||||
}
|
||||
|
||||
func (w *RbfDBWrapper) OpenSnList() (slc []int64) {
|
||||
w.muDb.Lock()
|
||||
for v := range w.openTx {
|
||||
slc = append(slc, v.sn)
|
||||
}
|
||||
w.muDb.Unlock()
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,20 +59,12 @@ func TestCursor_RoaringImport(t *testing.T) {
|
|||
tx := MustBegin(t, db, true)
|
||||
defer tx.Rollback()
|
||||
|
||||
changed, rowSet, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, rowSet, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
_ = rowSet
|
||||
if changed != 1 {
|
||||
t.Fatalf("expected 1 changed, got %v", changed)
|
||||
}
|
||||
if false {
|
||||
cur, err := tx.cursor(name)
|
||||
PanicOn(err)
|
||||
|
||||
cur.dump()
|
||||
|
||||
_ = cur.tx.dumpAllPages(true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursor_RoaringImport_clear_bits(t *testing.T) {
|
||||
|
|
@ -93,7 +85,7 @@ func TestCursor_RoaringImport_clear_bits(t *testing.T) {
|
|||
tx := MustBegin(t, db, true)
|
||||
defer tx.Rollback()
|
||||
|
||||
changed, rowSet, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, rowSet, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err)
|
||||
_ = rowSet
|
||||
if changed != 1 {
|
||||
|
|
@ -104,21 +96,12 @@ func TestCursor_RoaringImport_clear_bits(t *testing.T) {
|
|||
clear = true
|
||||
itr2 := getRoaringIter([]uint64{1}...)
|
||||
|
||||
changed, rowSet, err = tx.ImportRoaringBits(name, itr2, clear, false, rowSize, nil)
|
||||
changed, rowSet, err = tx.ImportRoaringBits(name, itr2, clear, false, rowSize)
|
||||
PanicOn(err)
|
||||
_ = rowSet
|
||||
if changed != 1 {
|
||||
t.Fatalf("expected 1 changed on clear true, got %v", changed)
|
||||
}
|
||||
|
||||
if false {
|
||||
cur, err := tx.cursor(name)
|
||||
PanicOn(err)
|
||||
|
||||
cur.dump()
|
||||
|
||||
_ = cur.tx.dumpAllPages(true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursor_RoaringImport_two_leaves(t *testing.T) {
|
||||
|
|
@ -152,20 +135,12 @@ func TestCursor_RoaringImport_two_leaves(t *testing.T) {
|
|||
tx := MustBegin(t, db, true)
|
||||
defer tx.Rollback()
|
||||
|
||||
changed, rowSet, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, rowSet, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
_ = rowSet
|
||||
if changed != 6000 {
|
||||
t.Fatalf("expected 6000 bits changed, got %v", changed)
|
||||
}
|
||||
if false {
|
||||
cur, err := tx.cursor(name)
|
||||
PanicOn(err)
|
||||
|
||||
cur.dump()
|
||||
|
||||
_ = cur.tx.dumpAllPages(true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursor_RoaringImport_many_leaves_manual_split(t *testing.T) {
|
||||
|
|
@ -211,7 +186,7 @@ func TestCursor_RoaringImport_many_leaves_manual_split(t *testing.T) {
|
|||
defer tx.Rollback()
|
||||
|
||||
//vv("DONE WITH Add()")
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
if changed != expectedBitsChanged-3000 {
|
||||
t.Fatalf("expected %v bits changed, got %v", expectedBitsChanged-3000, changed)
|
||||
|
|
@ -219,37 +194,23 @@ func TestCursor_RoaringImport_many_leaves_manual_split(t *testing.T) {
|
|||
//vv("changed on set is %v", changed)
|
||||
|
||||
//vv("about to do itr2, that starts with key %v", itr2.ContainerKeys()[0])
|
||||
changed, _, err = tx.ImportRoaringBits(name, itr2, clear, false, rowSize, nil)
|
||||
changed, _, err = tx.ImportRoaringBits(name, itr2, clear, false, rowSize)
|
||||
PanicOn(err)
|
||||
if changed != 3000 {
|
||||
t.Fatalf("expected %v bits changed, got %v", 3000, changed)
|
||||
}
|
||||
|
||||
//vv("done with itr2")
|
||||
|
||||
dump := func() {
|
||||
cur, err := tx.cursor(name)
|
||||
PanicOn(err)
|
||||
//cur.dump()
|
||||
_ = cur.tx.dumpAllPages(true)
|
||||
}
|
||||
_ = dump
|
||||
//dump()
|
||||
//vv("now clear")
|
||||
|
||||
// now clear
|
||||
clear = true
|
||||
//itr3 := getRoaringIter(want[len(want)-3000:]...)
|
||||
itr3 := getRoaringIter(want...)
|
||||
|
||||
changed, _, err = tx.ImportRoaringBits(name, itr3, clear, false, rowSize, nil)
|
||||
changed, _, err = tx.ImportRoaringBits(name, itr3, clear, false, rowSize)
|
||||
PanicOn(err)
|
||||
if changed != expectedBitsChanged {
|
||||
// cursor_internal_test.go:235: expected 2,724,000 bits changed, got 2,721,000
|
||||
t.Fatalf("expected %v bits changed, got %v", expectedBitsChanged, changed)
|
||||
}
|
||||
|
||||
//dump()
|
||||
}
|
||||
|
||||
func TestCursor_RoaringImport_auto_many_leaves(t *testing.T) {
|
||||
|
|
@ -292,34 +253,21 @@ func TestCursor_RoaringImport_auto_many_leaves(t *testing.T) {
|
|||
defer tx.Rollback()
|
||||
//vv("DONE WITH Add()")
|
||||
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
if changed != expectedBitsChanged {
|
||||
t.Fatalf("expected %v bits changed, got %v", expectedBitsChanged, changed)
|
||||
}
|
||||
//vv("changed on set is %v", changed)
|
||||
|
||||
dump := func() {
|
||||
cur, err := tx.cursor(name)
|
||||
PanicOn(err)
|
||||
//cur.dump()
|
||||
_ = cur.tx.dumpAllPages(true)
|
||||
}
|
||||
_ = dump
|
||||
//dump()
|
||||
//vv("now clear")
|
||||
|
||||
// now clear
|
||||
clear = true
|
||||
itr2 := getRoaringIter(want...)
|
||||
|
||||
changed, _, err = tx.ImportRoaringBits(name, itr2, clear, false, rowSize, nil)
|
||||
changed, _, err = tx.ImportRoaringBits(name, itr2, clear, false, rowSize)
|
||||
PanicOn(err)
|
||||
if changed != expectedBitsChanged {
|
||||
t.Fatalf("expected %v bits changed, got %v", expectedBitsChanged, changed)
|
||||
}
|
||||
|
||||
//dump()
|
||||
}
|
||||
|
||||
func TestCursor_putBranchCellsHandlesLotsOfNewBranchesAtTheRoot(t *testing.T) {
|
||||
|
|
@ -430,8 +378,6 @@ func TestCursor_putBranchCellsHandlesLotsOfNewBranchesAtTheRoot(t *testing.T) {
|
|||
|
||||
err = c.putBranchCells(0, branches)
|
||||
PanicOn(err)
|
||||
|
||||
//c.tx.dumpAllPages(true)
|
||||
}
|
||||
|
||||
func TestCursor_incrementally_add_pages_and_view_them(t *testing.T) {
|
||||
|
|
@ -475,29 +421,18 @@ func TestCursor_incrementally_add_pages_and_view_them(t *testing.T) {
|
|||
|
||||
tx := MustBegin(t, db, true)
|
||||
defer tx.Rollback()
|
||||
dump := func() {
|
||||
cur, err := tx.cursor(name)
|
||||
PanicOn(err)
|
||||
//cur.dump()
|
||||
_ = cur.tx.dumpAllPages(true)
|
||||
}
|
||||
_ = dump
|
||||
|
||||
for i := 0; i < NbranchCells; i++ {
|
||||
|
||||
itr := getRoaringIter(want[i*3000 : (i+1)*3000]...)
|
||||
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
if changed != 3000 {
|
||||
t.Fatalf("expected %v bits changed, got %v", 3000, changed)
|
||||
}
|
||||
//vv("changed on set is %v", changed)
|
||||
//dump()
|
||||
}
|
||||
|
||||
//vv("now clear")
|
||||
|
||||
// now clear
|
||||
clear = true
|
||||
|
||||
|
|
@ -505,13 +440,11 @@ func TestCursor_incrementally_add_pages_and_view_them(t *testing.T) {
|
|||
|
||||
itr := getRoaringIter(want[i*3000 : (i+1)*3000]...)
|
||||
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
if changed != 3000 {
|
||||
t.Fatalf("expected %v bits changed, got %v", 3000, changed)
|
||||
}
|
||||
//vv("changed on set is %v", changed)
|
||||
//dump()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -558,35 +491,22 @@ func TestCursor_from_B_to_C(t *testing.T) {
|
|||
|
||||
tx := MustBegin(t, db, true)
|
||||
defer tx.Rollback()
|
||||
dump := func() {
|
||||
cur, err := tx.cursor(name)
|
||||
PanicOn(err)
|
||||
//cur.dump()
|
||||
_ = cur.tx.dumpAllPages(true)
|
||||
}
|
||||
_ = dump
|
||||
itr := getRoaringIter(want[:6000]...)
|
||||
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
if changed != 6000 {
|
||||
t.Fatalf("expected %v bits changed, got %v", 6000, changed)
|
||||
}
|
||||
//vv("changed on set is %v", changed)
|
||||
//dump()
|
||||
|
||||
//vv("STARTING TO ADD C")
|
||||
itr = getRoaringIter(want[6000:9000]...)
|
||||
|
||||
changed, _, err = tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, _, err = tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
if changed != 3000 {
|
||||
t.Fatalf("expected %v bits changed, got %v", 3000, changed)
|
||||
}
|
||||
//vv("changed on set is %v", changed)
|
||||
//dump()
|
||||
|
||||
//vv("now clear")
|
||||
|
||||
// now clear
|
||||
clear = true
|
||||
|
|
@ -595,12 +515,10 @@ func TestCursor_from_B_to_C(t *testing.T) {
|
|||
|
||||
itr := getRoaringIter(want[i*3000 : (i+1)*3000]...)
|
||||
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize, nil)
|
||||
changed, _, err := tx.ImportRoaringBits(name, itr, clear, false, rowSize)
|
||||
PanicOn(err) // writeTheTailOfLeafCellsFromIter has to be AFTER any pre-existing data. ckey=0 was found already in db.
|
||||
if changed != 3000 {
|
||||
t.Fatalf("expected %v bits changed, got %v", 3000, changed) // failing here got 0
|
||||
}
|
||||
//vv("changed on clear is %v", changed)
|
||||
//dump()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ func intoContainer(l leafCell, tx *Tx, replacing *roaring.Container, target []by
|
|||
orig := l.Data
|
||||
var cpMaybe []byte
|
||||
var mapped bool
|
||||
if storage.EnableRowCache() || tx.db.cfg.DoAllocZero {
|
||||
if storage.RowCacheEnabled() || tx.db.cfg.DoAllocZero {
|
||||
// make a copy, otherwise the rowCache will see corrupted data
|
||||
// or mmapped data that may disappear.
|
||||
cpMaybe = target[:len(orig)]
|
||||
|
|
@ -191,7 +191,7 @@ func intoContainer(l leafCell, tx *Tx, replacing *roaring.Container, target []by
|
|||
case ContainerTypeBitmapPtr:
|
||||
_, bm, _ := tx.leafCellBitmap(toPgno(cpMaybe))
|
||||
cloneMaybe := bm
|
||||
if storage.EnableRowCache() {
|
||||
if storage.RowCacheEnabled() {
|
||||
cloneMaybe = (*[1024]uint64)(unsafe.Pointer(&target[0]))[:1024]
|
||||
copy(cloneMaybe, bm)
|
||||
}
|
||||
|
|
@ -217,7 +217,7 @@ func toContainer(l leafCell, tx *Tx) (c *roaring.Container) {
|
|||
orig := l.Data
|
||||
var cpMaybe []byte
|
||||
var mapped bool
|
||||
if storage.EnableRowCache() || tx.db.cfg.DoAllocZero {
|
||||
if storage.RowCacheEnabled() || tx.db.cfg.DoAllocZero {
|
||||
// make a copy, otherwise the rowCache will see corrupted data
|
||||
// or mmapped data that may disappear.
|
||||
cpMaybe = make([]byte, len(orig))
|
||||
|
|
@ -234,7 +234,7 @@ func toContainer(l leafCell, tx *Tx) (c *roaring.Container) {
|
|||
case ContainerTypeBitmapPtr:
|
||||
_, bm, _ := tx.leafCellBitmap(toPgno(cpMaybe))
|
||||
cloneMaybe := bm
|
||||
if storage.EnableRowCache() {
|
||||
if storage.RowCacheEnabled() {
|
||||
cloneMaybe = make([]uint64, len(bm))
|
||||
copy(cloneMaybe, bm)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ func (db *DB) Close() (err error) {
|
|||
// least a single hot bit inside the db
|
||||
// in order to return hasAnyRecords true.
|
||||
//
|
||||
// HasData is used by backend migration and blue/green checks.
|
||||
// HasData is used by backend migration.
|
||||
//
|
||||
// If there is a disk error we return (false, error), so always
|
||||
// check the error before deciding if hasAnyRecords is valid.
|
||||
|
|
|
|||
147
rbf/tx.go
147
rbf/tx.go
|
|
@ -23,7 +23,6 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/benbjohnson/immutable"
|
||||
"github.com/molecula/featurebase/v2/hash"
|
||||
"github.com/molecula/featurebase/v2/roaring"
|
||||
txkey "github.com/molecula/featurebase/v2/short_txkey"
|
||||
. "github.com/molecula/featurebase/v2/vprint"
|
||||
|
|
@ -1299,27 +1298,6 @@ func (tx *Tx) Min(name string) (uint64, bool, error) {
|
|||
return uint64((cell.Key << 16) | uint64(cell.firstValue(tx))), true, nil
|
||||
}
|
||||
|
||||
func (tx *Tx) UnionInPlace(name string, others ...*roaring.Bitmap) error {
|
||||
rbm, err := tx.RoaringBitmap(name)
|
||||
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(name, containerKey, rc)
|
||||
PanicOn(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// roaring.countRange counts the number of bits set between [start, end).
|
||||
func (tx *Tx) CountRange(name string, start, end uint64) (uint64, error) {
|
||||
tx.mu.RLock()
|
||||
|
|
@ -1538,130 +1516,7 @@ func (si *emptyContainerIterator) Value() (uint64, *roaring.Container) {
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
func (tx *Tx) Dump(short bool, shard uint64) {
|
||||
fmt.Println(tx.DumpString(short, shard))
|
||||
}
|
||||
func (tx *Tx) DumpString(short bool, shard uint64) (r string) {
|
||||
|
||||
r = "allkeys:[\n"
|
||||
|
||||
// grab root records, for a list of bitmaps.
|
||||
records, err := tx.RootRecords()
|
||||
PanicOn(err)
|
||||
n := 0
|
||||
|
||||
for itr := records.Iterator(); !itr.Done(); {
|
||||
name, _ := itr.Next()
|
||||
|
||||
c, err := tx.cursor(name.(string))
|
||||
PanicOn(err)
|
||||
defer c.Close()
|
||||
|
||||
err = c.First() // First will rewind to beginning.
|
||||
if err == io.EOF {
|
||||
r += "<empty bitmap>"
|
||||
n++
|
||||
continue
|
||||
}
|
||||
PanicOn(err)
|
||||
for {
|
||||
err := c.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
PanicOn(err)
|
||||
|
||||
elem := &c.stack.elems[c.stack.top]
|
||||
leafPage, _, err := c.tx.readPage(elem.pgno)
|
||||
PanicOn(err)
|
||||
cell := readLeafCell(leafPage, elem.index)
|
||||
|
||||
ckey := cell.Key
|
||||
ct := toContainer(cell, tx)
|
||||
|
||||
s := stringOfCkeyCt(ckey, ct, name.(string), short, true)
|
||||
r += s
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
// note that we can have a bitmap present, but it can be empty
|
||||
r += "]\n all-in-blake3:" + hash.Blake3sum16([]byte(r)) + "\n"
|
||||
|
||||
return "rbf-" + r
|
||||
}
|
||||
|
||||
func containerToBytes(ct *roaring.Container) []byte {
|
||||
|
||||
ty := roaring.ContainerType(ct)
|
||||
switch ty {
|
||||
case roaring.ContainerNil:
|
||||
PanicOn("nil container")
|
||||
case roaring.ContainerArray:
|
||||
return fromArray16(roaring.AsArray(ct))
|
||||
case roaring.ContainerBitmap:
|
||||
return fromArray64(roaring.AsBitmap(ct))
|
||||
case roaring.ContainerRun:
|
||||
return fromInterval16(roaring.AsRuns(ct))
|
||||
}
|
||||
PanicOn(fmt.Sprintf("unknown container type '%v'", int(ty)))
|
||||
return nil
|
||||
}
|
||||
|
||||
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 stringOfCkeyCt(ckey uint64, ct *roaring.Container, rrName string, short, showHash bool) (s string) {
|
||||
|
||||
hsh := ""
|
||||
if showHash {
|
||||
by := containerToBytes(ct)
|
||||
hsh = hash.Blake3sum16(by)
|
||||
}
|
||||
|
||||
cts := roaring.NewSliceContainers()
|
||||
cts.Put(ckey, ct)
|
||||
rbm := &roaring.Bitmap{Containers: cts}
|
||||
srbm := bitmapAsString(rbm)
|
||||
|
||||
var pre string
|
||||
if len(rrName) > 0 {
|
||||
pre = txkey.PrefixToString([]byte(rrName))
|
||||
}
|
||||
bkey := pre + fmt.Sprintf("ckey@%020d", ckey)
|
||||
|
||||
s = fmt.Sprintf("%v -> %v (%v hot)\n", bkey, hsh, ct.N())
|
||||
|
||||
if !short {
|
||||
s += " ......." + srbm + "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *Tx) ImportRoaringBits(name string, itr roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
|
||||
func (tx *Tx) ImportRoaringBits(name string, itr roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) {
|
||||
|
||||
// begin write boilerplate
|
||||
if tx.db == nil {
|
||||
|
|
|
|||
|
|
@ -16,14 +16,12 @@ package rbf_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/molecula/featurebase/v2/rbf"
|
||||
txkey "github.com/molecula/featurebase/v2/short_txkey"
|
||||
)
|
||||
|
||||
func TestTx_CommitRollback(t *testing.T) {
|
||||
|
|
@ -496,29 +494,6 @@ func BenchmarkTx_Contains(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTx_Dump(t *testing.T) {
|
||||
db := MustOpenDB(t)
|
||||
defer MustCloseDB(t, db)
|
||||
tx := MustBegin(t, db, true)
|
||||
defer tx.Rollback()
|
||||
|
||||
index, field, view, shard := "i", "f", "v", uint64(15)
|
||||
|
||||
nm := rbfName(index, field, view, shard)
|
||||
|
||||
if err := tx.CreateBitmap(nm); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if _, err := tx.Add(nm, 0x00000001, 0x00000002, 0x00010003, 0x00030004); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test that we don't crash, and get *something* back
|
||||
s := tx.DumpString(true, math.MaxUint64)
|
||||
if s == "" {
|
||||
panic("should have had 3 containers!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTx_CreateBitmap(t *testing.T) {
|
||||
t.Run("Bulk", func(t *testing.T) {
|
||||
db := MustOpenDB(t)
|
||||
|
|
@ -542,7 +517,3 @@ func TestTx_CreateBitmap(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
func rbfName(index, field, view string, shard uint64) string {
|
||||
return string(txkey.Prefix(index, field, view, shard))
|
||||
}
|
||||
|
|
|
|||
55
rbf/util.go
55
rbf/util.go
|
|
@ -15,13 +15,16 @@ package rbf
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
txkey "github.com/molecula/featurebase/v2/short_txkey"
|
||||
. "github.com/molecula/featurebase/v2/vprint"
|
||||
)
|
||||
|
||||
// we don't currently use dumpAllPages but it's tricky enough to get right
|
||||
// that it's probably worth keeping as a debugging tool.
|
||||
var _ = (*Tx).dumpAllPages
|
||||
|
||||
func (tx *Tx) dumpAllPages(showLeaves bool) error {
|
||||
|
||||
infos, err := tx.PageInfos()
|
||||
|
|
@ -213,56 +216,6 @@ func prefixToString(s string) (ret string) {
|
|||
return txkey.PrefixToString([]byte(s))
|
||||
}
|
||||
|
||||
func (c *Cursor) dump() {
|
||||
fmt.Printf("\n Cursor %p has bitmaps:\n%v\n", c, c.debugStringBitmaps())
|
||||
}
|
||||
|
||||
var _ = (&Cursor{}).dump
|
||||
var _ = (&Cursor{}).debugStringBitmaps
|
||||
|
||||
func (c_orig *Cursor) debugStringBitmaps() (r string) {
|
||||
|
||||
// work with a totally new Cursor, so we don't impact our current cursor
|
||||
// so any test using the cursor isn't disturbed.
|
||||
c2 := Cursor{tx: c_orig.tx}
|
||||
c2.stack.elems[0] = c_orig.stack.elems[0]
|
||||
err := c2.First()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
// ok, can be empty
|
||||
return "<empty cursor/tx>"
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
n := 0
|
||||
for {
|
||||
err := c2.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
PanicOn(err)
|
||||
|
||||
//instead of cell := c2.cell()
|
||||
elem := &c2.stack.elems[c2.stack.top]
|
||||
leafPage, _, err := c2.tx.readPage(elem.pgno)
|
||||
PanicOn(err)
|
||||
cell := readLeafCell(leafPage, elem.index)
|
||||
|
||||
ckey := cell.Key
|
||||
ct := toContainer(cell, c2.tx)
|
||||
const short = true
|
||||
s := stringOfCkeyCt(ckey, ct, "", short, true)
|
||||
r += s
|
||||
n++
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
///////////////// happy linter
|
||||
|
||||
var _ = printMetaPage
|
||||
|
|
|
|||
|
|
@ -2434,13 +2434,13 @@ func (b *Bitmap) writeOp(op *op) error {
|
|||
|
||||
// Iterator returns a new iterator for the bitmap.
|
||||
func (b *Bitmap) Iterator() *Iterator {
|
||||
itr := NewIterator(&BitmapIteratorFinder{b})
|
||||
itr := &Iterator{bitmap: b}
|
||||
itr.Seek(0)
|
||||
return itr
|
||||
}
|
||||
|
||||
func (b *Bitmap) IteratorAt(start uint64) *Iterator {
|
||||
itr := NewIterator(&BitmapIteratorFinder{b})
|
||||
itr := &Iterator{bitmap: b}
|
||||
itr.Seek(start)
|
||||
return itr
|
||||
}
|
||||
|
|
@ -2701,37 +2701,18 @@ 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 {
|
||||
finder IteratorFinder
|
||||
bitmap *Bitmap
|
||||
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()
|
||||
}
|
||||
// This exists because we used to support a backend which needed it, and I
|
||||
// don't want to re-experience the joy of figuring out where close calls are needed.
|
||||
func (itr *Iterator) Close() {}
|
||||
|
||||
// Seek moves to the first value equal to or greater than `seek`.
|
||||
func (itr *Iterator) Seek(seek uint64) {
|
||||
|
|
@ -2740,7 +2721,7 @@ func (itr *Iterator) Seek(seek uint64) {
|
|||
itr.k = -1
|
||||
|
||||
// Move to the correct container.
|
||||
itr.citer, _ = itr.finder.FindIterator(highbits(seek))
|
||||
itr.citer, _ = itr.bitmap.Containers.Iterator(highbits(seek))
|
||||
if !itr.citer.Next() {
|
||||
itr.c = nil
|
||||
return // eof
|
||||
|
|
@ -7537,10 +7518,6 @@ func (c *Container) Difference(other *Container) *Container {
|
|||
return difference(c, other)
|
||||
}
|
||||
|
||||
func NewSliceContainers() *sliceContainers {
|
||||
return newSliceContainers()
|
||||
}
|
||||
|
||||
// Slice returns an array of the values in the container as uint16.
|
||||
// Do NOT modify the result; it could be the container's actual storage.
|
||||
func (c *Container) Slice() (r []uint16) {
|
||||
|
|
|
|||
93
rrtx.go
93
rrtx.go
|
|
@ -15,9 +15,7 @@
|
|||
package pilosa
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
|
@ -49,27 +47,10 @@ type RoaringTx struct {
|
|||
w *RoaringWrapper
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) IsDone() (done bool) {
|
||||
tx.mu.Lock()
|
||||
done = tx.done
|
||||
tx.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) Type() string {
|
||||
return RoaringTxn
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) Dump(short bool, shard uint64) {
|
||||
o := tx.o
|
||||
o.Shard = shard
|
||||
fmt.Printf("%v\n", tx.Index.StringifiedRoaringKeys(short, false, o))
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) UseRowCache() bool {
|
||||
return storage.EnableRowCache()
|
||||
}
|
||||
|
||||
// based on view.openFragments()
|
||||
func roaringMapOfShards(optionalViewPath string) (shardMap map[uint64]bool, err error) {
|
||||
|
||||
|
|
@ -112,10 +93,6 @@ func roaringMapOfShards(optionalViewPath string) (shardMap map[uint64]bool, err
|
|||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -127,33 +104,16 @@ func (tx *RoaringTx) NewTxIterator(index, field, view string, shard uint64) *roa
|
|||
// ImportRoaringBits return values changed and rowSet will be inaccurate if
|
||||
// the data []byte is supplied. This mimics the traditional roaring-per-file
|
||||
// and should be faster.
|
||||
func (tx *RoaringTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
|
||||
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) {
|
||||
f, err := tx.getFragment(index, field, view, shard)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if len(data) > 0 {
|
||||
// changed and rowSet are ignored anyway when len(data) > 0;
|
||||
// when we are called from fragment.fillFragmentFromArchive()
|
||||
// which is the only place the data []byte is supplied.
|
||||
// blueGreenTx also turns off the checks in this case.
|
||||
return 0, nil, f.readStorageFromArchive(bytes.NewBuffer(data))
|
||||
}
|
||||
|
||||
changed, rowSet, err = f.storage.ImportRoaringRawIterator(rit, clear, true, rowSize)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func (c *RoaringTx) ApplyFilter(index, field, view string, shard uint64, ckey uint64, filter roaring.BitmapFilter) (err error) {
|
||||
return GenericApplyFilter(c, index, field, view, shard, ckey, filter)
|
||||
}
|
||||
|
|
@ -279,15 +239,6 @@ func (tx *RoaringTx) Min(index, field, view string, shard uint64) (uint64, bool,
|
|||
return v, ok, nil
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error {
|
||||
b, err := tx.bitmap(index, field, view, shard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.UnionInPlace(others...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) CountRange(index, field, view string, shard uint64, start, end uint64) (uint64, error) {
|
||||
b, err := tx.bitmap(index, field, view, shard)
|
||||
if err != nil {
|
||||
|
|
@ -381,33 +332,6 @@ func (tx *RoaringTx) bitmap(index, field, view string, shard uint64) (*roaring.B
|
|||
return frag.storage, nil
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) RoaringBitmapReader(index, field, view string, shard uint64, fragmentPathForRoaring string) (r io.ReadCloser, sz int64, err error) {
|
||||
file, err := os.Open(fragmentPathForRoaring) // open the fragment file
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, -1, errors.Wrap(err, "statting")
|
||||
}
|
||||
sz = fi.Size()
|
||||
r = file
|
||||
return
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) Group() *TxGroup {
|
||||
return tx.o.Group
|
||||
}
|
||||
|
||||
func (tx *RoaringTx) Options() Txo {
|
||||
return tx.o
|
||||
}
|
||||
|
||||
// Sn retreives the serial number of the Tx.
|
||||
func (tx *RoaringTx) Sn() int64 {
|
||||
return tx.sn
|
||||
}
|
||||
|
||||
func roaringGetFieldView2Shards(idx *Index) (vs *FieldView2Shards, err error) {
|
||||
vs = NewFieldView2Shards()
|
||||
|
||||
|
|
@ -651,18 +575,12 @@ func (w *RoaringWrapper) CleanupTx(tx Tx) {
|
|||
return
|
||||
}
|
||||
r.done = true
|
||||
|
||||
r.o.dbs.Cleanup(tx) // release the read/write lock.
|
||||
}
|
||||
|
||||
func (w *RoaringWrapper) OpenListString() (r string) {
|
||||
return "RoaringWrapper.OpenListString() not yet implemented"
|
||||
}
|
||||
|
||||
func (w *RoaringWrapper) OpenSnList() (slc []int64) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *RoaringWrapper) CloseDB() error {
|
||||
return errors.New("CloseDB not supported in roaring")
|
||||
}
|
||||
|
|
@ -721,21 +639,12 @@ func (w *RoaringWrapper) IsClosed() (closed bool) {
|
|||
return
|
||||
}
|
||||
|
||||
func (w *RoaringWrapper) DeleteDBPath(dbs *DBShard) (err error) {
|
||||
//vv("RoaringWrapper.DeleteDBPath called on dbs = '%#v'", dbs)
|
||||
path := dbs.pathForType(roaringTxn)
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
func (w *RoaringWrapper) DeleteField(index, field, fieldPath string) error {
|
||||
//vv("RoaringWrapper.DeleteField(index = '%v', field = '%v', fieldPath = '%v'", index, field, fieldPath)
|
||||
|
||||
// match txn sn count vs lmdb/etc.
|
||||
atomic.AddInt64(&globalNextTxSnRoaring, 1)
|
||||
|
||||
// under blue-green bolt_roaring, the directory will not be found, b/c bolt will have
|
||||
// already done the os.RemoveAll(). BUT, RemoveAll returns nil error in this case. Docs:
|
||||
// "If the path does not exist, RemoveAll returns nil (no error)"
|
||||
err := os.RemoveAll(fieldPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "removing directory")
|
||||
|
|
|
|||
|
|
@ -334,8 +334,8 @@ func OptServerOpenTranslateReader(fn OpenTranslateReaderFunc) ServerOption {
|
|||
}
|
||||
|
||||
// OptServerStorageConfig is a functional option on Server used to specify the
|
||||
// transactional-storage backend to use, resulting in RoaringTx, RbfTx,
|
||||
// BadgerTx, or a blueGreen* Tx being used for all Tx interface calls.
|
||||
// transactional-storage backend to use, resulting in RoaringTx or RbfTx
|
||||
// being used for all Tx interface calls.
|
||||
func OptServerStorageConfig(cfg *storage.Config) ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.holderConfig.StorageConfig = cfg
|
||||
|
|
|
|||
|
|
@ -142,19 +142,6 @@ func TestClusterResize_EmptyNodes(t *testing.T) {
|
|||
|
||||
// Ensure that adding a node correctly resizes the cluster.
|
||||
func TestClusterResize_AddNode(t *testing.T) {
|
||||
// Why are we skipping this test under blue-green with Roaring?
|
||||
//
|
||||
// We see red test: during resize during importRoaringBits
|
||||
// PILOSA_STORAGE_BACKEND=rbf_roaring go test -v -tags=' shardwidth20' "-gcflags=all=-d=checkptr=0" -run TestClusterResize_AddNode/"ContinuousShards"
|
||||
// green:
|
||||
// PILOSA_STORAGE_BACKEND=roaring_rbf go test -v -tags=' shardwidth20' "-gcflags=all=-d=checkptr=0" -run TestClusterResize_AddNode/"ContinuousShards"
|
||||
//
|
||||
// but rbf_badger and badger_rbf are both green (use the same data values for containers).
|
||||
//
|
||||
// Conclude: roaring reads a different size of data []byte in (due to ops log) bits vs others (RBF, badger), so
|
||||
// we can't do blue-green with roaring on this test.
|
||||
skipTestUnderBlueGreenWithRoaring(t)
|
||||
|
||||
t.Run("NoData", func(t *testing.T) {
|
||||
clus := test.MustRunCluster(t, 3)
|
||||
defer clus.Close()
|
||||
|
|
@ -344,8 +331,6 @@ func TestClusterResize_AddNode(t *testing.T) {
|
|||
|
||||
// Ensure that adding a node correctly resizes the cluster.
|
||||
func TestClusterResize_AddNodeConcurrentIndex(t *testing.T) {
|
||||
skipTestUnderBlueGreenWithRoaring(t)
|
||||
|
||||
t.Run("WithIndex", func(t *testing.T) {
|
||||
c := test.MustRunCluster(t, 3)
|
||||
defer c.Close()
|
||||
|
|
@ -630,12 +615,3 @@ func TestClusterMutualTLS(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func skipTestUnderBlueGreenWithRoaring(t *testing.T) {
|
||||
src := pilosa.CurrentBackend()
|
||||
if strings.Contains(src, "_") {
|
||||
if strings.Contains(src, "roaring") {
|
||||
t.Skip("skip for roaring blue-green")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,15 +207,8 @@ type Config struct {
|
|||
|
||||
// Storage.Backend 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","bolt",
|
||||
// "rbf", "bolt_roaring", "roaring_bolt", "rbf_roaring", "roaring_rbf",
|
||||
// "bolt_rbf", "rbf_bolt", 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.
|
||||
// listed in the string constants below. Should be one of "roaring" or
|
||||
// "rbf".
|
||||
Storage *storage.Config `toml:"storage"`
|
||||
|
||||
// RowcacheOn, if true, turns on the row cache for all storage backends.
|
||||
|
|
|
|||
123
stattx.go
123
stattx.go
|
|
@ -16,7 +16,6 @@ package pilosa
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"runtime"
|
||||
"sort"
|
||||
|
|
@ -151,8 +150,7 @@ type kall int
|
|||
|
||||
// constants for kall argument to callStats.add()
|
||||
const (
|
||||
kIncrementOpN kall = iota
|
||||
kNewTxIterator
|
||||
kNewTxIterator kall = iota
|
||||
kImportRoaringBits
|
||||
kRollback
|
||||
kCommit
|
||||
|
|
@ -169,23 +167,14 @@ const (
|
|||
kCount
|
||||
kMax
|
||||
kMin
|
||||
kUnionInPlace
|
||||
kCountRange
|
||||
kOffsetRange
|
||||
kRoaringBitmapReader
|
||||
kSliceOfShards
|
||||
kLast // mark the end, always keep this last. The following aren't tracked atm:
|
||||
kType
|
||||
kDump
|
||||
kReadonly
|
||||
kPointer
|
||||
kUseRowCache
|
||||
)
|
||||
|
||||
func (k kall) String() string {
|
||||
switch k {
|
||||
case kIncrementOpN:
|
||||
return "kIncrementOpN"
|
||||
case kNewTxIterator:
|
||||
return "kNewTxIterator"
|
||||
case kImportRoaringBits:
|
||||
|
|
@ -220,62 +209,21 @@ func (k kall) String() string {
|
|||
return "kMax"
|
||||
case kMin:
|
||||
return "kMin"
|
||||
case kUnionInPlace:
|
||||
return "kUnionInPlace"
|
||||
case kCountRange:
|
||||
return "kCountRange"
|
||||
case kOffsetRange:
|
||||
return "kOffsetRange"
|
||||
case kRoaringBitmapReader:
|
||||
return "kRoaringBitmapReader"
|
||||
case kSliceOfShards:
|
||||
return "kSliceOfShards"
|
||||
case kLast:
|
||||
return "kLast"
|
||||
case kType:
|
||||
return "kType"
|
||||
case kDump:
|
||||
return "kDump"
|
||||
case kReadonly:
|
||||
return "kReadonly"
|
||||
case kPointer:
|
||||
return "kPointer"
|
||||
case kUseRowCache:
|
||||
return "kUseRowCache"
|
||||
}
|
||||
PanicOn(fmt.Sprintf("unknown kall '%v'", int(k)))
|
||||
return ""
|
||||
}
|
||||
|
||||
var _ = newStatTx // happy linter
|
||||
var _ = kPointer
|
||||
var _ = kUseRowCache
|
||||
var _ = kType
|
||||
var _ = kDump
|
||||
var _ = kReadonly
|
||||
|
||||
var _ Tx = (*statTx)(nil)
|
||||
|
||||
func (c *statTx) Group() *TxGroup {
|
||||
return c.b.Group()
|
||||
}
|
||||
|
||||
func (c *statTx) Options() Txo {
|
||||
return c.b.Options()
|
||||
}
|
||||
|
||||
//IncrementOpN
|
||||
func (c *statTx) IncrementOpN(index, field, view string, shard uint64, changedN int) {
|
||||
me := kIncrementOpN
|
||||
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
c.stats.add(me, time.Since(t0))
|
||||
}()
|
||||
|
||||
c.b.IncrementOpN(index, field, view, shard, changedN)
|
||||
}
|
||||
|
||||
func (c *statTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator {
|
||||
me := kNewTxIterator
|
||||
|
||||
|
|
@ -286,7 +234,7 @@ func (c *statTx) NewTxIterator(index, field, view string, shard uint64) *roaring
|
|||
return c.b.NewTxIterator(index, field, view, shard)
|
||||
}
|
||||
|
||||
func (c *statTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
|
||||
func (c *statTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error) {
|
||||
me := kImportRoaringBits
|
||||
|
||||
t0 := time.Now()
|
||||
|
|
@ -299,25 +247,7 @@ func (c *statTx) ImportRoaringBits(index, field, view string, shard uint64, rit
|
|||
PanicOn(r)
|
||||
}
|
||||
}()
|
||||
return c.b.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize, data)
|
||||
}
|
||||
|
||||
func (c *statTx) Dump(short bool, shard uint64) {
|
||||
c.b.Dump(short, shard)
|
||||
}
|
||||
|
||||
func (c *statTx) Readonly() bool {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see Readonly() PanicOn '%v' at '%v'", r, Stack())
|
||||
PanicOn(r)
|
||||
}
|
||||
}()
|
||||
return c.b.Readonly()
|
||||
}
|
||||
|
||||
func (tx *statTx) Pointer() string {
|
||||
return fmt.Sprintf("%p", tx)
|
||||
return c.b.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize)
|
||||
}
|
||||
|
||||
func (c *statTx) Rollback() {
|
||||
|
|
@ -421,14 +351,6 @@ func (c *statTx) RemoveContainer(index, field, view string, shard uint64, key ui
|
|||
return c.b.RemoveContainer(index, field, view, shard, key)
|
||||
}
|
||||
|
||||
func (c *statTx) UseRowCache() bool {
|
||||
return c.b.UseRowCache()
|
||||
}
|
||||
|
||||
func (c *statTx) IsDone() (done bool) {
|
||||
return c.b.IsDone()
|
||||
}
|
||||
|
||||
func (c *statTx) Add(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) {
|
||||
me := kAdd
|
||||
|
||||
|
|
@ -586,23 +508,6 @@ func (c *statTx) Min(index, field, view string, shard uint64) (uint64, bool, err
|
|||
return c.b.Min(index, field, view, shard)
|
||||
}
|
||||
|
||||
func (c *statTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error {
|
||||
me := kUnionInPlace
|
||||
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
c.stats.add(me, time.Since(t0))
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see UnionInPlace() PanicOn '%v' at '%v'", r, Stack())
|
||||
PanicOn(r)
|
||||
}
|
||||
}()
|
||||
return c.b.UnionInPlace(index, field, view, shard, others...)
|
||||
}
|
||||
|
||||
func (c *statTx) CountRange(index, field, view string, shard uint64, start, end uint64) (n uint64, err error) {
|
||||
me := kCountRange
|
||||
|
||||
|
|
@ -636,32 +541,10 @@ func (c *statTx) OffsetRange(index, field, view string, shard, offset, start, en
|
|||
return c.b.OffsetRange(index, field, view, shard, offset, start, end)
|
||||
}
|
||||
|
||||
func (c *statTx) RoaringBitmapReader(index, field, view string, shard uint64, fragmentPathForRoaring string) (r io.ReadCloser, sz int64, err error) {
|
||||
me := kRoaringBitmapReader
|
||||
|
||||
t0 := time.Now()
|
||||
defer func() {
|
||||
c.stats.add(me, time.Since(t0))
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
AlwaysPrintf("see RoaringBitmapReader() PanicOn '%v' at '%v'", r, Stack())
|
||||
PanicOn(r)
|
||||
}
|
||||
}()
|
||||
return c.b.RoaringBitmapReader(index, field, view, shard, fragmentPathForRoaring)
|
||||
}
|
||||
|
||||
func (c *statTx) Type() string {
|
||||
return c.b.Type()
|
||||
}
|
||||
|
||||
// Sn retreives the serial number of the Tx.
|
||||
func (c *statTx) Sn() int64 {
|
||||
return c.b.Sn()
|
||||
}
|
||||
|
||||
func (c *statTx) GetSortedFieldViewList(idx *Index, shard uint64) (fvs []txkey.FieldView, err error) {
|
||||
return c.b.GetSortedFieldViewList(idx, shard)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,6 @@ func SetRowCacheOn(on bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func EnableRowCache() bool {
|
||||
func RowCacheEnabled() bool {
|
||||
return atomic.LoadInt64(&enableRowcache) == 1
|
||||
}
|
||||
|
|
|
|||
107
tx.go
107
tx.go
|
|
@ -15,8 +15,6 @@
|
|||
package pilosa
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/molecula/featurebase/v2/roaring"
|
||||
txkey "github.com/molecula/featurebase/v2/short_txkey"
|
||||
//txkey "github.com/molecula/featurebase/v2/txkey"
|
||||
|
|
@ -45,8 +43,8 @@ const writable = true
|
|||
// that have not been committed.
|
||||
type Tx interface {
|
||||
|
||||
// Type returns "roaring", "rbf", "bolt", "badger_roaring", or one of the other
|
||||
// blue-green Tx types at the top of txfactory.go
|
||||
// Type returns "roaring", "rbf", or one of the other
|
||||
// Tx types at the top of txfactory.go
|
||||
Type() string
|
||||
|
||||
// Rollback must be called the end of read-only transactions. Either
|
||||
|
|
@ -65,32 +63,6 @@ type Tx interface {
|
|||
// Commit makes the updates in the Tx visible to subsequent transactions.
|
||||
Commit() error
|
||||
|
||||
// IsDone must return true if Rollback() or Commit() has already
|
||||
// been called. Otherwise it must return false. This allows
|
||||
// DBWrapper.CleanupTx(tx Tx) to be idempotent.
|
||||
IsDone() bool
|
||||
|
||||
// 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 wants 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.
|
||||
|
|
@ -103,8 +75,7 @@ type Tx interface {
|
|||
// Return value 'found' is true when the ckey container was present.
|
||||
// ckey of 0 gives all containers (in the fragment).
|
||||
//
|
||||
// ContainerIterator must not have side-effects. blueGreenTx will
|
||||
// call it at the very beginning of commit to verify db contents.
|
||||
// ContainerIterator must not have side-effects.
|
||||
//
|
||||
// citer.Close() must be called when the client is done using it.
|
||||
ContainerIterator(index, field, view string, shard uint64, ckey uint64) (citer roaring.ContainerIterator, found bool, err error)
|
||||
|
|
@ -154,9 +125,6 @@ type Tx interface {
|
|||
// 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)
|
||||
|
||||
|
|
@ -170,31 +138,9 @@ type Tx interface {
|
|||
// If clear is true, the bits from rit are cleared, otherwise they are set in the
|
||||
// specifed fragment.
|
||||
//
|
||||
// The data argument can be nil, its ignored for RBF/BadgerTx. It is supplied to
|
||||
// RoaringTx.ImportRoaringBits() in fragment.go fragment.fillFragmentFromArchive()
|
||||
// to do the traditional fragment.readStorageFromArchive() which
|
||||
// does some in memory field/view/fragment metadata updates.
|
||||
// It makes blueGreenTx testing viable too.
|
||||
//
|
||||
// ImportRoaringBits return values changed and rowSet may be inaccurate if
|
||||
// the data []byte is supplied (the RoaringTx implementation neglects this for speed).
|
||||
ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error)
|
||||
|
||||
RoaringBitmapReader(index, field, view string, shard uint64, fragmentPathForRoaring string) (r io.ReadCloser, sz int64, err error)
|
||||
|
||||
// Group returns nil or the TxGroup that this Tx is a part of.
|
||||
Group() *TxGroup
|
||||
|
||||
// Dump is for debugging, what does this Tx see as its database?
|
||||
Dump(short bool, shard uint64)
|
||||
|
||||
// Options returns the options used to create this Tx. This
|
||||
// can be implementd by embedding Txo, and Txo provides the
|
||||
// Options() method.
|
||||
Options() Txo
|
||||
|
||||
// Sn retreives the serial number of the Tx.
|
||||
Sn() int64
|
||||
ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64) (changed int, rowSet map[uint64]int, err error)
|
||||
|
||||
// GetSortedFieldViewList gets the set of FieldView(s)
|
||||
GetSortedFieldViewList(idx *Index, shard uint64) (fvs []txkey.FieldView, err error)
|
||||
|
|
@ -202,51 +148,6 @@ type Tx interface {
|
|||
GetFieldSizeBytes(index, field string) (uint64, error)
|
||||
}
|
||||
|
||||
// Closer is used by Finders
|
||||
type Closer interface {
|
||||
Close()
|
||||
}
|
||||
|
||||
type Dumper interface {
|
||||
// Dump is for debugging, what does this Tx see as its database?
|
||||
AllDump()
|
||||
}
|
||||
|
||||
// TxStore has operations that will create and commit multiple
|
||||
// Tx on a backing store.
|
||||
type TxStore interface {
|
||||
|
||||
// DeleteFragment deletes all the containers in a fragment.
|
||||
//
|
||||
// This is not in a Tx because it will often do too many deletes for a single
|
||||
// transaction, and clients would be suprised to find their Tx had already
|
||||
// been commited and they are getting an error on double-Commit.
|
||||
// Instead each TxStore implementation creates and commits as many
|
||||
// transactions as needed.
|
||||
//
|
||||
// Argument frag should be passed by any RoaringTx user, but for RBF/Badger it can be nil.
|
||||
// If not nil, it must be of type *fragment. If frag is supplied, then
|
||||
// index must be equal to frag.index, field equal to frag.field, view equal
|
||||
// to frag.view, and shard equal to frag.shard.
|
||||
//
|
||||
DeleteFragment(index, field, view string, shard uint64, frag interface{}) error
|
||||
|
||||
DeleteField(index, field string) error
|
||||
|
||||
// Close shuts down the database.
|
||||
Close() 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)
|
||||
}
|
||||
|
||||
// GenericApplyFilter implements ApplyFilter in terms of tx.ContainerIterator,
|
||||
// as a convenience if a Tx backend hasn't implemented this new function yet.
|
||||
func GenericApplyFilter(tx Tx, index, field, view string, shard uint64, ckey uint64, filter roaring.BitmapFilter) (err error) {
|
||||
|
|
|
|||
676
txfactory.go
676
txfactory.go
|
|
@ -16,32 +16,22 @@ package pilosa
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/molecula/featurebase/v2/hash"
|
||||
"github.com/molecula/featurebase/v2/roaring"
|
||||
txkey "github.com/molecula/featurebase/v2/short_txkey"
|
||||
"github.com/molecula/featurebase/v2/storage"
|
||||
"github.com/molecula/featurebase/v2/testhook"
|
||||
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeebo/blake3"
|
||||
)
|
||||
|
||||
// public strings that pilosa/server/config.go can reference
|
||||
const (
|
||||
RoaringTxn string = "roaring"
|
||||
RBFTxn string = "rbf"
|
||||
BoltTxn string = "bolt"
|
||||
)
|
||||
|
||||
// DetectMemAccessPastTx true helps us catch places in api and executor
|
||||
|
|
@ -267,7 +257,7 @@ func (qcx *Qcx) GetTx(o Txo) (tx Tx, finisher func(perr *error), err error) {
|
|||
// qcx.write reflects the top executor determination
|
||||
// if a write will be done at the end, so we upgrade
|
||||
// the "local" read Tx to be writes, so that they
|
||||
// don't deadlock against themselves under blue-green.
|
||||
// don't deadlock against themselves.
|
||||
o.Write = o.Write || qcx.write
|
||||
|
||||
// In general, we make ALL write transactions local, and never reuse them
|
||||
|
|
@ -305,9 +295,8 @@ func (qcx *Qcx) GetTx(o Txo) (tx Tx, finisher func(perr *error), err error) {
|
|||
if already {
|
||||
return
|
||||
}
|
||||
o.Group = qcx.Grp
|
||||
tx = qcx.Txf.NewTx(o)
|
||||
qcx.Grp.AddTx(tx)
|
||||
qcx.Grp.AddTx(tx, o)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +340,6 @@ func (qcx *Qcx) StartAtomicWriteTx(o Txo) {
|
|||
// new Tx needed
|
||||
tx := qcx.Txf.NewTx(o)
|
||||
qcx.RequiredForAtomicWriteTx = &tx
|
||||
o := tx.Options()
|
||||
qcx.RequiredTxo = &o
|
||||
return
|
||||
}
|
||||
|
|
@ -374,55 +362,23 @@ func (qcx *Qcx) StartAtomicWriteTx(o Txo) {
|
|||
}
|
||||
}
|
||||
|
||||
func (qcx *Qcx) SetRequiredForAtomicWriteTx(tx Tx) {
|
||||
if tx == nil || NilInside(tx) {
|
||||
PanicOn("cannot set nil tx in SetRequiredForAtomicWriteTx")
|
||||
}
|
||||
qcx.mu.Lock()
|
||||
qcx.RequiredForAtomicWriteTx = &tx
|
||||
o := tx.Options()
|
||||
qcx.RequiredTxo = &o
|
||||
qcx.mu.Unlock()
|
||||
}
|
||||
|
||||
func (qcx *Qcx) ClearRequiredForAtomicWriteTx() {
|
||||
qcx.mu.Lock()
|
||||
qcx.RequiredForAtomicWriteTx = nil
|
||||
qcx.RequiredTxo = nil
|
||||
qcx.mu.Unlock()
|
||||
}
|
||||
|
||||
func (qcx *Qcx) ListOpenTx() string {
|
||||
return qcx.Grp.String()
|
||||
}
|
||||
|
||||
// TxFactory abstracts the creation of Tx interface-level
|
||||
// transactions so that RBF, BoltDB, or Roaring-fragment-files, or several
|
||||
// transactions so that RBF, or Roaring-fragment-files, or several
|
||||
// of these at once in parallel, is used as the storage and transction layer.
|
||||
type TxFactory struct {
|
||||
typeOfTx string
|
||||
|
||||
mu sync.Mutex
|
||||
|
||||
types []txtype // blue-green split individually here
|
||||
typ txtype
|
||||
|
||||
dbsClosed bool // idemopotent CloseDB()
|
||||
|
||||
dbPerShard *DBPerShard
|
||||
|
||||
holder *Holder
|
||||
|
||||
blueGreenReg *blueGreenRegistry
|
||||
|
||||
// allow holder to activate blue-green checking only
|
||||
// once we have synced both sides at start up time.
|
||||
blueGreenOff bool
|
||||
|
||||
isBlueGreen bool
|
||||
}
|
||||
|
||||
func (f *TxFactory) Types() []txtype {
|
||||
return f.types
|
||||
}
|
||||
|
||||
// integer types for fast switch{}
|
||||
|
|
@ -432,7 +388,6 @@ const (
|
|||
noneTxn txtype = 0
|
||||
roaringTxn txtype = 1 // these don't really have any transactions
|
||||
rbfTxn txtype = 2
|
||||
boltTxn txtype = 4
|
||||
)
|
||||
|
||||
// DirectoryName just returns a string version of the transaction type. We
|
||||
|
|
@ -445,73 +400,41 @@ func (ty txtype) DirectoryName() string {
|
|||
return "roaring"
|
||||
case rbfTxn:
|
||||
return "rbf"
|
||||
case boltTxn:
|
||||
return "boltdb"
|
||||
}
|
||||
PanicOn(fmt.Sprintf("unkown txtype %v", int(ty)))
|
||||
return ""
|
||||
}
|
||||
|
||||
func (txf *TxFactory) NeedsSnapshot() (b bool) {
|
||||
for _, ty := range txf.types {
|
||||
switch ty {
|
||||
case roaringTxn:
|
||||
b = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
return txf.typ == roaringTxn
|
||||
}
|
||||
|
||||
func MustBackendToTxtype(backend string) (types []txtype) {
|
||||
var srcs []string
|
||||
func MustBackendToTxtype(backend string) (typ txtype) {
|
||||
if strings.Contains(backend, "_") {
|
||||
srcs = strings.Split(backend, "_")
|
||||
if len(srcs) != 2 {
|
||||
PanicOn("only two blue-green comparisons permitted")
|
||||
}
|
||||
} else {
|
||||
srcs = append(srcs, backend)
|
||||
panic("blue-green comparisons removed")
|
||||
}
|
||||
|
||||
for i, s := range srcs {
|
||||
switch s {
|
||||
case RoaringTxn: // "roaring"
|
||||
types = append(types, roaringTxn)
|
||||
case RBFTxn: // "rbf"
|
||||
types = append(types, rbfTxn)
|
||||
case BoltTxn: // "bolt"
|
||||
types = append(types, boltTxn)
|
||||
default:
|
||||
PanicOn(fmt.Sprintf("unknown backend '%v'", s))
|
||||
}
|
||||
if i == 1 {
|
||||
if types[1] == types[0] {
|
||||
PanicOn(fmt.Sprintf("cannot blue-green the same backend on both arms: '%v'", s))
|
||||
}
|
||||
}
|
||||
switch backend {
|
||||
case RoaringTxn: // "roaring"
|
||||
return roaringTxn
|
||||
case RBFTxn: // "rbf"
|
||||
return rbfTxn
|
||||
}
|
||||
return
|
||||
panic(fmt.Sprintf("unknown backend '%v'", backend))
|
||||
}
|
||||
|
||||
// NewTxFactory always opens an existing database. If you
|
||||
// want to a fresh database, os.RemoveAll on dir/name ahead of time.
|
||||
// We always store files in a subdir of holderDir.
|
||||
func NewTxFactory(backend string, holderDir string, holder *Holder) (f *TxFactory, err error) {
|
||||
types := MustBackendToTxtype(backend)
|
||||
typ := MustBackendToTxtype(backend)
|
||||
|
||||
f = &TxFactory{
|
||||
types: types,
|
||||
typ: typ,
|
||||
typeOfTx: backend,
|
||||
holder: holder,
|
||||
}
|
||||
if len(types) == 2 {
|
||||
f.blueGreenReg = newBlueGreenReg(types)
|
||||
f.isBlueGreen = true
|
||||
// blue-green can never use the rowCache.
|
||||
storage.SetRowCacheOn(false)
|
||||
}
|
||||
f.dbPerShard = f.NewDBPerShard(types, holderDir, holder)
|
||||
f.dbPerShard = f.NewDBPerShard(typ, holderDir, holder)
|
||||
|
||||
if f.hasRBF() {
|
||||
holder.Logger.Infof("rbf config = %#v", holder.cfg.RBFConfig)
|
||||
|
|
@ -526,15 +449,6 @@ func (f *TxFactory) Open() error {
|
|||
return f.dbPerShard.LoadExistingDBs()
|
||||
}
|
||||
|
||||
// UseRowCache can be more "global" than Tx at the moment, because
|
||||
// we are sharing the same bool flag in rbf at the moment. If
|
||||
// this changes then fragment.openStorage() will need a new way
|
||||
// to determine if it should use the rowCache. Currently it
|
||||
// doesn't have a tx Tx parameter, so we use the Txf instead.
|
||||
func (f *TxFactory) UseRowCache() bool {
|
||||
return storage.EnableRowCache()
|
||||
}
|
||||
|
||||
// Txo holds the transaction options
|
||||
type Txo struct {
|
||||
Write bool
|
||||
|
|
@ -544,23 +458,14 @@ type Txo struct {
|
|||
Shard uint64
|
||||
|
||||
dbs *DBShard
|
||||
per *DBPerShard
|
||||
|
||||
Group *TxGroup
|
||||
|
||||
blueGreenOff bool
|
||||
}
|
||||
|
||||
func (o Txo) String() string {
|
||||
return fmt.Sprintf("Txo{Write:%v, Index:%v Shard:%v Group:%p}", o.Write, o.Index.name, o.Shard, o.Group)
|
||||
}
|
||||
|
||||
func (f *TxFactory) TxType() string {
|
||||
return f.typeOfTx
|
||||
}
|
||||
|
||||
func (f *TxFactory) TxTypes() []txtype {
|
||||
return f.types
|
||||
func (f *TxFactory) TxTyp() txtype {
|
||||
return f.typ
|
||||
}
|
||||
|
||||
func (f *TxFactory) DeleteIndex(name string) (err error) {
|
||||
|
|
@ -577,10 +482,6 @@ func (f *TxFactory) DeleteFragmentFromStore(
|
|||
return f.dbPerShard.DeleteFragment(index, field, view, shard, frag)
|
||||
}
|
||||
|
||||
func (f *TxFactory) DumpAll() {
|
||||
f.dbPerShard.DumpAll()
|
||||
}
|
||||
|
||||
// IndexUsageDetails computes the sum of filesizes used by the node, broken down
|
||||
// by index, field, fragments and keys.
|
||||
func (f *TxFactory) IndexUsageDetails(isClosing func() bool) (map[string]IndexUsage, uint64, error) {
|
||||
|
|
@ -769,7 +670,6 @@ func directoryUsage(fname string, recursive bool) (uint64, error) {
|
|||
|
||||
// CloseIndex is a no-op. This seems to be in place for debugging purposes.
|
||||
func (f *TxFactory) CloseIndex(idx *Index) error {
|
||||
//idx.Dump("CloseIndex")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -790,24 +690,24 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
// TxGroup holds a set of read and a set of write transactions
|
||||
// that will en-mass have Rollback() (for the read set) and
|
||||
// Commit() (for the write set) called on
|
||||
// TxGroup holds a set of read transactions
|
||||
// that will en-mass have Rollback() (for the read set) called on
|
||||
// them when TxGroup.Finish() is invoked.
|
||||
// Alternatively, TxGroup.Abort() will call Rollback()
|
||||
// on all Tx group memebers.
|
||||
//
|
||||
// It used to have writes but we never actually used that because
|
||||
// of the Qcx needing to make every commit get its own transaction.
|
||||
type TxGroup struct {
|
||||
mu sync.Mutex
|
||||
fac *TxFactory
|
||||
reads []Tx
|
||||
writes []Tx
|
||||
finished bool
|
||||
|
||||
all map[grpkey]Tx
|
||||
}
|
||||
|
||||
type grpkey struct {
|
||||
write bool
|
||||
index string
|
||||
shard uint64
|
||||
}
|
||||
|
|
@ -822,7 +722,7 @@ func (g *TxGroup) AlreadyHaveTx(o Txo) (tx Tx, already bool) {
|
|||
mustHaveIndexShard(&o)
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
key := grpkey{write: o.Write, index: o.Index.name, shard: o.Shard}
|
||||
key := grpkey{index: o.Index.name, shard: o.Shard}
|
||||
tx, already = g.all[key]
|
||||
return
|
||||
}
|
||||
|
|
@ -830,21 +730,14 @@ func (g *TxGroup) AlreadyHaveTx(o Txo) (tx Tx, already bool) {
|
|||
func (g *TxGroup) String() (r string) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if len(g.reads) == 0 && len(g.writes) == 0 {
|
||||
if len(g.reads) == 0 {
|
||||
return "<empty-TxGroup>"
|
||||
}
|
||||
|
||||
i := 0
|
||||
r += "\n"
|
||||
for _, tx := range g.reads {
|
||||
r += fmt.Sprintf("[%v]read: _sn_ %v %v, \n", i, tx.Sn(), tx.Options())
|
||||
i++
|
||||
for i, tx := range g.reads {
|
||||
r += fmt.Sprintf("[%v]read: %#v,\n", i, tx)
|
||||
}
|
||||
for _, tx := range g.writes {
|
||||
r += fmt.Sprintf("[%v]write: _sn_ %v %v, \n", i, tx.Sn(), tx.Options())
|
||||
i++
|
||||
}
|
||||
return
|
||||
return r
|
||||
}
|
||||
|
||||
// NewTxGroup
|
||||
|
|
@ -857,7 +750,7 @@ func (f *TxFactory) NewTxGroup() (g *TxGroup) {
|
|||
}
|
||||
|
||||
// AddTx adds tx to the group.
|
||||
func (g *TxGroup) AddTx(tx Tx) {
|
||||
func (g *TxGroup) AddTx(tx Tx, o Txo) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.finished {
|
||||
|
|
@ -867,15 +760,9 @@ func (g *TxGroup) AddTx(tx Tx) {
|
|||
PanicOn("Cannot add nil Tx to TxGroup")
|
||||
}
|
||||
|
||||
if tx.Readonly() {
|
||||
g.reads = append(g.reads, tx)
|
||||
} else {
|
||||
g.writes = append(g.writes, tx)
|
||||
}
|
||||
o := tx.Options()
|
||||
mustHaveIndexShard(&o)
|
||||
g.reads = append(g.reads, tx)
|
||||
|
||||
key := grpkey{write: o.Write, index: o.Index.name, shard: o.Shard}
|
||||
key := grpkey{index: o.Index.name, shard: o.Shard}
|
||||
prior, ok := g.all[key]
|
||||
if ok {
|
||||
PanicOn(fmt.Sprintf("already have Tx in group for this, we should have re-used it! prior is '%v'; tx='%v'", prior, tx))
|
||||
|
|
@ -893,15 +780,6 @@ func (g *TxGroup) FinishGroup() (err error) {
|
|||
PanicOn("in TxGroup.Finish(): TxGroup already finished")
|
||||
}
|
||||
g.finished = true
|
||||
for i, tx := range g.writes {
|
||||
_ = i
|
||||
err0 := tx.Commit()
|
||||
if err0 != nil {
|
||||
if err == nil {
|
||||
err = err0 // keep the first error, but Commit them all.
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, r := range g.reads {
|
||||
r.Rollback()
|
||||
}
|
||||
|
|
@ -923,9 +801,6 @@ func (g *TxGroup) AbortGroup() {
|
|||
for _, r := range g.reads {
|
||||
r.Rollback()
|
||||
}
|
||||
for _, tx := range g.writes {
|
||||
tx.Rollback()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TxFactory) NewTx(o Txo) (txn Tx) {
|
||||
|
|
@ -935,12 +810,6 @@ func (f *TxFactory) NewTx(o Txo) (txn Tx) {
|
|||
}
|
||||
}()
|
||||
|
||||
if f.isBlueGreen {
|
||||
f.mu.Lock()
|
||||
o.blueGreenOff = f.blueGreenOff
|
||||
f.mu.Unlock()
|
||||
}
|
||||
|
||||
indexName := ""
|
||||
if o.Index != nil {
|
||||
indexName = o.Index.name
|
||||
|
|
@ -963,10 +832,8 @@ func (f *TxFactory) NewTx(o Txo) (txn Tx) {
|
|||
if dbs.Shard != o.Shard {
|
||||
PanicOn(fmt.Sprintf("asked for o.Shard=%v but got dbs.Shard=%v", int(o.Shard), int(dbs.Shard)))
|
||||
}
|
||||
//vv("got dbs='%p' for o.Index='%v'; shard='%v'; dbs.types='%#v'; dbs.W='%#v'", dbs, o.Index.name, o.Shard, dbs.types, dbs.W)
|
||||
|
||||
o.dbs = dbs // our specific database per shard.
|
||||
o.per = f.dbPerShard // for top level debug Dumps
|
||||
//vv("got dbs='%p' for o.Index='%v'; shard='%v'; dbs.typ='%#v'; dbs.W='%#v'", dbs, o.Index.name, o.Shard, dbs.typ, dbs.W)
|
||||
o.dbs = dbs
|
||||
|
||||
tx, err := dbs.NewTx(o.Write, indexName, o)
|
||||
if err != nil {
|
||||
|
|
@ -984,8 +851,6 @@ func (ty txtype) String() string {
|
|||
return "roaring"
|
||||
case rbfTxn:
|
||||
return "rbf"
|
||||
case boltTxn:
|
||||
return "bolt"
|
||||
}
|
||||
PanicOn(fmt.Sprintf("unhandled ty '%v' in txtype.String()", int(ty)))
|
||||
return ""
|
||||
|
|
@ -1022,147 +887,6 @@ func fragmentSpecFromRoaringPath(path string) (field, view string, shard uint64,
|
|||
return
|
||||
}
|
||||
|
||||
// hashOnly means only show the value hash, not the content bits.
|
||||
// showOps means display the ops log.
|
||||
func (idx *Index) StringifiedRoaringKeys(hashOnly, showOps bool, o Txo) (r string) {
|
||||
paths, err := listFilesUnderDir(idx.path, false, "", true)
|
||||
PanicOn(err)
|
||||
index := idx.name
|
||||
|
||||
r = "allkeys:[\n"
|
||||
n := 0
|
||||
for _, relpath := range paths {
|
||||
field, view, shard, err := fragmentSpecFromRoaringPath(relpath)
|
||||
if err != nil {
|
||||
continue // ignore .meta paths
|
||||
}
|
||||
if shard != o.Shard {
|
||||
continue // only print the shard the Txo is on.
|
||||
}
|
||||
abspath := idx.path + sep + relpath
|
||||
|
||||
s, _, err := stringifiedRawRoaringFragment(abspath, index, field, view, shard, showOps, hashOnly, os.Stdout)
|
||||
PanicOn(err)
|
||||
//r += fmt.Sprintf("path:'%v' fragment contains:\n") + s
|
||||
//if s == "" {
|
||||
//s = "<empty bitmap>"
|
||||
//}
|
||||
r += s
|
||||
n++
|
||||
}
|
||||
if n == 0 {
|
||||
return "<empty roaring data>"
|
||||
}
|
||||
// note that we can have a bitmap present, but it can be empty
|
||||
r += "]\n all-in-blake3:" + hash.Blake3sum16([]byte(r)) + "\n"
|
||||
|
||||
return "roaring-" + r
|
||||
}
|
||||
|
||||
func RoaringFragmentChecksum(path string, index, field, view string, shard uint64) (r string, hotbits int) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
PanicOn(fmt.Sprintf("caught PanicOn on path='%v', index='%v', field='%v', view='%v', shard='%v': %v",
|
||||
path, index, field, view, shard, r))
|
||||
}
|
||||
}()
|
||||
hasher := blake3.New()
|
||||
showOps := false
|
||||
hashOnly := true
|
||||
hash, hotbits, err := stringifiedRawRoaringFragment(path, index, field, view, shard, showOps, hashOnly, hasher)
|
||||
PanicOn(err)
|
||||
fmt.Fprintf(hasher, "%v/%v/%v/%v/%v", index, field, view, shard, hash)
|
||||
var buf [16]byte
|
||||
_, _ = hasher.Digest().Read(buf[0:])
|
||||
return fmt.Sprintf("%x", buf), hotbits
|
||||
|
||||
}
|
||||
|
||||
func stringifiedRawRoaringFragment(path string, index, field, view string, shard uint64, showOps, hashOnly bool, w io.Writer) (r string, hotbits int, 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 {
|
||||
PanicOn(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
|
||||
}
|
||||
|
||||
//cmd.DisplayInfo(info)
|
||||
// inlined
|
||||
if showOps {
|
||||
pC := pointerContext{
|
||||
from: info.From,
|
||||
to: info.To,
|
||||
}
|
||||
if info.ContainerCount > 0 {
|
||||
printContainers(w, info, pC)
|
||||
}
|
||||
if info.Ops > 0 {
|
||||
printOps(w, info)
|
||||
}
|
||||
}
|
||||
|
||||
citer, found := rbm.Containers.Iterator(0)
|
||||
_ = found // probably gonna use just the Ops log instead, so don't PanicOn if !found.
|
||||
|
||||
for citer.Next() {
|
||||
ckey, ct := citer.Value()
|
||||
by := containerToBytes(ct)
|
||||
hash := hash.Blake3sum16(by)
|
||||
|
||||
cts := roaring.NewSliceContainers()
|
||||
cts.Put(ckey, ct)
|
||||
rbm := &roaring.Bitmap{Containers: cts}
|
||||
|
||||
var srbm string
|
||||
if !hashOnly {
|
||||
srbm = BitmapAsString(rbm)
|
||||
}
|
||||
|
||||
bkey := txkey.ToString(txkey.Key(index, field, view, shard, ckey))
|
||||
|
||||
n := ct.N()
|
||||
hotbits += int(n)
|
||||
r += fmt.Sprintf("%v -> %v (%v hot)\n", bkey, hash, n)
|
||||
if !hashOnly {
|
||||
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,
|
||||
|
|
@ -1218,130 +942,6 @@ func fileSize(name string) (int64, error) {
|
|||
return fi.Size(), nil
|
||||
}
|
||||
|
||||
var _ = fileSize // happy linter
|
||||
|
||||
func containerToBytes(ct *roaring.Container) []byte {
|
||||
ty := roaring.ContainerType(ct)
|
||||
switch ty {
|
||||
case roaring.ContainerNil:
|
||||
PanicOn("nil roaring.Container")
|
||||
case roaring.ContainerArray:
|
||||
return fromArray16(roaring.AsArray(ct))
|
||||
case roaring.ContainerBitmap:
|
||||
return fromArray64(roaring.AsBitmap(ct))
|
||||
case roaring.ContainerRun:
|
||||
return fromInterval16(roaring.AsRuns(ct))
|
||||
}
|
||||
PanicOn(fmt.Sprintf("unknown roaring.Container type '%v'", int(ty)))
|
||||
return nil
|
||||
}
|
||||
|
||||
type pointerContext struct {
|
||||
from, to uintptr
|
||||
}
|
||||
|
||||
func printOps(w io.Writer, info roaring.BitmapInfo) {
|
||||
fmt.Fprintln(w, " Ops:")
|
||||
tw := tabwriter.NewWriter(w, 0, 8, 0, '\t', 0)
|
||||
fmt.Fprintf(tw, " \t%s\t%s\t%s\t\n", "TYPE", "OpN", "SIZE")
|
||||
printed := 0
|
||||
for _, op := range info.OpDetails {
|
||||
fmt.Fprintf(tw, "\t%s\t%d\t%d\t\n", op.Type, op.OpN, op.Size)
|
||||
printed++
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
func (p *pointerContext) pretty(c roaring.ContainerInfo) string {
|
||||
var pointer string
|
||||
if c.Mapped {
|
||||
if c.Pointer >= p.from && c.Pointer < p.to {
|
||||
pointer = fmt.Sprintf("@+0x%x", c.Pointer-p.from)
|
||||
} else {
|
||||
pointer = fmt.Sprintf("!0x%x!", c.Pointer)
|
||||
}
|
||||
} else {
|
||||
pointer = fmt.Sprintf("0x%x", c.Pointer)
|
||||
}
|
||||
return fmt.Sprintf("%s \t%d \t%d \t%s ", c.Type, c.N, c.Alloc, pointer)
|
||||
}
|
||||
|
||||
// stolen from ctl/inspect.go
|
||||
func printContainers(w io.Writer, info roaring.BitmapInfo, pC pointerContext) {
|
||||
fmt.Fprintln(w, " Containers:")
|
||||
tw := tabwriter.NewWriter(w, 0, 8, 0, '\t', 0)
|
||||
fmt.Fprintf(tw, " \t\tRoaring\t\t\t\tOps\t\t\t\tFlags\t\n")
|
||||
fmt.Fprintf(tw, "\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "KEY", "TYPE", "N", "ALLOC", "OFFSET", "TYPE", "N", "ALLOC", "OFFSET", "FLAGS")
|
||||
c1s := info.Containers
|
||||
c2s := info.OpContainers
|
||||
l1 := len(c1s)
|
||||
l2 := len(c2s)
|
||||
i1 := 0
|
||||
i2 := 0
|
||||
var c1, c2 roaring.ContainerInfo
|
||||
c1.Key = ^uint64(0)
|
||||
c2.Key = ^uint64(0)
|
||||
c1e := false
|
||||
c2e := false
|
||||
if i1 < l1 {
|
||||
c1 = c1s[i1]
|
||||
i1++
|
||||
c1e = true
|
||||
}
|
||||
if i2 < l2 {
|
||||
c2 = c2s[i2]
|
||||
i2++
|
||||
c2e = true
|
||||
}
|
||||
printed := 0
|
||||
for c1e || c2e {
|
||||
c1used := false
|
||||
c2used := false
|
||||
var key uint64
|
||||
c1fmt := "-\t\t\t"
|
||||
c2fmt := "-\t\t\t"
|
||||
// If c2 exists, we'll always prefer its flags,
|
||||
// if it doesn't, this gets overwritten.
|
||||
flags := c2.Flags
|
||||
if !c2e || (c1e && c1.Key < c2.Key) {
|
||||
c1fmt = pC.pretty(c1)
|
||||
key = c1.Key
|
||||
c1used = true
|
||||
flags = c1.Flags
|
||||
} else if !c1e || (c2e && c2.Key < c1.Key) {
|
||||
c2fmt = pC.pretty(c2)
|
||||
key = c2.Key
|
||||
c2used = true
|
||||
} else {
|
||||
// c1e and c2e both set, and neither key is < the other.
|
||||
c1fmt = pC.pretty(c1)
|
||||
c2fmt = pC.pretty(c2)
|
||||
key = c1.Key
|
||||
c1used = true
|
||||
c2used = true
|
||||
}
|
||||
if c1used {
|
||||
if i1 < l1 {
|
||||
c1 = c1s[i1]
|
||||
i1++
|
||||
} else {
|
||||
c1e = false
|
||||
}
|
||||
}
|
||||
if c2used {
|
||||
if i2 < l2 {
|
||||
c2 = c2s[i2]
|
||||
i2++
|
||||
} else {
|
||||
c2e = false
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(tw, "\t%d\t%s\t%s\t%s\t\n", key, c1fmt, c2fmt, flags)
|
||||
printed++
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
var _ = anyGlobalDBWrappersStillOpen // happy linter
|
||||
|
||||
func anyGlobalDBWrappersStillOpen() bool {
|
||||
|
|
@ -1351,225 +951,19 @@ func anyGlobalDBWrappersStillOpen() bool {
|
|||
if globalRbfDBReg.Size() != 0 {
|
||||
return true
|
||||
}
|
||||
if globalBoltReg.Size() != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *TxFactory) blueGreenOnIfRunningBlueGreen() {
|
||||
if len(f.types) == 2 {
|
||||
f.blueGreenOff = false
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TxFactory) blueGreenOffIfRunningBlueGreen() {
|
||||
if len(f.types) == 2 {
|
||||
f.blueGreenOff = true
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TxFactory) hasRoaring() bool {
|
||||
return f.types[0] == roaringTxn || (len(f.types) > 1 && f.types[1] == roaringTxn)
|
||||
return f.typ == roaringTxn
|
||||
}
|
||||
|
||||
func (f *TxFactory) hasRBF() bool {
|
||||
return f.types[0] == rbfTxn || (len(f.types) > 1 && f.types[1] == rbfTxn)
|
||||
return f.typ == rbfTxn
|
||||
}
|
||||
|
||||
var _ = (&TxFactory{}).hasRoaring // happy linter
|
||||
|
||||
func (f *TxFactory) blueHasData() (hasData bool, err error) {
|
||||
if len(f.types) != 2 {
|
||||
return false, nil
|
||||
}
|
||||
return f.dbPerShard.HasData(0)
|
||||
}
|
||||
|
||||
func (f *TxFactory) greenHasData() (hasData bool, err error) {
|
||||
n := len(f.types)
|
||||
switch n {
|
||||
case 1:
|
||||
return f.dbPerShard.HasData(0)
|
||||
case 2:
|
||||
return f.dbPerShard.HasData(1)
|
||||
}
|
||||
err = fmt.Errorf("unsupported len(f.types): %v; must be 1 or 2", n)
|
||||
PanicOn(err)
|
||||
return
|
||||
}
|
||||
|
||||
// green2blue is called at the very end of Holder.Open(), so
|
||||
// we know that the holder is ready to go, knowing its holder.Indexes(), fields,
|
||||
// view, shards, and other metadata if any.
|
||||
//
|
||||
// Called by test Test_TxFactory_UpdateBlueFromGreen_OnStartup() in
|
||||
// txfactory_internal_test.go as well.
|
||||
//
|
||||
// This is a noop if we aren't running under a blue_green PILOSA_STORAGE_BACKEND.
|
||||
func (f *TxFactory) green2blue(holder *Holder) (err0 error) {
|
||||
|
||||
// Holder.Open will always call us, even without blue_green. Which is fine.
|
||||
// We are just a no-op in that case.
|
||||
if len(f.types) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
holder.Logger.Infof("green2blue analysis begins.")
|
||||
|
||||
blueDest := f.types[0]
|
||||
greenSrc := f.types[1]
|
||||
|
||||
if blueDest == roaringTxn {
|
||||
return fmt.Errorf("error: cannot migrate to 'roaring': not implemented")
|
||||
}
|
||||
|
||||
idxs := holder.Indexes()
|
||||
|
||||
verifyInsteadOfCopy := false
|
||||
|
||||
blueHasData, err := f.blueHasData()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "TxFactory.green2blue f.blueHasData()")
|
||||
}
|
||||
|
||||
greenHasData, err := f.greenHasData()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "TxFactory.green2blue f.greenHasData()")
|
||||
}
|
||||
if !blueHasData && !greenHasData {
|
||||
holder.Logger.Infof("no data in blue or green. No migration or verification to do")
|
||||
return nil
|
||||
}
|
||||
// INVAR: blue has data.
|
||||
if !greenHasData {
|
||||
holder.Logger.Errorf("cannot migrate from green '%v' because it has no data in it", greenSrc)
|
||||
return fmt.Errorf("error: cannot migrate from green '%v' because it has no data in it", greenSrc)
|
||||
}
|
||||
|
||||
nGoro := runtime.NumCPU()
|
||||
if nGoro < 5 {
|
||||
// try to get some overlapped IO
|
||||
nGoro = 5
|
||||
}
|
||||
pj := newParallelJobs(nGoro)
|
||||
|
||||
action := "verify"
|
||||
if blueHasData {
|
||||
verifyInsteadOfCopy = true
|
||||
defer holder.Logger.Infof("bitmap-backend verification done : %v compared to %v", blueDest, greenSrc)
|
||||
} else {
|
||||
action = "migrate"
|
||||
holder.Logger.Infof("bitmap-backend migration starting: populating %v from %v with %v threads", blueDest, greenSrc, nGoro)
|
||||
defer holder.Logger.Infof("bitmap-backend migration done : populated %v from %v", blueDest, greenSrc)
|
||||
}
|
||||
firstPjobStarted := false
|
||||
|
||||
indexloop:
|
||||
for k, idx := range idxs {
|
||||
|
||||
// scan directories
|
||||
blueShards, err := f.dbPerShard.TypedDBPerShardGetShardsForIndex(blueDest, idx, "", false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("GetDBShard(index='%v') error fetching blueShards", idx.name))
|
||||
}
|
||||
|
||||
// scan directories
|
||||
greenShards, err := f.dbPerShard.TypedDBPerShardGetShardsForIndex(greenSrc, idx, "", true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("GetDBShard(index='%v') error fetching greenShards", idx.name))
|
||||
}
|
||||
|
||||
if verifyInsteadOfCopy {
|
||||
diff := f.shardSetDiff(blueShards, greenShards)
|
||||
if diff != "" {
|
||||
return fmt.Errorf("verifyInsteadOfCopy true, blue[%v]=%#v and green[%v]=%#v have different shards for index '%v': '%v'; stack=\n%v", blueDest, blueShards, greenSrc, greenShards, idx.name, diff, Stack())
|
||||
}
|
||||
|
||||
// can also check against meta data
|
||||
shards := idx.AvailableShards(localOnly).Slice()
|
||||
meta := make(map[uint64]bool)
|
||||
for _, shard := range shards {
|
||||
meta[shard] = true
|
||||
}
|
||||
diff2 := f.shardSetDiff(greenShards, meta)
|
||||
if diff2 != "" {
|
||||
return fmt.Errorf("green[%v] = '%#v' and meta data '%#v' have different shards for index '%v': %v", greenSrc, greenShards, shards, idx.name, diff2)
|
||||
}
|
||||
}
|
||||
|
||||
shardNum := 0
|
||||
for shard := range greenShards {
|
||||
shardNum++
|
||||
shnum := shardNum
|
||||
idx := idx
|
||||
shard := shard
|
||||
k := k
|
||||
fun := func(worker int) error {
|
||||
|
||||
dbs, err := f.dbPerShard.GetDBShard(idx.name, shard, idx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("GetDBShard(index='%v', shard='%v')", idx.name, int(shard)))
|
||||
}
|
||||
|
||||
holder.Logger.Infof("%v progress on index '%v' (%v of %v): on shard '%v' (%v of %v) [worker %v]",
|
||||
action, idx.name, k+1, len(idxs), shard, shnum, len(greenShards), worker)
|
||||
|
||||
if verifyInsteadOfCopy {
|
||||
// verify all containers
|
||||
err = dbs.verifyBlueEqualsGreen()
|
||||
if err != nil {
|
||||
return errors.Wrap(err,
|
||||
fmt.Sprintf("dbs.verifyBlueEqualsGreen(blue='%v', "+
|
||||
"green='%v') for index='%v', shard='%v'",
|
||||
blueDest, greenSrc, idx.name, int(shard)))
|
||||
}
|
||||
} else {
|
||||
// the main copy work
|
||||
err = dbs.populateBlueFromGreen()
|
||||
if err != nil {
|
||||
return errors.Wrap(err,
|
||||
fmt.Sprintf("dbs.copyGreenToBlue(blue='%v', "+
|
||||
"green='%v') for index='%v', shard='%v'",
|
||||
blueDest, greenSrc, idx.name, int(shard)))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
} // end of fun definition
|
||||
|
||||
if !pj.run(fun) {
|
||||
break indexloop
|
||||
}
|
||||
if !firstPjobStarted {
|
||||
firstPjobStarted = true
|
||||
defer func() {
|
||||
err1 := pj.waitForFinish()
|
||||
if err0 == nil {
|
||||
err0 = err1
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *TxFactory) shardSetDiff(blueShards, greenShards map[uint64]bool) (diff string) {
|
||||
nb := len(blueShards)
|
||||
ng := len(greenShards)
|
||||
if nb != ng {
|
||||
diff = fmt.Sprintf("blueShard[%v] count = %v; greenShard[%v] count = %v; ", f.types[0], nb, f.types[1], ng)
|
||||
}
|
||||
bmg := mapDiff(blueShards, greenShards) // get blue - green
|
||||
gmb := mapDiff(greenShards, blueShards) // get green - blue
|
||||
|
||||
if len(bmg) == 0 && len(gmb) == 0 {
|
||||
return ""
|
||||
}
|
||||
diff += fmt.Sprintf("shard diff: blueMinusGreen shards: '%#v'; greenMinusBlue shards: '%#v'", bmg, gmb)
|
||||
return
|
||||
}
|
||||
|
||||
func (f *TxFactory) GetDBShardPath(index string, shard uint64, idx *Index, ty txtype, write bool) (shardPath string, err error) {
|
||||
dbs, err := f.dbPerShard.GetDBShard(index, shard, idx)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -15,393 +15,14 @@
|
|||
package pilosa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
||||
)
|
||||
|
||||
func Test_TxFactory_Qcx_query_context(t *testing.T) {
|
||||
src := CurrentBackend()
|
||||
if src == "rbf" || src == "bolt" {
|
||||
// ok
|
||||
} else {
|
||||
t.Skip("this test only for rbf and bolt")
|
||||
}
|
||||
|
||||
shard := uint64(0)
|
||||
f, idx, tx := mustOpenFragment(t, "i", "f", viewStandard, shard, "")
|
||||
defer f.Clean(t)
|
||||
tx.Rollback()
|
||||
|
||||
barrier := NewBarrier()
|
||||
defer barrier.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
|
||||
setter := func(k int) {
|
||||
for i := 0; ; i++ {
|
||||
barrier.WaitAtGate(0)
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
// add to the group txn on the txf.
|
||||
qcx := idx.holder.txf.NewQcx()
|
||||
|
||||
tx, finisher, err := qcx.GetTx(Txo{Write: true, Index: idx, Shard: f.shard})
|
||||
PanicOn(err)
|
||||
|
||||
// Set bits on the fragment.
|
||||
if _, err := f.setBit(tx, 120, 1); err != nil {
|
||||
panic(err)
|
||||
} else if _, err := f.setBit(tx, 120, 6); err != nil {
|
||||
panic(err)
|
||||
} else if _, err := f.setBit(tx, 121, 0); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// should have two containers set in the fragment.
|
||||
|
||||
// Verify counts on rows.
|
||||
if n := f.mustRow(tx, 120).Count(); n != 2 {
|
||||
panic(fmt.Sprintf("unexpected count: %d", n))
|
||||
} else if n := f.mustRow(tx, 121).Count(); n != 1 {
|
||||
panic(fmt.Sprintf("unexpected count: %d", n))
|
||||
}
|
||||
finisher(nil) // hit the write tx.Commit path
|
||||
// commit the change, and verify it is still there
|
||||
PanicOn(qcx.Finish())
|
||||
qcx.Reset()
|
||||
|
||||
tx, finread, err := qcx.GetTx(Txo{Write: !writable, Index: idx, Fragment: f, Shard: f.shard})
|
||||
PanicOn(err)
|
||||
if n := f.mustRow(tx, 120).Count(); n != 2 {
|
||||
panic(fmt.Sprintf("unexpected count (reopen): %d", n))
|
||||
} else if n := f.mustRow(tx, 121).Count(); n != 1 {
|
||||
panic(fmt.Sprintf("unexpected count (reopen): %d", n))
|
||||
}
|
||||
finread(nil) // no-op on reads that are in a group, so must qcx.Abort() to stop them.
|
||||
qcx.Abort()
|
||||
qcx.Reset()
|
||||
}
|
||||
}
|
||||
N := 1000
|
||||
for i := 0; i < N; i++ {
|
||||
go setter(i)
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
close(done)
|
||||
|
||||
// allow all goro to finish before Closing the lmdb.env, otherwise
|
||||
// we will crash as the goroutines making Tx will try to use the env
|
||||
// after it is closed. It can take quite a while.
|
||||
// one writer might be blocking the other... so ask for only N-2 at first
|
||||
// to avoid deadlock.
|
||||
barrier.BlockUntil(N - 2)
|
||||
barrier.UnblockReaders()
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
// test TxFactory.green2blue
|
||||
//
|
||||
// blue_green starting with an empty or full blue database
|
||||
// should copy all of green (if blue is empty); or if blue is ull,
|
||||
// verify that blue has all the same bits as green.
|
||||
//
|
||||
// Benefits: a) we start with known identical state so our testing/comparisons can be valid;
|
||||
// and b) we have an easy migration mechanism, to go from one storage format to another.
|
||||
//
|
||||
func Test_TxFactory_UpdateBlueFromGreen_OnStartup(t *testing.T) {
|
||||
checked := []string{"roaring", "rbf"}
|
||||
|
||||
expectError := false
|
||||
for _, blue := range checked {
|
||||
for _, green := range checked {
|
||||
if blue == green {
|
||||
continue
|
||||
}
|
||||
if blue == "roaring" {
|
||||
// not supported
|
||||
expectError = true
|
||||
} else {
|
||||
expectError = false
|
||||
}
|
||||
blue_green := blue + "_" + green
|
||||
//vv("setting blue_green to '%v'", blue_green)
|
||||
|
||||
// =============================
|
||||
// Begin setup.
|
||||
//
|
||||
// Setup happens with green only.
|
||||
|
||||
h, path, err := makeHolder(t, green)
|
||||
if err != nil {
|
||||
t.Fatalf("creating holder: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
//vv("path = %v", path)
|
||||
|
||||
// we will manually h.Close() below
|
||||
|
||||
// Write bits to separate indexes.
|
||||
testSetBit(t, h, "i0", "f", 100, 200)
|
||||
testSetBit(t, h, "i1", "f", 100, 200)
|
||||
testSetBit(t, h, "i1", "f", 100, 12345678)
|
||||
|
||||
testOp := testHolderOperator{}
|
||||
ctx := context.Background()
|
||||
err = h.Process(ctx, &testOp)
|
||||
if err != nil {
|
||||
t.Fatalf("processing holder: %v", err)
|
||||
}
|
||||
expected := testHolderOperator{
|
||||
indexSeen: 2, indexProcessed: 2,
|
||||
fieldSeen: 2, fieldProcessed: 2,
|
||||
viewSeen: 2, viewProcessed: 2,
|
||||
fragmentSeen: 3, fragmentProcessed: 3,
|
||||
}
|
||||
if testOp != expected {
|
||||
t.Fatalf("holder processor did not process as expected. expected %#v, got %#v", expected, testOp)
|
||||
}
|
||||
|
||||
// verify data is there
|
||||
rowID := uint64(100)
|
||||
colID := uint64(200)
|
||||
_, _ = rowID, colID
|
||||
testMustHaveBit(t, h, "i0", "f", rowID, colID)
|
||||
testMustHaveBit(t, h, "i1", "f", 100, 200)
|
||||
testMustHaveBit(t, h, "i1", "f", 100, 12345678)
|
||||
|
||||
//vv("about to reopen; blue_green = '%v' but PILOSA_STORAGE_BACKEND='%v'", blue_green, os.Getenv("PILOSA_STORAGE_BACKEND"))
|
||||
//h.DumpAllShards()
|
||||
|
||||
//vv("after dump, about to close")
|
||||
h.Close()
|
||||
|
||||
//vv("after close, about to re-open")
|
||||
|
||||
// can we re.Open the same holder h? hopefully without a problem.
|
||||
PanicOn(h.Open())
|
||||
|
||||
//vv("h.Open() re-open worked; blue_green = '%v'; dump; with PILOSA_STORAGE_BACKEND='%v'", blue_green, os.Getenv("PILOSA_STORAGE_BACKEND"))
|
||||
//h.DumpAllShards()
|
||||
|
||||
testMustHaveBit(t, h, "i0", "f", rowID, colID) // panic here, colID 200 bit was cold.
|
||||
testMustHaveBit(t, h, "i1", "f", 100, 200)
|
||||
testMustHaveBit(t, h, "i1", "f", 100, 12345678)
|
||||
h.Close()
|
||||
|
||||
//vv("successful re-open and then Close again of h.")
|
||||
|
||||
// check that we can open a NewHolder on green, on same path, and still see our bits.
|
||||
// Because the NewHolder is the code that creates and configures TxFactory as blue_green.
|
||||
cfg := mustHolderConfig()
|
||||
cfg.StorageConfig.Backend = green
|
||||
h2 := NewHolder(path, cfg)
|
||||
PanicOn(h2.Open())
|
||||
|
||||
testMustHaveBit(t, h2, "i0", "f", rowID, colID)
|
||||
testMustHaveBit(t, h2, "i1", "f", 100, 200)
|
||||
testMustHaveBit(t, h2, "i1", "f", 100, 12345678)
|
||||
h2.Close()
|
||||
|
||||
// verify that blue does not have it.
|
||||
// open a new holder on path, just looking at blue.
|
||||
cfg = mustHolderConfig()
|
||||
cfg.StorageConfig.Backend = blue
|
||||
h3 := NewHolder(path, cfg)
|
||||
PanicOn(h3.Open())
|
||||
|
||||
testMustNotHaveBit(t, h3, "i0", "f", rowID, colID)
|
||||
testMustNotHaveBit(t, h3, "i1", "f", 100, 200)
|
||||
testMustNotHaveBit(t, h3, "i1", "f", 100, 12345678)
|
||||
|
||||
h3.Close()
|
||||
|
||||
// =============================
|
||||
// Setup done. On to actual test.
|
||||
|
||||
// Opening in blue_green mode means that once Holder.Open()
|
||||
// returns without error, the blue and green databases are
|
||||
// identical.
|
||||
// Since blue is empty, the blue database will get synched up
|
||||
// with the green during Holder.Open().
|
||||
|
||||
// open a holder with path again, now looking at both blue and green.
|
||||
// The Holder.Open should do the migration from green, populating blue.
|
||||
cfg = mustHolderConfig()
|
||||
cfg.StorageConfig.Backend = blue_green
|
||||
h4 := NewHolder(path, cfg)
|
||||
|
||||
//vv("about to h4.Open we should populate blue from green")
|
||||
err = h4.Open()
|
||||
if expectError {
|
||||
if err == nil {
|
||||
panic("expected error since migration to roaring not supported")
|
||||
}
|
||||
} else {
|
||||
PanicOn(err)
|
||||
}
|
||||
|
||||
testMustHaveBit(t, h4, "i0", "f", rowID, colID)
|
||||
testMustHaveBit(t, h4, "i1", "f", 100, 200)
|
||||
testMustHaveBit(t, h4, "i1", "f", 100, 12345678)
|
||||
|
||||
//vv("successfully verified populatingBlueFromGreen with blue_green = '%v'", blue_green)
|
||||
h4.Close()
|
||||
os.RemoveAll(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test the situation where we startup blue_green with existing data and
|
||||
// go to verify it but blue has more data than green.
|
||||
// That will also cause query divergence.
|
||||
func Test_TxFactory_verifyBlueEqualsGreen(t *testing.T) {
|
||||
checked := []string{"roaring", "bolt", "rbf"}
|
||||
|
||||
for _, blue := range checked {
|
||||
for _, green := range checked {
|
||||
if blue == green {
|
||||
continue
|
||||
}
|
||||
if blue == "roaring" {
|
||||
// not supported
|
||||
continue
|
||||
}
|
||||
blue_green := blue + "_" + green
|
||||
|
||||
// =============================
|
||||
// Begin setup.
|
||||
//
|
||||
// Setup happens with green only.
|
||||
|
||||
h, path, err := makeHolder(t, green)
|
||||
if err != nil {
|
||||
t.Fatalf("creating holder: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(path)
|
||||
|
||||
//vv("on green, which is '%v'", green)
|
||||
// we will manually h.Close() below
|
||||
|
||||
// Write bits to separate indexes.
|
||||
testSetBit(t, h, "i0", "f", 100, 200)
|
||||
testSetBit(t, h, "i1", "f", 100, 200)
|
||||
testSetBit(t, h, "i1", "f", 100, 12345678)
|
||||
|
||||
testOp := testHolderOperator{}
|
||||
ctx := context.Background()
|
||||
err = h.Process(ctx, &testOp)
|
||||
if err != nil {
|
||||
t.Fatalf("processing holder: %v", err)
|
||||
}
|
||||
expected := testHolderOperator{
|
||||
indexSeen: 2, indexProcessed: 2,
|
||||
fieldSeen: 2, fieldProcessed: 2,
|
||||
viewSeen: 2, viewProcessed: 2,
|
||||
fragmentSeen: 3, fragmentProcessed: 3,
|
||||
}
|
||||
if testOp != expected {
|
||||
t.Fatalf("holder processor did not process as expected. expected %#v, got %#v", expected, testOp)
|
||||
}
|
||||
|
||||
// verify data is there
|
||||
rowID := uint64(100)
|
||||
colID := uint64(200)
|
||||
_, _ = rowID, colID
|
||||
testMustHaveBit(t, h, "i0", "f", rowID, colID)
|
||||
testMustHaveBit(t, h, "i1", "f", 100, 200)
|
||||
testMustHaveBit(t, h, "i1", "f", 100, 12345678)
|
||||
|
||||
h.Close()
|
||||
|
||||
// verify that blue does not have it.
|
||||
// open a new holder on path, just looking at blue.
|
||||
|
||||
//vv("on blue, which is '%v'", blue)
|
||||
|
||||
cfg := mustHolderConfig()
|
||||
cfg.StorageConfig.Backend = blue
|
||||
h3 := NewHolder(path, cfg)
|
||||
PanicOn(h3.Open())
|
||||
|
||||
testMustNotHaveBit(t, h3, "i0", "f", rowID, colID)
|
||||
testMustNotHaveBit(t, h3, "i1", "f", 100, 200)
|
||||
testMustNotHaveBit(t, h3, "i1", "f", 100, 12345678)
|
||||
|
||||
h3.Close()
|
||||
|
||||
// =============================
|
||||
// Setup done. On to actual test.
|
||||
|
||||
// Opening in blue_green mode means that once Holder.Open()
|
||||
// returns without error, the blue and green databases are
|
||||
// identical.
|
||||
// Since blue is empty, the blue database will get synched up
|
||||
// with the green during Holder.Open().
|
||||
|
||||
//vv("on blue_green, which is '%v'", blue_green)
|
||||
|
||||
// open a holder with path again, now looking at both blue and green.
|
||||
// The Holder.Open should do the migration from green, populating blue.
|
||||
cfg = mustHolderConfig()
|
||||
cfg.StorageConfig.Backend = blue_green
|
||||
h4 := NewHolder(path, cfg)
|
||||
PanicOn(h4.Open())
|
||||
|
||||
testMustHaveBit(t, h4, "i0", "f", rowID, colID)
|
||||
testMustHaveBit(t, h4, "i1", "f", 100, 200)
|
||||
testMustHaveBit(t, h4, "i1", "f", 100, 12345678)
|
||||
h4.Close()
|
||||
|
||||
// now open just blue, and add a bit to a new index, i2.
|
||||
//vv("on blue, which is '%v'", blue)
|
||||
cfg = mustHolderConfig()
|
||||
cfg.StorageConfig.Backend = blue
|
||||
h5 := NewHolder(path, cfg)
|
||||
PanicOn(h5.Open())
|
||||
testSetBit(t, h5, "i2", "f", 500, 777)
|
||||
|
||||
//vv("after adding a bit to blue, we have:")
|
||||
//h5.DumpAllShards()
|
||||
|
||||
h5.Close()
|
||||
|
||||
// now open blue_green. should get a verification failure
|
||||
// due to the extra bit in blue.
|
||||
|
||||
// BEGIN verficiation that should ERROR out b/c blue has more data.
|
||||
|
||||
// open a holder with path again, now looking at both blue and green.
|
||||
// The Holder.Open should verify blue against green and notice the extra bit.
|
||||
cfg = mustHolderConfig()
|
||||
cfg.StorageConfig.Backend = blue_green
|
||||
h6 := NewHolder(path, cfg)
|
||||
err = h6.Open()
|
||||
//h6.DumpAllShards()
|
||||
|
||||
if err == nil {
|
||||
h6.Close()
|
||||
t.Fatalf("should have had blue-green verification fail on Holder.Open")
|
||||
}
|
||||
|
||||
h6.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_TxFactory_verifyStringConstantsMatch(t *testing.T) {
|
||||
// txtype.String() method MUST return strings that match
|
||||
// our const definitions at the top of txfactory.go, or
|
||||
// else blue-green transactions cannot determine when
|
||||
// the second transaction is being released in dbshard.go.
|
||||
check := []txtype{roaringTxn, rbfTxn, boltTxn}
|
||||
expect := []string{RoaringTxn, RBFTxn, BoltTxn}
|
||||
// our const definitions at the top of txfactory.go.
|
||||
check := []txtype{roaringTxn, rbfTxn}
|
||||
expect := []string{RoaringTxn, RBFTxn}
|
||||
for i, chk := range check {
|
||||
obs := chk.String()
|
||||
if obs != expect[i] {
|
||||
|
|
|
|||
230
util.go
230
util.go
|
|
@ -17,19 +17,12 @@ package pilosa
|
|||
// util.go: a place for generic, reusable utilities.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/molecula/featurebase/v2/roaring"
|
||||
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
|
@ -63,229 +56,6 @@ func NilInside(iface interface{}) bool {
|
|||
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]
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
sort.Ints(r)
|
||||
return
|
||||
}
|
||||
|
||||
func asInts(a []uint64) (r []int) {
|
||||
r = make([]int, len(a))
|
||||
for i, v := range a {
|
||||
r[i] = int(v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
var _ = zeroKeyContainerAsString // happy linter
|
||||
|
||||
// for debugging
|
||||
func zeroKeyContainerAsString(ct *roaring.Container) (r string) {
|
||||
cts := roaring.NewSliceContainers()
|
||||
cts.Put(0, ct)
|
||||
rbm := &roaring.Bitmap{Containers: cts}
|
||||
r = fmt.Sprintf("[%v]:", containerTypeNames[roaring.ContainerType(ct)]) + BitmapAsString(rbm)
|
||||
return
|
||||
}
|
||||
|
||||
var containerTypeNames = map[byte]string{
|
||||
roaring.ContainerArray: "array",
|
||||
roaring.ContainerBitmap: "bitmap",
|
||||
roaring.ContainerRun: "run",
|
||||
}
|
||||
|
||||
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 + ")"
|
||||
}
|
||||
|
||||
// fromArray16 converts to an 8KB page
|
||||
func fromArray16(a []uint16) []byte {
|
||||
if len(a) == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
if len(a) > 4096 {
|
||||
PanicOn(fmt.Sprintf("cannot put more than 4096 integers into an array container: %v too big", len(a)))
|
||||
}
|
||||
return (*[8192]byte)(unsafe.Pointer(&a[0]))[: len(a)*2 : len(a)*2]
|
||||
}
|
||||
|
||||
// fromArray64 converts to an 8KB page
|
||||
func fromArray64(a []uint64) []byte {
|
||||
if len(a) == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
return (*[8192]byte)(unsafe.Pointer(&a[0]))[:8192:8192]
|
||||
}
|
||||
|
||||
// fromInterval16 converts to 8KB page
|
||||
func fromInterval16(a []roaring.Interval16) []byte {
|
||||
if len(a) == 0 {
|
||||
return []byte{}
|
||||
}
|
||||
if len(a) > 2048 {
|
||||
PanicOn(fmt.Sprintf("cannot put more than 2048 roaring.Interval16 into a container: %v too big", len(a)))
|
||||
}
|
||||
return (*[8192]byte)(unsafe.Pointer(&a[0]))[: len(a)*4 : len(a)*4]
|
||||
}
|
||||
|
||||
// DiskUse reports the total bytes uses by all files under root
|
||||
// that match requiredSuffix. requiredSuffix can be empty string.
|
||||
// Space used by directories is not counted.
|
||||
func DiskUse(root string, requiredSuffix string) (tot int, err error) {
|
||||
if !DirExists(root) {
|
||||
return -1, fmt.Errorf("listFilesUnderDir error: root directory '%v' not found", root)
|
||||
}
|
||||
|
||||
err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil {
|
||||
PanicOn(fmt.Sprintf("info was nil for path = '%v'", path))
|
||||
}
|
||||
if info.IsDir() {
|
||||
// skip the size of directories themselves, only summing files.
|
||||
} else {
|
||||
sz := info.Size()
|
||||
if requiredSuffix == "" || strings.HasSuffix(path, requiredSuffix) {
|
||||
tot += int(sz)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// rootDir must exist. Return the size in bytes of the largest sub-directory
|
||||
// that has the required suffix. The largestSize is from DiskUse() called
|
||||
// on the sub-dir. DiskUse only counts file size, nothing for directory inodes.
|
||||
func SubdirLargestDirWithSuffix(rootDir, requiredDirSuffix string) (exists bool, largestSize int, err error) {
|
||||
if !DirExists(rootDir) {
|
||||
return false, -1, fmt.Errorf("SubdirExistsWithSuffix error: root directory '%v' not found", rootDir)
|
||||
}
|
||||
|
||||
err = filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if info == nil {
|
||||
PanicOn(fmt.Sprintf("info was nil for path = '%v'", path))
|
||||
}
|
||||
|
||||
if info.IsDir() && strings.HasSuffix(path, requiredDirSuffix) {
|
||||
exists = true
|
||||
size, err := DiskUse(path, "")
|
||||
if err != nil {
|
||||
// disk error? report it
|
||||
return err
|
||||
}
|
||||
if size > largestSize {
|
||||
largestSize = size
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return exists, -1, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// called by Holder.hasRoaringData()
|
||||
func roaringFragmentHasData(path string, index, field, view string, shard uint64) (hasData bool, err error) {
|
||||
|
||||
|
|
|
|||
|
|
@ -15,47 +15,17 @@
|
|||
package pilosa
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pnet "github.com/molecula/featurebase/v2/net"
|
||||
"github.com/molecula/featurebase/v2/roaring"
|
||||
"github.com/molecula/featurebase/v2/testhook"
|
||||
"github.com/molecula/featurebase/v2/topology"
|
||||
. "github.com/molecula/featurebase/v2/vprint" // nolint:staticcheck
|
||||
)
|
||||
|
||||
// utilities used by tests
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// NewTestCluster returns a cluster with n nodes and uses a mod-based hasher.
|
||||
func NewTestCluster(tb testing.TB, n int) *cluster {
|
||||
path, err := testhook.TempDir(tb, "pilosa-cluster-")
|
||||
|
|
|
|||
16
view.go
16
view.go
|
|
@ -158,26 +158,28 @@ func (v *view) openWithShardSet(ss *shardSet) error {
|
|||
if nGoro < 4 {
|
||||
nGoro = 4
|
||||
}
|
||||
pj := newParallelJobs(nGoro)
|
||||
var eg errgroup.Group
|
||||
throttle := make(chan struct{}, nGoro)
|
||||
|
||||
for i := range frags {
|
||||
// create a new variable frag on each time through
|
||||
// the loop (instead of i, frag := range frags)
|
||||
// so that the closure run on the
|
||||
// goroutine has its own variable.
|
||||
frag := frags[i]
|
||||
accepted := pj.run(func(worker int) error {
|
||||
throttle <- struct{}{}
|
||||
eg.Go(func() error {
|
||||
defer func() {
|
||||
<-throttle
|
||||
}()
|
||||
if err := frag.Open(); err != nil {
|
||||
return fmt.Errorf("open fragment: shard=%d, err=%s", frag.shard, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if !accepted {
|
||||
// have error/shutting down the pj, so stop
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err := pj.waitForFinish()
|
||||
err := eg.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue