featurebase/txfactory_internal_test.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)
}
}
}