Compare commits

...

5 commits

Author SHA1 Message Date
Travis Turner
b8ef96e523
Merge pull request #2128 from travisturner/release-v1.4.1
update changelog and docs for v1.4.1
2020-10-08 16:30:08 -05:00
Travis Turner
bead79a816
update changelog and docs for v1.4.1 2020-10-08 16:16:06 -05:00
Travis Turner
0e4d0cbc28
Merge pull request #2127 from travisturner/backport-pr2084
Backport pr2084
2020-10-08 15:22:37 -05:00
Travis Turner
5ebd7e8da8
move requiredDepth calculation after min/max ranges are checked
Without this, a data set with a ludicrously large value in it
could break a BSI field's depth even though the import would then
reject it.

always treat BSI fields as having at least their depth:

If you imported only small values, BSI fields could end up
not bothering to clear higher bits in existing values, which
produced strange behaviors.
2020-10-08 14:36:58 -05:00
Travis Turner
5d71f04e54
reset fragment.rowCache after importValue 2020-10-08 14:36:42 -05:00
7 changed files with 190 additions and 43 deletions

View file

@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.4.1] - 2020-10-08
This version contains 1 contribution from 1 contributor. There are 4 files changed; 170 insertions; and 30 deletions.
### Fixed
- Add logic that clears fragment.rowCache after importValue ([#2084](https://github.com/pilosa/pilosa/pull/2084))
## [1.4.0] - 2019-09-17 ## [1.4.0] - 2019-09-17
This version contains 99 contributions from 11 contributors. There are 94 files changed; 9,453 insertions; and 6,121 deletions. This version contains 99 contributions from 11 contributors. There are 94 files changed; 9,453 insertions; and 6,121 deletions.

View file

@ -1,4 +1,4 @@
FROM golang:1.13.0 as builder FROM golang:1.15.0 as builder
COPY . pilosa COPY . pilosa

View file

@ -42,7 +42,7 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
backing up, and more. Complete documentation is available backing up, and more. Complete documentation is available
at https://www.pilosa.com/docs/. at https://www.pilosa.com/docs/.
Version: v1.4.0 Version: v1.4.1
Build Time: 2018-05-14T22:14:01+0000 Build Time: 2018-05-14T22:14:01+0000
Usage: Usage:
@ -71,19 +71,19 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
1. Download the latest release: 1. Download the latest release:
``` ```
curl -L -O https://github.com/pilosa/pilosa/releases/download/v1.4.0/pilosa-v1.4.0-darwin-amd64.tar.gz curl -L -O https://github.com/pilosa/pilosa/releases/download/v1.4.1/pilosa-v1.4.1-darwin-amd64.tar.gz
``` ```
Other releases can be downloaded from our Releases page on Github. Other releases can be downloaded from our Releases page on Github.
2. Extract the binary: 2. Extract the binary:
``` ```
tar xfz pilosa-v1.4.0-darwin-amd64.tar.gz tar xfz pilosa-v1.4.1-darwin-amd64.tar.gz
``` ```
3. Move the binary into your PATH so you can run `pilosa` from any shell: 3. Move the binary into your PATH so you can run `pilosa` from any shell:
``` ```
cp -i pilosa-v1.4.0-darwin-amd64/pilosa /usr/local/bin cp -i pilosa-v1.4.1-darwin-amd64/pilosa /usr/local/bin
``` ```
4. Make sure Pilosa is installed successfully: 4. Make sure Pilosa is installed successfully:
@ -100,7 +100,7 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
backing up, and more. Complete documentation is available backing up, and more. Complete documentation is available
at https://www.pilosa.com/docs/. at https://www.pilosa.com/docs/.
Version: v1.4.0 Version: v1.4.1
Build Time: 2018-05-14T22:14:01+0000 Build Time: 2018-05-14T22:14:01+0000
Usage: Usage:
@ -163,7 +163,7 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
backing up, and more. Complete documentation is available backing up, and more. Complete documentation is available
at https://www.pilosa.com/docs/. at https://www.pilosa.com/docs/.
Version: v1.4.0 Version: v1.4.1
Build Time: 2018-05-14T22:14:01+0000 Build Time: 2018-05-14T22:14:01+0000
Usage: Usage:
@ -201,19 +201,19 @@ There are three ways to install Pilosa on Linux: download the binary (recommende
1. To install the latest version of Pilosa, download the latest release: 1. To install the latest version of Pilosa, download the latest release:
``` ```
curl -L -O https://github.com/pilosa/pilosa/releases/download/v1.4.0/pilosa-v1.4.0-linux-amd64.tar.gz curl -L -O https://github.com/pilosa/pilosa/releases/download/v1.4.1/pilosa-v1.4.1-linux-amd64.tar.gz
``` ```
Note: This assumes you are using an `amd64` compatible architecture. Other releases can be downloaded from our Releases page on Github. Note: This assumes you are using an `amd64` compatible architecture. Other releases can be downloaded from our Releases page on Github.
2. Extract the binary: 2. Extract the binary:
``` ```
tar xfz pilosa-v1.4.0-linux-amd64.tar.gz tar xfz pilosa-v1.4.1-linux-amd64.tar.gz
``` ```
3. Move the binary into your PATH so you can run `pilosa` from any shell: 3. Move the binary into your PATH so you can run `pilosa` from any shell:
``` ```
cp -i pilosa-v1.4.0-linux-amd64/pilosa /usr/local/bin cp -i pilosa-v1.4.1-linux-amd64/pilosa /usr/local/bin
``` ```
4. Make sure Pilosa is installed successfully: 4. Make sure Pilosa is installed successfully:
@ -230,7 +230,7 @@ There are three ways to install Pilosa on Linux: download the binary (recommende
backing up, and more. Complete documentation is available backing up, and more. Complete documentation is available
at https://www.pilosa.com/docs/. at https://www.pilosa.com/docs/.
Version: v1.4.0 Version: v1.4.1
Build Time: 2018-05-14T22:14:01+0000 Build Time: 2018-05-14T22:14:01+0000
Usage: Usage:
@ -293,7 +293,7 @@ There are three ways to install Pilosa on Linux: download the binary (recommende
backing up, and more. Complete documentation is available backing up, and more. Complete documentation is available
at https://www.pilosa.com/docs/. at https://www.pilosa.com/docs/.
Version: v1.4.0 Version: v1.4.1
Build Time: 2018-05-14T22:14:01+0000 Build Time: 2018-05-14T22:14:01+0000
Usage: Usage:

View file

@ -1269,34 +1269,12 @@ func (f *Field) importValue(columnIDs []uint64, values []int64, options *ImportO
return errors.Wrap(ErrBSIGroupNotFound, f.name) return errors.Wrap(ErrBSIGroupNotFound, f.name)
} }
// Find the lowest/highest values. // We want to determine the required bit depth, in case the field doesn't
// have as many bits currently as would be needed to represent these values,
// but only if the values are in-range for the field.
var min, max int64 var min, max int64
for i, value := range values { if len(values) > 0 {
if i == 0 || value < min { min, max = values[0], values[0]
min = value
}
if i == 0 || value > max {
max = value
}
}
// Determine the highest bit depth required by the min & max.
requiredDepth := bitDepthInt64(min - bsig.Base)
if v := bitDepthInt64(max - bsig.Base); v > requiredDepth {
requiredDepth = v
}
// Increase bit depth if required.
if requiredDepth > bsig.BitDepth {
if err := func() error {
f.mu.Lock()
defer f.mu.Unlock()
bsig.BitDepth = requiredDepth
f.options.BitDepth = requiredDepth
return f.saveMeta()
}(); err != nil {
return errors.Wrap(err, "increasing bsi bit depth")
}
} }
// Split import data by fragment. // Split import data by fragment.
@ -1308,6 +1286,12 @@ func (f *Field) importValue(columnIDs []uint64, values []int64, options *ImportO
} else if value < bsig.Min { } else if value < bsig.Min {
return fmt.Errorf("%v, columnID=%v, value=%v", ErrBSIGroupValueTooLow, columnID, value) return fmt.Errorf("%v, columnID=%v, value=%v", ErrBSIGroupValueTooLow, columnID, value)
} }
if value > max {
max = value
}
if value < min {
min = value
}
// Attach value to each bsiGroup view. // Attach value to each bsiGroup view.
for _, name := range []string{viewName} { for _, name := range []string{viewName} {
@ -1319,6 +1303,26 @@ func (f *Field) importValue(columnIDs []uint64, values []int64, options *ImportO
} }
} }
// Determine the highest bit depth required by the min & max.
requiredDepth := bitDepthInt64(min - bsig.Base)
if v := bitDepthInt64(max - bsig.Base); v > requiredDepth {
requiredDepth = v
}
// Increase bit depth if required.
if requiredDepth > bsig.BitDepth {
if err := func() error {
f.mu.Lock()
defer f.mu.Unlock()
bsig.BitDepth = requiredDepth
f.options.BitDepth = requiredDepth
return f.saveMeta()
}(); err != nil {
return errors.Wrap(err, "increasing bsi bit depth")
}
} else {
requiredDepth = bsig.BitDepth
}
// Import into each fragment. // Import into each fragment.
for key, data := range dataByFragment { for key, data := range dataByFragment {
// The view must already exist (i.e. we can't create it) // The view must already exist (i.e. we can't create it)

View file

@ -2191,7 +2191,14 @@ func (f *fragment) importValueSmallWrite(columnIDs []uint64, values []int64, bit
rowSet[uint64(i)] = struct{}{} rowSet[uint64(i)] = struct{}{}
} }
err := f.importPositions(toSet, toClear, rowSet) err := f.importPositions(toSet, toClear, rowSet)
return errors.Wrap(err, "importing positions") if err != nil {
return errors.Wrap(err, "importing positions")
}
// Reset the rowCache.
f.rowCache = &simpleCache{make(map[uint64]*Row)}
return nil
} }
// importValue bulk imports a set of range-encoded values. // importValue bulk imports a set of range-encoded values.
@ -2230,6 +2237,9 @@ func (f *fragment) importValue(columnIDs []uint64, values []int64, bitDepth uint
// We don't actually care, except we want our stats to be accurate. // We don't actually care, except we want our stats to be accurate.
f.incrementOpN(totalChanges) f.incrementOpN(totalChanges)
// Reset the rowCache.
f.rowCache = &simpleCache{make(map[uint64]*Row)}
// in theory, this should probably have happened anyway, but if enough // in theory, this should probably have happened anyway, but if enough
// of the bits matched existing bits, we'll be under our opN estimate, and // of the bits matched existing bits, we'll be under our opN estimate, and
// we want to ensure that the snapshot happens. // we want to ensure that the snapshot happens.

View file

@ -3363,7 +3363,7 @@ func TestImportMultipleValues(t *testing.T) {
cols []uint64 cols []uint64
vals []int64 vals []int64
checkCols []uint64 checkCols []uint64
checkVals []uint64 checkVals []int64
depth uint depth uint
}{ }{
{ {
@ -3371,7 +3371,7 @@ func TestImportMultipleValues(t *testing.T) {
vals: []int64{97, 100}, vals: []int64{97, 100},
depth: 7, depth: 7,
checkCols: []uint64{0}, checkCols: []uint64{0},
checkVals: []uint64{100}, checkVals: []int64{100},
}, },
} }
@ -3395,7 +3395,7 @@ func TestImportMultipleValues(t *testing.T) {
if !exists { if !exists {
t.Errorf("column %d should exist", cc) t.Errorf("column %d should exist", cc)
} }
if n != 100 { if n != cv {
t.Errorf("wrong value: %d is not %d", n, cv) t.Errorf("wrong value: %d is not %d", n, cv)
} }
} }
@ -3405,6 +3405,66 @@ func TestImportMultipleValues(t *testing.T) {
} }
} }
func TestImportValueRowCache(t *testing.T) {
type testCase struct {
cols []uint64
vals []int64
checkCols []uint64
depth uint
}
tests := []struct {
tc1 testCase
tc2 testCase
}{
{
tc1: testCase{
cols: []uint64{2},
vals: []int64{1},
depth: 1,
checkCols: []uint64{2},
},
tc2: testCase{
cols: []uint64{1000},
vals: []int64{1},
depth: 1,
checkCols: []uint64{2, 1000},
},
},
}
for i, test := range tests {
for _, maxOpN := range []int{1, 10000} {
t.Run(fmt.Sprintf("%dMaxOpN%d", i, maxOpN), func(t *testing.T) {
f := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0)
f.MaxOpN = maxOpN
defer f.Clean(t)
// First import (tc1)
if err := f.importValue(test.tc1.cols, test.tc1.vals, test.tc1.depth, false); err != nil {
t.Fatalf("importing values: %v", err)
}
if r, err := f.rangeOp(pql.GT, test.tc1.depth, 0); err != nil {
t.Error("getting range of values")
} else if !reflect.DeepEqual(r.Columns(), test.tc1.checkCols) {
t.Errorf("wrong column values. expected: %v, but got: %v", test.tc1.checkCols, r.Columns())
}
// Second import (tc2)
if err := f.importValue(test.tc2.cols, test.tc2.vals, test.tc2.depth, false); err != nil {
t.Fatalf("importing values: %v", err)
}
if r, err := f.rangeOp(pql.GT, test.tc2.depth, 0); err != nil {
t.Error("getting range of values")
} else if !reflect.DeepEqual(r.Columns(), test.tc2.checkCols) {
t.Errorf("wrong column values. expected: %v, but got: %v", test.tc2.checkCols, r.Columns())
}
})
}
}
}
func TestFragmentConcurrentReadWrite(t *testing.T) { func TestFragmentConcurrentReadWrite(t *testing.T) {
f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked) f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked)
defer f.Clean(t) defer f.Clean(t)

