membuffer: implement snapshot get and iterator for ART (#1467)

ref pingcap/tidb#55287

Signed-off-by: you06 <you1474600@gmail.com>
This commit is contained in:
you06 2024-09-25 15:03:02 +08:00 committed by GitHub
parent 527f80a186
commit 58f3322fc3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 178 additions and 48 deletions

View File

@ -423,6 +423,11 @@ func (t *ART) InspectNode(addr arena.MemdbArenaAddr) (*artLeaf, arena.MemdbArena
return lf, lf.vAddr return lf, lf.vAddr
} }
// IsStaging returns whether the MemBuffer is in staging status.
func (t *ART) IsStaging() bool {
return len(t.stages) > 0
}
// Checkpoint returns a checkpoint of ART. // Checkpoint returns a checkpoint of ART.
func (t *ART) Checkpoint() *arena.MemDBCheckpoint { func (t *ART) Checkpoint() *arena.MemDBCheckpoint {
cp := t.allocator.vlogAllocator.Checkpoint() cp := t.allocator.vlogAllocator.Checkpoint()
@ -471,7 +476,7 @@ func (t *ART) Cleanup(h int) {
return return
} }
if h < len(t.stages) { if h < len(t.stages) {
panic(fmt.Sprintf("cannot cleanup staging buffer, h=%v, len(db.stages)=%v", h, len(t.stages))) panic(fmt.Sprintf("cannot cleanup staging buffer, h=%v, len(tree.stages)=%v", h, len(t.stages)))
} }
cp := &t.stages[h-1] cp := &t.stages[h-1]
@ -501,7 +506,8 @@ func (t *ART) Reset() {
// DiscardValues releases the memory used by all values. // DiscardValues releases the memory used by all values.
// NOTE: any operation need value will panic after this function. // NOTE: any operation need value will panic after this function.
func (t *ART) DiscardValues() { func (t *ART) DiscardValues() {
panic("unimplemented") t.vlogInvalid = true
t.allocator.vlogAllocator.Reset()
} }
// InspectStage used to inspect the value updates in the given stage. // InspectStage used to inspect the value updates in the given stage.
@ -514,16 +520,35 @@ func (t *ART) InspectStage(handle int, f func([]byte, kv.KeyFlags, []byte)) {
// SelectValueHistory select the latest value which makes `predicate` returns true from the modification history. // SelectValueHistory select the latest value which makes `predicate` returns true from the modification history.
func (t *ART) SelectValueHistory(key []byte, predicate func(value []byte) bool) ([]byte, error) { func (t *ART) SelectValueHistory(key []byte, predicate func(value []byte) bool) ([]byte, error) {
panic("unimplemented") _, x := t.search(key)
if x == nil {
return nil, tikverr.ErrNotExist
}
if x.vAddr.IsNull() {
// A flags only key, act as value not exists
return nil, tikverr.ErrNotExist
}
result := t.allocator.vlogAllocator.SelectValueHistory(x.vAddr, func(addr arena.MemdbArenaAddr) bool {
return predicate(t.allocator.vlogAllocator.GetValue(addr))
})
if result.IsNull() {
return nil, nil
}
return t.allocator.vlogAllocator.GetValue(result), nil
} }
func (t *ART) SetMemoryFootprintChangeHook(fn func(uint64)) { func (t *ART) SetMemoryFootprintChangeHook(hook func(uint64)) {
panic("unimplemented") innerHook := func() {
hook(t.allocator.nodeAllocator.Capacity() + t.allocator.vlogAllocator.Capacity())
}
t.allocator.nodeAllocator.SetMemChangeHook(innerHook)
t.allocator.vlogAllocator.SetMemChangeHook(innerHook)
} }
// MemHookSet implements the MemBuffer interface. // MemHookSet implements the MemBuffer interface.
func (t *ART) MemHookSet() bool { func (t *ART) MemHookSet() bool {
panic("unimplemented") return t.allocator.nodeAllocator.MemHookSet()
} }
// GetKeyByHandle returns key by handle. // GetKeyByHandle returns key by handle.
@ -544,10 +569,24 @@ func (t *ART) GetValueByHandle(handle arena.MemKeyHandle) ([]byte, bool) {
return t.allocator.vlogAllocator.GetValue(lf.vAddr), true return t.allocator.vlogAllocator.GetValue(lf.vAddr), true
} }
// GetEntrySizeLimit gets the size limit for each entry and total buffer.
func (t *ART) GetEntrySizeLimit() (uint64, uint64) {
return t.entrySizeLimit, t.bufferSizeLimit
}
func (t *ART) SetEntrySizeLimit(entryLimit, bufferLimit uint64) { func (t *ART) SetEntrySizeLimit(entryLimit, bufferLimit uint64) {
t.entrySizeLimit, t.bufferSizeLimit = entryLimit, bufferLimit t.entrySizeLimit, t.bufferSizeLimit = entryLimit, bufferLimit
} }
// RemoveFromBuffer is a test function, not support yet.
func (t *ART) RemoveFromBuffer(key []byte) { func (t *ART) RemoveFromBuffer(key []byte) {
panic("unimplemented") panic("unimplemented")
} }
func (t *ART) GetCacheHitCount() uint64 {
return 0
}
func (t *ART) GetCacheMissCount() uint64 {
return 0
}

View File

@ -50,7 +50,10 @@ func (t *ART) iter(lowerBound, upperBound []byte, reverse, includeFlags bool) (*
inner: &baseIter{ inner: &baseIter{
allocator: &t.allocator, allocator: &t.allocator,
}, },
currAddr: arena.BadAddr, // the default value of currAddr is not equal to any valid address // the default value of currAddr is not equal to any valid address
// arena.BadAddr's idx is maxuint32 - 1, which is impossible in common cases,
// this avoids the initial value of currAddr equals to endAddr.
currAddr: arena.BadAddr,
endAddr: arena.NullAddr, endAddr: arena.NullAddr,
} }
it.init(lowerBound, upperBound) it.init(lowerBound, upperBound)
@ -84,6 +87,15 @@ func (it *Iterator) Value() []byte {
return it.tree.allocator.vlogAllocator.GetValue(it.currLeaf.vAddr) return it.tree.allocator.vlogAllocator.GetValue(it.currLeaf.vAddr)
} }
// HasValue returns false if it is flags only.
func (it *Iterator) HasValue() bool {
return !it.isFlagsOnly()
}
func (it *Iterator) isFlagsOnly() bool {
return it.currLeaf != nil && it.currLeaf.vAddr.IsNull()
}
func (it *Iterator) Next() error { func (it *Iterator) Next() error {
if !it.valid { if !it.valid {
// iterate is finished // iterate is finished

View File

@ -14,30 +14,111 @@
package art package art
import "context" import (
"context"
func (*ART) SnapshotGetter() *SnapshotGetter { tikverr "github.com/tikv/client-go/v2/error"
panic("unimplemented") "github.com/tikv/client-go/v2/internal/unionstore/arena"
)
func (t *ART) getSnapshot() arena.MemDBCheckpoint {
if len(t.stages) > 0 {
return t.stages[0]
}
return t.checkpoint()
} }
func (*ART) SnapshotIter([]byte, []byte) *SnapshotIter { // SnapshotGetter returns a Getter for a snapshot of MemBuffer.
panic("unimplemented") func (t *ART) SnapshotGetter() *SnapGetter {
return &SnapGetter{
tree: t,
cp: t.getSnapshot(),
}
} }
func (*ART) SnapshotIterReverse([]byte, []byte) *SnapshotIter { // SnapshotIter returns an Iterator for a snapshot of MemBuffer.
panic("unimplemented") func (t *ART) SnapshotIter(start, end []byte) *SnapIter {
inner, err := t.Iter(start, end)
if err != nil {
panic(err)
}
it := &SnapIter{
Iterator: inner,
cp: t.getSnapshot(),
}
for !it.setValue() && it.Valid() {
_ = it.Next()
}
return it
} }
type SnapshotGetter struct{} // SnapshotIterReverse returns a reverse Iterator for a snapshot of MemBuffer.
func (t *ART) SnapshotIterReverse(k, lowerBound []byte) *SnapIter {
func (s *SnapshotGetter) Get(context.Context, []byte) ([]byte, error) { inner, err := t.IterReverse(k, lowerBound)
panic("unimplemented") if err != nil {
panic(err)
}
it := &SnapIter{
Iterator: inner,
cp: t.getSnapshot(),
}
for !it.setValue() && it.valid {
_ = it.Next()
}
return it
} }
type SnapshotIter struct{} type SnapGetter struct {
tree *ART
cp arena.MemDBCheckpoint
}
func (i *SnapshotIter) Valid() bool { panic("unimplemented") } func (snap *SnapGetter) Get(ctx context.Context, key []byte) ([]byte, error) {
func (i *SnapshotIter) Key() []byte { panic("unimplemented") } addr, lf := snap.tree.search(key)
func (i *SnapshotIter) Value() []byte { panic("unimplemented") } if addr.IsNull() {
func (i *SnapshotIter) Next() error { panic("unimplemented") } return nil, tikverr.ErrNotExist
func (i *SnapshotIter) Close() { panic("unimplemented") } }
if lf.vAddr.IsNull() {
// A flags only key, act as value not exists
return nil, tikverr.ErrNotExist
}
v, ok := snap.tree.allocator.vlogAllocator.GetSnapshotValue(lf.vAddr, &snap.cp)
if !ok {
return nil, tikverr.ErrNotExist
}
return v, nil
}
type SnapIter struct {
*Iterator
value []byte
cp arena.MemDBCheckpoint
}
func (i *SnapIter) Value() []byte {
return i.value
}
func (i *SnapIter) Next() error {
i.value = nil
for i.Valid() {
if err := i.Iterator.Next(); err != nil {
return err
}
if i.setValue() {
return nil
}
}
return nil
}
func (i *SnapIter) setValue() bool {
if !i.Valid() {
return false
}
if v, ok := i.tree.allocator.vlogAllocator.GetSnapshotValue(i.currLeaf.vAddr, &i.cp); ok {
i.value = v
return true
}
return false
}

View File

@ -49,7 +49,7 @@ type MemDBCheckpoint = arena.MemDBCheckpoint
type MemKeyHandle = arena.MemKeyHandle type MemKeyHandle = arena.MemKeyHandle
type MemDB = rbtDBWithContext type MemDB = artDBWithContext
var NewMemDB = newRbtDBWithContext var NewMemDB = newArtDBWithContext
var NewMemDBWithContext = newRbtDBWithContext var NewMemDBWithContext = newArtDBWithContext

View File

@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//nolint:unused
package unionstore package unionstore
import ( import (
@ -36,7 +35,6 @@ type artDBWithContext struct {
skipMutex bool skipMutex bool
} }
//nolint:unused
func newArtDBWithContext() *artDBWithContext { func newArtDBWithContext() *artDBWithContext {
return &artDBWithContext{ART: art.New()} return &artDBWithContext{ART: art.New()}
} }
@ -118,7 +116,7 @@ func (db *artDBWithContext) FlushWait() error { return nil }
// GetMemDB implements the MemBuffer interface. // GetMemDB implements the MemBuffer interface.
func (db *artDBWithContext) GetMemDB() *MemDB { func (db *artDBWithContext) GetMemDB() *MemDB {
panic("unimplemented") return db
} }
// BatchGet returns the values for given keys from the MemBuffer. // BatchGet returns the values for given keys from the MemBuffer.

View File

@ -42,6 +42,7 @@ func newRbtDBWithContext() *rbtDBWithContext {
} }
} }
//nolint:unused
func (db *rbtDBWithContext) setSkipMutex(skip bool) { func (db *rbtDBWithContext) setSkipMutex(skip bool) {
db.skipMutex = skip db.skipMutex = skip
} }
@ -125,7 +126,7 @@ func (db *rbtDBWithContext) FlushWait() error { return nil }
// GetMemDB implements the MemBuffer interface. // GetMemDB implements the MemBuffer interface.
func (db *rbtDBWithContext) GetMemDB() *MemDB { func (db *rbtDBWithContext) GetMemDB() *MemDB {
return db return nil
} }
// BatchGet returns the values for given keys from the MemBuffer. // BatchGet returns the values for given keys from the MemBuffer.

View File

@ -995,6 +995,7 @@ func testUnsetTemporaryFlag(t *testing.T, buffer MemBuffer) {
func TestSnapshotGetIter(t *testing.T) { func TestSnapshotGetIter(t *testing.T) {
testSnapshotGetIter(t, newRbtDBWithContext()) testSnapshotGetIter(t, newRbtDBWithContext())
testSnapshotGetIter(t, newArtDBWithContext())
} }
func testSnapshotGetIter(t *testing.T, db MemBuffer) { func testSnapshotGetIter(t *testing.T, db MemBuffer) {
@ -1089,21 +1090,19 @@ func testIterNoResult(t *testing.T, buffer MemBuffer) {
assert := assert.New(t) assert := assert.New(t)
assert.Nil(buffer.Set([]byte{1, 1}, []byte{1, 1})) assert.Nil(buffer.Set([]byte{1, 1}, []byte{1, 1}))
// Test lower bound and upper bound seek same position
iter, err := buffer.Iter([]byte{1, 0, 0}, []byte{1, 0, 1}) checkFn := func(lowerBound, upperBound []byte) {
iter, err := buffer.Iter(lowerBound, upperBound)
assert.Nil(err) assert.Nil(err)
assert.False(iter.Valid()) assert.False(iter.Valid())
iter, err = buffer.IterReverse([]byte{1, 0, 1}, []byte{1, 0, 0}) iter, err = buffer.IterReverse(upperBound, lowerBound)
assert.Nil(err)
assert.False(iter.Valid())
// Test lower bound >= upper bound
iter, err = buffer.Iter([]byte{1, 0, 1}, []byte{1, 0, 0})
assert.Nil(err)
assert.False(iter.Valid())
iter, err = buffer.IterReverse([]byte{1, 0, 0}, []byte{1, 0, 1})
assert.Nil(err)
assert.False(iter.Valid())
iter, err = buffer.Iter([]byte{1, 1}, []byte{1, 1})
assert.Nil(err) assert.Nil(err)
assert.False(iter.Valid()) assert.False(iter.Valid())
}
// Test lower bound and upper bound seek to the same position
checkFn([]byte{1, 1}, []byte{1, 1})
checkFn([]byte{1, 0, 0}, []byte{1, 0, 1})
// Test lower bound > upper bound
checkFn([]byte{1, 0, 1}, []byte{1, 0, 0})
} }