mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
409 lines
12 KiB
Go
409 lines
12 KiB
Go
// 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 (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
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}
|
|
for i, chk := range check {
|
|
obs := chk.String()
|
|
if obs != expect[i] {
|
|
t.Fatalf("expected '%v' but got '%v'", expect[i], obs)
|
|
}
|
|
}
|
|
}
|