mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-06 08:35:55 +00:00
UpdateEvery can change every key, and I think it strongly suggests no reasonable expectation of repeated access to a previously-accessed key, but also it can change the containers and replace them. We were avoiding caching mapped containers in some but not all cases, and that was causing segfaults. But really, the *problem* is that the remap operation wasn't clearing (or updating) the cache. Cleaning that up allows us to take advantage of the caching performance advantage even when working with read-only/mapped bitmaps. The only way to hit this: * Have mmapped containers to begin with. * Do reads so those containers get frozen. * Access, either reading or writing, a specific container with key K. * Snapshot, so the bitmap gets its containers replaced. * Remember, they have to be frozen -- if they aren't frozen, we'll update the containers in place. * Now have GC run so it actually unmaps the data. * Now try to write to the container with key K *before reading or writing any other key*. You have to get through the whole snapshot and GC process without any other reads or writes. * You get the cached value. You try to use it. You explode. The sliceContainers code was also setting lastKey to 0 in some cases, but also setting lastContainer to nil, so this wouldn't have caused problems, but just to be careful, I've standardized on ^uint64(0) for everything.
262 lines
6.2 KiB
Go
262 lines
6.2 KiB
Go
// 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 roaring
|
|
|
|
type sliceContainers struct {
|
|
keys []uint64
|
|
containers []*Container
|
|
lastKey uint64
|
|
lastContainer *Container
|
|
}
|
|
|
|
func newSliceContainers() *sliceContainers {
|
|
return &sliceContainers{}
|
|
}
|
|
|
|
func (sc *sliceContainers) Get(key uint64) *Container {
|
|
i := search64(sc.keys, key)
|
|
if i < 0 {
|
|
return nil
|
|
}
|
|
return sc.containers[i]
|
|
}
|
|
|
|
func (sc *sliceContainers) Put(key uint64, c *Container) {
|
|
i := search64(sc.keys, key)
|
|
|
|
// If index is negative then there's not an exact match
|
|
// and a container needs to be added.
|
|
if i < 0 {
|
|
sc.insertAt(key, c, -i-1)
|
|
} else {
|
|
sc.containers[i] = c
|
|
}
|
|
sc.lastKey = key
|
|
sc.lastContainer = c
|
|
}
|
|
|
|
func (sc *sliceContainers) PutContainerValues(key uint64, typ byte, n int, mapped bool) {
|
|
i := search64(sc.keys, key)
|
|
if i < 0 {
|
|
c := NewContainer()
|
|
c.setTyp(typ)
|
|
c.setN(int32(n))
|
|
c.setMapped(mapped)
|
|
sc.insertAt(key, c, -i-1)
|
|
} else {
|
|
// if the container already exists, and is frozen, this may
|
|
// result in copying its data, which is sort of pointless
|
|
// because PutContainerValues almost always gets called
|
|
// because we're reading new data from a file -- but also
|
|
// that means this case probably never happens.
|
|
c := sc.containers[i].Thaw()
|
|
c.setTyp(typ)
|
|
c.setN(int32(n))
|
|
c.setMapped(mapped)
|
|
sc.containers[i] = c
|
|
}
|
|
|
|
}
|
|
|
|
func (sc *sliceContainers) Remove(key uint64) {
|
|
statsHit("sliceContainers/Remove")
|
|
i := search64(sc.keys, key)
|
|
if i < 0 {
|
|
return
|
|
}
|
|
if key == sc.lastKey {
|
|
sc.lastKey = ^uint64(0)
|
|
sc.lastContainer = nil
|
|
}
|
|
sc.keys = append(sc.keys[:i], sc.keys[i+1:]...)
|
|
sc.containers = append(sc.containers[:i], sc.containers[i+1:]...)
|
|
|
|
}
|
|
func (sc *sliceContainers) insertAt(key uint64, c *Container, i int) {
|
|
statsHit("sliceContainers/insertAt")
|
|
sc.keys = append(sc.keys, 0)
|
|
copy(sc.keys[i+1:], sc.keys[i:])
|
|
sc.keys[i] = key
|
|
|
|
sc.containers = append(sc.containers, nil)
|
|
copy(sc.containers[i+1:], sc.containers[i:])
|
|
sc.containers[i] = c
|
|
}
|
|
|
|
func (sc *sliceContainers) GetOrCreate(key uint64) *Container {
|
|
// Check the last* cache for same container.
|
|
if key == sc.lastKey && sc.lastContainer != nil {
|
|
return sc.lastContainer
|
|
}
|
|
|
|
sc.lastKey = key
|
|
i := search64(sc.keys, key)
|
|
if i < 0 {
|
|
c := NewContainer()
|
|
sc.insertAt(key, c, -i-1)
|
|
sc.lastContainer = c
|
|
return c
|
|
}
|
|
|
|
sc.lastContainer = sc.containers[i]
|
|
return sc.lastContainer
|
|
}
|
|
|
|
func (sc *sliceContainers) Clone() Containers {
|
|
other := newSliceContainers()
|
|
other.keys = make([]uint64, len(sc.keys))
|
|
other.containers = make([]*Container, len(sc.containers))
|
|
copy(other.keys, sc.keys)
|
|
for i, c := range sc.containers {
|
|
other.containers[i] = c.Clone()
|
|
}
|
|
return other
|
|
}
|
|
|
|
func (sc *sliceContainers) Freeze() Containers {
|
|
other := newSliceContainers()
|
|
other.keys = make([]uint64, len(sc.keys))
|
|
other.containers = make([]*Container, len(sc.containers))
|
|
copy(other.keys, sc.keys)
|
|
for i, c := range sc.containers {
|
|
other.containers[i] = c.Freeze()
|
|
}
|
|
return other
|
|
}
|
|
|
|
func (sc *sliceContainers) Last() (key uint64, c *Container) {
|
|
if len(sc.keys) == 0 {
|
|
return 0, nil
|
|
}
|
|
return sc.keys[len(sc.keys)-1], sc.containers[len(sc.keys)-1]
|
|
}
|
|
|
|
func (sc *sliceContainers) Size() int {
|
|
return len(sc.keys)
|
|
|
|
}
|
|
|
|
func (sc *sliceContainers) Count() uint64 {
|
|
n := uint64(0)
|
|
for i := range sc.containers {
|
|
n += uint64(sc.containers[i].N())
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (sc *sliceContainers) Reset() {
|
|
sc.keys = sc.keys[:0]
|
|
sc.containers = sc.containers[:0]
|
|
sc.lastContainer = nil
|
|
sc.lastKey = ^uint64(0)
|
|
}
|
|
|
|
func (sc *sliceContainers) ResetN(n int) {
|
|
if cap(sc.keys) < n {
|
|
sc.keys = make([]uint64, 0, n)
|
|
sc.containers = make([]*Container, 0, n)
|
|
} else {
|
|
sc.keys = sc.keys[:0]
|
|
sc.containers = sc.containers[:0]
|
|
}
|
|
sc.lastContainer = nil
|
|
sc.lastKey = ^uint64(0)
|
|
}
|
|
|
|
func (sc *sliceContainers) seek(key uint64) (int, bool) {
|
|
i := search64(sc.keys, key)
|
|
found := true
|
|
if i < 0 {
|
|
found = false
|
|
i = -i - 1
|
|
}
|
|
return i, found
|
|
}
|
|
|
|
func (sc *sliceContainers) Iterator(key uint64) (citer ContainerIterator, found bool) {
|
|
i, found := sc.seek(key)
|
|
return &sliceIterator{e: sc, i: i}, found
|
|
}
|
|
|
|
func (sc *sliceContainers) Repair() {
|
|
for _, c := range sc.containers {
|
|
c.Repair()
|
|
}
|
|
}
|
|
|
|
// Update calls fn (existing-container, existed), and expects
|
|
// (new-container, write). If write is true, the container is used to
|
|
// replace the given container.
|
|
func (sc *sliceContainers) Update(key uint64, fn func(*Container, bool) (*Container, bool)) {
|
|
i, found := sc.seek(key)
|
|
var nc *Container
|
|
var write bool
|
|
if found {
|
|
nc, write = fn(sc.containers[i], true)
|
|
if write {
|
|
sc.containers[i] = nc
|
|
}
|
|
} else {
|
|
nc, write = fn(nil, false)
|
|
// don't expand the slice just to add a nil container, we
|
|
// could return that anyway
|
|
if write && nc != nil {
|
|
sc.insertAt(key, nc, i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// UpdateEvery calls fn (existing-container, existed), and expects
|
|
// (new-container, write). If write is true, the container is used to
|
|
// replace the given container.
|
|
func (sc *sliceContainers) UpdateEvery(fn func(uint64, *Container, bool) (*Container, bool)) {
|
|
for i, c := range sc.containers {
|
|
nc, write := fn(sc.keys[i], c, true)
|
|
if write {
|
|
sc.containers[i] = nc
|
|
}
|
|
}
|
|
// invalidate cache.
|
|
sc.lastKey = ^uint64(0)
|
|
sc.lastContainer = nil
|
|
}
|
|
|
|
type sliceIterator struct {
|
|
e *sliceContainers
|
|
i int
|
|
key uint64
|
|
value *Container
|
|
}
|
|
|
|
func (si *sliceIterator) Next() bool {
|
|
if si.e == nil {
|
|
return false
|
|
}
|
|
// discard nil containers from iteration. we don't always
|
|
// actually remove them because copying is expensive.
|
|
for si.i < len(si.e.keys) {
|
|
si.key = si.e.keys[si.i]
|
|
si.value = si.e.containers[si.i]
|
|
si.i++
|
|
if si.value != nil {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (si *sliceIterator) Value() (uint64, *Container) {
|
|
return si.key, si.value
|
|
}
|