mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-05 16:15:56 +00:00
This is a lot more complex than it sounds like it will be. We shut down the cache flush when a holder is closed, but if you're deleting an index, we don't check for that, and can have a cache flush still creating cache files in an index which could conceivably result in os.RemoteAll() failing. This shouldn't happen often, but it's happened at least once. To address this, first, we make sure that every tier of this operation bails as quickly as it can after the thing it's working on closes. Second, we retry RemoveAll. Unfortunately, some things get reopened, so we have to handle that, have mutexes covering the access to the channel, and so on. Also, some things were getting double-closed, which was previously harmless but could now fail. So, first, catch all the existing double-closes and remove them, second, make the double-close fail with an error. Note that virtually none of the tests check for errors on close. This passes tests and should be unable to hit the original problem. Unfortunately, it's unreasonably hard to check that, because it requires an incredible coincidence of timing on the delete aligning with a cache flush.
613 lines
20 KiB
Go
613 lines
20 KiB
Go
// Copyright 2021 Molecula Corp. All rights reserved.
|
|
package pilosa_test
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
pilosa "github.com/molecula/featurebase/v3"
|
|
"github.com/molecula/featurebase/v3/disco"
|
|
"github.com/molecula/featurebase/v3/pql"
|
|
"github.com/molecula/featurebase/v3/test"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// mustHolderConfig provides a default test-friendly holder config.
|
|
func mustHolderConfig() *pilosa.HolderConfig {
|
|
cfg := pilosa.DefaultHolderConfig()
|
|
cfg.StorageConfig.Backend = "rbf"
|
|
cfg.StorageConfig.FsyncEnabled = false
|
|
cfg.RBFConfig.FsyncEnabled = false
|
|
cfg.Schemator = disco.InMemSchemator
|
|
cfg.Sharder = disco.InMemSharder
|
|
return cfg
|
|
}
|
|
|
|
func TestHolder_Open(t *testing.T) {
|
|
t.Run("ErrIndexPermission", func(t *testing.T) {
|
|
if os.Geteuid() == 0 {
|
|
t.Skip("Skipping permissions test since user is root.")
|
|
}
|
|
h := test.MustOpenHolder(t)
|
|
// no automatic close here, because we manually close this, and then
|
|
// *fail* to reopen it.
|
|
|
|
if _, err := h.CreateIndex("test", pilosa.IndexOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
} else if err := h.Close(); err != nil {
|
|
t.Fatal(err)
|
|
} else if err := os.Chmod(h.IndexPath("test"), 0000); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() {
|
|
_ = os.Chmod(h.IndexPath("test"), 0755)
|
|
}()
|
|
|
|
if err := h.Reopen(); err == nil || !strings.Contains(err.Error(), "permission denied") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
t.Run("ForeignIndex", func(t *testing.T) {
|
|
t.Run("ErrForeignIndexNotFound", func(t *testing.T) {
|
|
h := test.MustOpenHolder(t)
|
|
defer h.Close()
|
|
|
|
if idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
_, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(0, 100), pilosa.OptFieldForeignIndex("nonexistent"))
|
|
if err == nil {
|
|
t.Fatalf("expected error: %s", pilosa.ErrForeignIndexNotFound)
|
|
} else if errors.Cause(err) != pilosa.ErrForeignIndexNotFound {
|
|
t.Fatalf("expected error: %s, but got: %s", pilosa.ErrForeignIndexNotFound, err)
|
|
}
|
|
}
|
|
})
|
|
|
|
// Foreign index zzz is opened after foo/bar.
|
|
t.Run("ForeignIndexNotOpenYet", func(t *testing.T) {
|
|
h := test.MustOpenHolder(t)
|
|
defer h.Close()
|
|
|
|
if _, err := h.CreateIndex("zzz", pilosa.IndexOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
} else if idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
} else if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(0, 100), pilosa.OptFieldForeignIndex("zzz")); err != nil {
|
|
t.Fatal(err)
|
|
} else if err := h.Holder.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := h.Reopen(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
// Foreign index aaa is opened before foo/bar.
|
|
t.Run("ForeignIndexIsOpen", func(t *testing.T) {
|
|
h := test.MustOpenHolder(t)
|
|
defer h.Close()
|
|
|
|
if _, err := h.CreateIndex("aaa", pilosa.IndexOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
} else if idx, err := h.CreateIndex("foo", pilosa.IndexOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
} else if _, err := idx.CreateField("bar", pilosa.OptFieldTypeInt(0, 100), pilosa.OptFieldForeignIndex("aaa")); err != nil {
|
|
t.Fatal(err)
|
|
} else if err := h.Holder.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := h.Reopen(); err != nil {
|
|
t.Fatalf("unexpected error: %s", err)
|
|
}
|
|
})
|
|
|
|
// Try to re-create existing index
|
|
t.Run("CreateIndexIfNotExists", func(t *testing.T) {
|
|
h := test.MustOpenHolder(t)
|
|
defer h.Close()
|
|
|
|
idx1, err := h.CreateIndexIfNotExists("aaa", pilosa.IndexOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err = h.CreateIndex("aaa", pilosa.IndexOptions{}); err == nil {
|
|
t.Fatalf("expected: ConflictError, got: nil")
|
|
} else if _, ok := err.(pilosa.ConflictError); !ok {
|
|
t.Fatalf("expected: ConflictError, got: %s", err)
|
|
}
|
|
|
|
idx2, err := h.CreateIndexIfNotExists("aaa", pilosa.IndexOptions{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if idx1 != idx2 {
|
|
t.Fatalf("expected the same indexes, got: %s and %s", idx1.Name(), idx2.Name())
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestHolder_HasData(t *testing.T) {
|
|
t.Run("IndexDirectory", func(t *testing.T) {
|
|
h := test.MustOpenHolder(t)
|
|
defer h.Close()
|
|
|
|
if ok, err := h.HasData(); ok || err != nil {
|
|
t.Fatal("expected HasData to return false, no err, but", ok, err)
|
|
}
|
|
|
|
if _, err := h.CreateIndex("test", pilosa.IndexOptions{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if ok, err := h.HasData(); !ok || err != nil {
|
|
t.Fatal("expected HasData to return true, but ", ok, err)
|
|
}
|
|
})
|
|
|
|
t.Run("Peek", func(t *testing.T) {
|
|
h := test.MustOpenHolder(t)
|
|
defer h.Close()
|
|
|
|
if ok, err := h.HasData(); ok || err != nil {
|
|
t.Fatal("expected HasData to return false, no err, but", ok, err)
|
|
}
|
|
|
|
// Create an index directory to indicate data exists.
|
|
if err := os.Mkdir(h.IndexPath("test"), 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if ok, err := h.HasData(); !ok || err != nil {
|
|
t.Fatal("expected HasData to return true, no err, but", ok, err)
|
|
}
|
|
})
|
|
|
|
t.Run("Peek at missing directory", func(t *testing.T) {
|
|
// Ensure that hasData is false when dir doesn't exist.
|
|
|
|
// Note that we are intentionally not using test.NewHolder,
|
|
// because we want to create a Holder object with an invalid path,
|
|
// rather than creating a valid holder with a temporary path.
|
|
h := pilosa.NewHolder("bad-path", mustHolderConfig())
|
|
|
|
if ok, err := h.HasData(); ok || err != nil {
|
|
t.Fatal("expected HasData to return false, no err, but", ok, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Ensure holder can delete an index and its underlying files.
|
|
func TestHolder_DeleteIndex(t *testing.T) {
|
|
|
|
hldr := test.MustOpenHolder(t)
|
|
defer hldr.Close()
|
|
|
|
// Write bits to separate indexes.
|
|
hldr.SetBit("i0", "f", 100, 200)
|
|
hldr.SetBit("i1", "f", 100, 200)
|
|
|
|
// Ensure i0 exists.
|
|
if _, err := os.Stat(hldr.IndexPath("i0")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Delete i0.
|
|
if err := hldr.DeleteIndex("i0"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Ensure i0 files are removed & i1 still exists.
|
|
if _, err := os.Stat(hldr.IndexPath("i0")); !os.IsNotExist(err) {
|
|
t.Fatal("expected i0 file deletion")
|
|
} else if _, err := os.Stat(hldr.IndexPath("i1")); err != nil {
|
|
t.Fatal("expected i1 files to still exist", err)
|
|
}
|
|
}
|
|
|
|
// Ensure holder can sync with a remote holder.
|
|
func TestHolderSyncer_SyncHolder(t *testing.T) {
|
|
c := test.MustNewCluster(t, 2)
|
|
c.GetIdleNode(0).Config.Cluster.ReplicaN = 2
|
|
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
|
|
c.GetIdleNode(1).Config.Cluster.ReplicaN = 2
|
|
c.GetIdleNode(1).Config.AntiEntropy.Interval = 0
|
|
err := c.Start()
|
|
|
|
if err != nil {
|
|
t.Fatalf("starting cluster: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
|
|
if err != nil {
|
|
t.Fatalf("creating index i: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "y", pilosa.IndexOptions{})
|
|
if err != nil {
|
|
t.Fatalf("creating index y: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
|
|
if err != nil {
|
|
t.Fatalf("creating field f: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f0", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
|
|
if err != nil {
|
|
t.Fatalf("creating field f0: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "y", "z", pilosa.OptFieldTypeMutex(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
|
|
if err != nil {
|
|
t.Fatalf("creating field z in y: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "y", "b", pilosa.OptFieldTypeBool())
|
|
if err != nil {
|
|
t.Fatalf("creating field b in y: %v", err)
|
|
}
|
|
|
|
hldr0 := &test.Holder{Holder: c.GetNode(0).Server.Holder()}
|
|
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
|
|
|
|
// Set data on the local holder.
|
|
hldr0.SetBit("i", "f", 0, 10)
|
|
hldr0.SetBit("i", "f", 2, 20)
|
|
hldr0.SetBit("i", "f", 120, 10)
|
|
hldr0.SetBit("i", "f", 200, 4)
|
|
|
|
hldr0.SetBit("i", "f0", 9, ShardWidth+5)
|
|
|
|
// Set a bit to create the fragment.
|
|
hldr0.SetBit("y", "z", 0, 0)
|
|
hldr0.SetBit("y", "b", 0, 0) // rowID = 0 means false
|
|
|
|
// Set data on the remote holder.
|
|
hldr1.SetBit("i", "f", 0, 4000)
|
|
hldr1.SetBit("i", "f", 3, 10)
|
|
hldr1.SetBit("i", "f", 120, 10)
|
|
|
|
hldr1.SetBit("y", "z", 10, (3*ShardWidth)+4)
|
|
hldr1.SetBit("y", "z", 10, (3*ShardWidth)+5)
|
|
hldr1.SetBit("y", "z", 10, (3*ShardWidth)+7)
|
|
|
|
hldr1.SetBit("y", "b", 1, (3*ShardWidth)+4) // true
|
|
hldr1.SetBit("y", "b", 0, (3*ShardWidth)+5) // false
|
|
hldr1.SetBit("y", "b", 1, (3*ShardWidth)+7) // true
|
|
|
|
err = c.GetNode(0).Server.SyncData()
|
|
if err != nil {
|
|
t.Fatalf("syncing node 0: %v", err)
|
|
}
|
|
err = c.GetNode(1).Server.SyncData()
|
|
if err != nil {
|
|
t.Fatalf("syncing node 1: %v", err)
|
|
}
|
|
|
|
// Verify data is the same on both nodes.
|
|
for i, hldr := range []*test.Holder{hldr0, hldr1} {
|
|
if a := hldr.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{10, 4000}) {
|
|
t.Errorf("unexpected columns(%d/0): %+v", i, a)
|
|
}
|
|
if a := hldr.Row("i", "f", 2).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
|
|
t.Errorf("unexpected columns(%d/2): %+v", i, a)
|
|
}
|
|
if a := hldr.Row("i", "f", 3).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
|
|
t.Errorf("unexpected columns(%d/3): %+v", i, a)
|
|
}
|
|
if a := hldr.Row("i", "f", 120).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
|
|
t.Errorf("unexpected columns(%d/120): %+v", i, a)
|
|
}
|
|
if a := hldr.Row("i", "f", 200).Columns(); !reflect.DeepEqual(a, []uint64{4}) {
|
|
t.Errorf("unexpected columns(%d/200): %+v", i, a)
|
|
}
|
|
|
|
if a := hldr.Row("i", "f0", 9).Columns(); !reflect.DeepEqual(a, []uint64{ShardWidth + 5}) {
|
|
t.Errorf("unexpected columns(%d/d/f0): %+v", i, a)
|
|
}
|
|
|
|
if a := hldr.Row("y", "z", 10).Columns(); !reflect.DeepEqual(a, []uint64{(3 * ShardWidth) + 4, (3 * ShardWidth) + 5, (3 * ShardWidth) + 7}) {
|
|
t.Errorf("unexpected columns(%d/y/z): %+v", i, a)
|
|
}
|
|
|
|
if a := hldr.Row("y", "b", 0).Columns(); !reflect.DeepEqual(a, []uint64{0, (3 * ShardWidth) + 5}) {
|
|
t.Errorf("unexpected false columns(%d/y/b): %+v", i, a)
|
|
}
|
|
if a := hldr.Row("y", "b", 1).Columns(); !reflect.DeepEqual(a, []uint64{(3 * ShardWidth) + 4, (3 * ShardWidth) + 7}) {
|
|
t.Errorf("unexpected true columns(%d/y/b): %+v", i, a)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure holder can sync with a remote holder and respects
|
|
// the row boundaries of the block.
|
|
func TestHolderSyncer_BlockIteratorLimits(t *testing.T) {
|
|
c := test.MustNewCluster(t, 3)
|
|
c.GetIdleNode(0).Config.Cluster.ReplicaN = 3
|
|
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
|
|
c.GetIdleNode(1).Config.Cluster.ReplicaN = 3
|
|
c.GetIdleNode(1).Config.AntiEntropy.Interval = 0
|
|
c.GetIdleNode(2).Config.Cluster.ReplicaN = 3
|
|
c.GetIdleNode(2).Config.AntiEntropy.Interval = 0
|
|
err := c.Start()
|
|
if err != nil {
|
|
t.Fatalf("starting cluster: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
|
|
if err != nil {
|
|
t.Fatalf("creating index i: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
|
|
if err != nil {
|
|
t.Fatalf("creating field f: %v", err)
|
|
}
|
|
|
|
blockEdge := uint64(pilosa.HashBlockSize)
|
|
|
|
hldr0 := &test.Holder{Holder: c.GetNode(0).Server.Holder()}
|
|
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
|
|
hldr2 := &test.Holder{Holder: c.GetNode(2).Server.Holder()}
|
|
|
|
// Set data on the local holder.
|
|
hldr0.SetBit("i", "f", blockEdge-1, 10)
|
|
hldr0.SetBit("i", "f", blockEdge, 20)
|
|
|
|
// Set the same data on one of the replicas
|
|
// so that we have a quorum.
|
|
hldr1.SetBit("i", "f", blockEdge-1, 10)
|
|
hldr1.SetBit("i", "f", blockEdge, 20)
|
|
|
|
// Leave the third replica empty to force a block merge.
|
|
//
|
|
err = c.GetNode(0).Server.SyncData()
|
|
if err != nil {
|
|
t.Fatalf("syncing node 0: %v", err)
|
|
}
|
|
|
|
// Verify data is the same on all nodes.
|
|
for i, hldr := range []*test.Holder{hldr0, hldr1, hldr2} {
|
|
if a := hldr.Row("i", "f", blockEdge-1).Columns(); !reflect.DeepEqual(a, []uint64{10}) {
|
|
t.Errorf("unexpected columns(%d/block 0): %+v", i, a)
|
|
}
|
|
if a := hldr.Row("i", "f", blockEdge).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
|
|
t.Errorf("unexpected columns(%d/block 1): %+v", i, a)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure holder correctly handles clears during block sync.
|
|
func TestHolderSyncer_Clears(t *testing.T) {
|
|
c := test.MustNewCluster(t, 3)
|
|
c.GetIdleNode(0).Config.Cluster.ReplicaN = 3
|
|
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
|
|
c.GetIdleNode(1).Config.Cluster.ReplicaN = 3
|
|
c.GetIdleNode(1).Config.AntiEntropy.Interval = 0
|
|
c.GetIdleNode(2).Config.Cluster.ReplicaN = 3
|
|
c.GetIdleNode(2).Config.AntiEntropy.Interval = 0
|
|
err := c.Start()
|
|
if err != nil {
|
|
t.Fatalf("starting cluster: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
|
|
if err != nil {
|
|
t.Fatalf("creating index i: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeSet(pilosa.DefaultCacheType, pilosa.DefaultCacheSize))
|
|
if err != nil {
|
|
t.Fatalf("creating field f: %v", err)
|
|
}
|
|
|
|
hldr0 := &test.Holder{Holder: c.GetNode(0).Server.Holder()}
|
|
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
|
|
hldr2 := &test.Holder{Holder: c.GetNode(2).Server.Holder()}
|
|
|
|
// Set data on the local holder that should be cleared
|
|
// because it's the only instance of this value.
|
|
hldr0.SetBit("i", "f", 0, 30)
|
|
|
|
// Set similar data on the replicas, but
|
|
// different from what's on local. This should end
|
|
// up being set on all replicas
|
|
hldr1.SetBit("i", "f", 0, 20)
|
|
hldr2.SetBit("i", "f", 0, 20)
|
|
|
|
err = c.GetNode(0).Server.SyncData()
|
|
if err != nil {
|
|
t.Fatalf("syncing node 0: %v", err)
|
|
}
|
|
|
|
// Verify data is the same on all nodes.
|
|
for i, hldr := range []*test.Holder{hldr0, hldr1, hldr2} {
|
|
if a := hldr.Row("i", "f", 0).Columns(); !reflect.DeepEqual(a, []uint64{20}) {
|
|
t.Errorf("unexpected columns(%d): %+v", i, a)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure holder can sync time quantum views with a remote holder.
|
|
func TestHolderSyncer_TimeQuantum(t *testing.T) {
|
|
c := test.MustNewCluster(t, 2)
|
|
c.GetIdleNode(0).Config.Cluster.ReplicaN = 2
|
|
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
|
|
c.GetIdleNode(1).Config.Cluster.ReplicaN = 2
|
|
c.GetIdleNode(1).Config.AntiEntropy.Interval = 0
|
|
err := c.Start()
|
|
if err != nil {
|
|
t.Fatalf("starting cluster: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
quantum := "D"
|
|
|
|
_, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
|
|
if err != nil {
|
|
t.Fatalf("creating index i: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeTime(pilosa.TimeQuantum(quantum), "0"))
|
|
if err != nil {
|
|
t.Fatalf("creating field f: %v", err)
|
|
}
|
|
|
|
hldr0 := &test.Holder{Holder: c.GetNode(0).Server.Holder()}
|
|
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
|
|
|
|
// Set data on the local holder for node0.
|
|
t1 := time.Date(2018, 8, 1, 12, 30, 0, 0, time.UTC)
|
|
t2 := time.Date(2018, 8, 2, 12, 30, 0, 0, time.UTC)
|
|
hldr0.SetBitTime("i", "f", 0, 1, &t1)
|
|
hldr0.SetBitTime("i", "f", 0, 2, &t2)
|
|
|
|
// Set data on node1.
|
|
hldr1.SetBitTime("i", "f", 0, 22, &t2)
|
|
|
|
err = c.GetNode(0).Server.SyncData()
|
|
if err != nil {
|
|
t.Fatalf("syncing node 0: %v", err)
|
|
}
|
|
|
|
// Verify data is the same on both nodes.
|
|
for i, hldr := range []*test.Holder{hldr0, hldr1} {
|
|
if a := hldr.RowTime("i", "f", 0, t1, quantum).Columns(); !reflect.DeepEqual(a, []uint64{1}) {
|
|
t.Errorf("unexpected columns(%d/0): %+v", i, a)
|
|
}
|
|
if a := hldr.RowTime("i", "f", 0, t2, quantum).Columns(); !reflect.DeepEqual(a, []uint64{2, 22}) {
|
|
t.Errorf("unexpected columns(%d/0): %+v", i, a)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure holder can sync integer views with a remote holder.
|
|
func TestHolderSyncer_IntField(t *testing.T) {
|
|
t.Run("BasicSync", func(t *testing.T) {
|
|
c := test.MustNewCluster(t, 2)
|
|
c.GetIdleNode(0).Config.Cluster.ReplicaN = 2
|
|
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
|
|
c.GetIdleNode(1).Config.Cluster.ReplicaN = 2
|
|
c.GetIdleNode(1).Config.AntiEntropy.Interval = 0
|
|
err := c.Start()
|
|
if err != nil {
|
|
t.Fatalf("starting cluster: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
var idx0 *pilosa.Index
|
|
idx0, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
|
|
_ = idx0
|
|
if err != nil {
|
|
t.Fatalf("creating index i: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeInt(0, 100))
|
|
if err != nil {
|
|
t.Fatalf("creating field f: %v", err)
|
|
}
|
|
|
|
hldr0 := &test.Holder{Holder: c.GetNode(0).Server.Holder()}
|
|
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
|
|
|
|
// Set data on the local holder for node0. columnID=1, value=1
|
|
hldr0.SetValue("i", "f", 1, 1)
|
|
|
|
// in c0 expect the 1 bit
|
|
|
|
// Set data on node1. columnID=2, value=2
|
|
idx1 := hldr1.SetValue("i", "f", 2, 2)
|
|
_ = idx1
|
|
|
|
err = c.GetNode(0).Server.SyncData()
|
|
if err != nil {
|
|
t.Fatalf("syncing node 0: %v", err)
|
|
}
|
|
|
|
// expect 3 rows, the 1 bit + 2 rows for the 2 value as BSI. But, we only see that c0 overwrote c1.
|
|
|
|
// Problem is: data at c1 was replaced by c0, instead of being merged with existing c1.
|
|
// Problem is: data at c0 did not receive and merge the c1 data.
|
|
|
|
// Verify data is the same on both nodes.
|
|
for i, hldr := range []*test.Holder{hldr0, hldr1} {
|
|
if a, exists := hldr.Value("i", "f", 1); !exists || a != 1 {
|
|
// expects exists==true, a==1
|
|
t.Errorf("unexpected value(node%d/0): a:%d, exists: %v", i, a, exists)
|
|
}
|
|
if a, exists := hldr.Value("i", "f", 2); exists {
|
|
t.Errorf("unexpected value(node%d/1): a:%d, exists: %v", i, a, exists)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("MultiShard", func(t *testing.T) {
|
|
c := test.MustNewCluster(t, 2)
|
|
c.GetIdleNode(0).Config.Cluster.ReplicaN = 2
|
|
c.GetIdleNode(0).Config.AntiEntropy.Interval = 0
|
|
c.GetIdleNode(1).Config.Cluster.ReplicaN = 2
|
|
c.GetIdleNode(1).Config.AntiEntropy.Interval = 0
|
|
err := c.Start()
|
|
if err != nil {
|
|
t.Fatalf("starting cluster: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
var idx0 *pilosa.Index
|
|
_ = idx0
|
|
idx0, err = c.GetNode(0).API.CreateIndex(context.Background(), "i", pilosa.IndexOptions{})
|
|
_ = idx0
|
|
if err != nil {
|
|
t.Fatalf("creating index i: %v", err)
|
|
}
|
|
_, err = c.GetNode(0).API.CreateField(context.Background(), "i", "f", pilosa.OptFieldTypeInt(math.MinInt64, math.MaxInt64))
|
|
if err != nil {
|
|
t.Fatalf("creating field f: %v", err)
|
|
}
|
|
|
|
hldr0 := &test.Holder{Holder: c.GetNode(0).Server.Holder()}
|
|
hldr1 := &test.Holder{Holder: c.GetNode(1).Server.Holder()}
|
|
|
|
// Set data on the local holder for node0.
|
|
hldr0.SetValue("i", "f", 1*pilosa.ShardWidth, 11)
|
|
hldr0.SetValue("i", "f", 3*pilosa.ShardWidth, 32)
|
|
hldr0.SetValue("i", "f", 4*pilosa.ShardWidth, math.MinInt32)
|
|
hldr0.SetValue("i", "f", 7*pilosa.ShardWidth, math.MinInt32)
|
|
|
|
// Set data on node1.
|
|
hldr1.SetValue("i", "f", 0*pilosa.ShardWidth, 2)
|
|
hldr1.SetValue("i", "f", 2*pilosa.ShardWidth, 22)
|
|
hldr1.SetValue("i", "f", 4*pilosa.ShardWidth, math.MaxInt32)
|
|
hldr1.SetValue("i", "f", 7*pilosa.ShardWidth, math.MaxInt32)
|
|
|
|
// Primary for shards (for index "i"):
|
|
// node0: [0,3,7]
|
|
// node1: [1,2,4]
|
|
|
|
err = c.GetNode(0).Server.SyncData()
|
|
if err != nil {
|
|
t.Fatalf("syncing node 0: %v", err)
|
|
}
|
|
err = c.GetNode(1).Server.SyncData()
|
|
if err != nil {
|
|
t.Fatalf("syncing node 1: %v", err)
|
|
}
|
|
|
|
// dump the rbf keys for both c0 and c1
|
|
|
|
// Verify data is the same on both nodes.
|
|
for i, hldr := range []*test.Holder{hldr0, hldr1} {
|
|
if a := hldr.Range("i", "f", pql.GT, 0); !reflect.DeepEqual(a.Columns(), []uint64{2 * pilosa.ShardWidth, 3 * pilosa.ShardWidth, 4 * pilosa.ShardWidth}) {
|
|
t.Errorf("unexpected columns(node%d/0): %d", i, a.Columns())
|
|
}
|
|
if a := hldr.Range("i", "f", pql.LT, 0); !reflect.DeepEqual(a.Columns(), []uint64{7 * pilosa.ShardWidth}) {
|
|
t.Errorf("unexpected columns(node%d/0): %d", i, a.Columns())
|
|
}
|
|
}
|
|
})
|
|
}
|