View file

@ -777,6 +777,72 @@ func TestClient_ImportKeys(t *testing.T) {
}) })
} }
func TestClient_ImportIDs(t *testing.T) {
// Ensure that running a query between two imports does
// not affect the result set. It turns out, this is caused
// by the fragment.rowCache failing to be cleared after an
// importValue. This ensures that the rowCache is cleared
// after an import.
t.Run("ImportRangeImport", func(t *testing.T) {
cluster := test.MustRunCluster(t, 1)
defer cluster.Close()
cmd := cluster[0]
host := cmd.URL()
holder := cmd.Server.Holder()
hldr := test.Holder{Holder: holder}
idxName := "i"
fldName := "f"
// Load bitmap into cache to ensure cache gets updated.
index := hldr.MustCreateIndexIfNotExists(idxName, pilosa.IndexOptions{Keys: false})
_, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(-10000, 10000))
if err != nil {
t.Fatal(err)
}
// Send import request.
c := MustNewClient(host, http.GetHTTPClient(nil))
if err := c.ImportValue(context.Background(), idxName, fldName, 0, []pilosa.FieldValue{
{ColumnID: 2, Value: 1},
}); err != nil {
t.Fatal(err)
}
// Verify range.
queryRequest := &pilosa.QueryRequest{
Query: fmt.Sprintf(`Row(%s>0)`, fldName),
Remote: false,
}
if result, err := c.Query(context.Background(), idxName, queryRequest); err != nil {
t.Fatal(err)
} else {
res := result.Results[0].(*pilosa.Row).Columns()
if !reflect.DeepEqual(res, []uint64{2}) {
t.Fatalf("unexpected column ids: %v", res)
}
}
// Send import request.
if err := c.ImportValue(context.Background(), idxName, fldName, 0, []pilosa.FieldValue{
{ColumnID: 1000, Value: 1},
}); err != nil {
t.Fatal(err)
}
// Verify range.
if result, err := c.Query(context.Background(), idxName, queryRequest); err != nil {
t.Fatal(err)
} else {
res := result.Results[0].(*pilosa.Row).Columns()
if !reflect.DeepEqual(res, []uint64{2, 1000}) {
t.Fatalf("unexpected column ids: %v", res)
}
}
})
}
// Ensure client can bulk import value data. // Ensure client can bulk import value data.
func TestClient_ImportValue(t *testing.T) { func TestClient_ImportValue(t *testing.T) {
cluster := test.MustRunCluster(t, 1) cluster := test.MustRunCluster(t, 1)