Skip to content

Commit c3e10ae

Browse files
authored
membuffer: implement cache for ART (#1470)
ref pingcap/tidb#55287 Signed-off-by: you06 <[email protected]>
1 parent 58f3322 commit c3e10ae

File tree

11 files changed

+164
-14
lines changed

11 files changed

+164
-14
lines changed

examples/gcworker/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/opentracing/opentracing-go v1.2.0 // indirect
2222
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
2323
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
24-
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 // indirect
24+
github.com/pingcap/kvproto v0.0.0-20240924080114-4a3e17f5e62d // indirect
2525
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/prometheus/client_golang v1.18.0 // indirect

examples/rawkv/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/opentracing/opentracing-go v1.2.0 // indirect
2222
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
2323
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
24-
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 // indirect
24+
github.com/pingcap/kvproto v0.0.0-20240924080114-4a3e17f5e62d // indirect
2525
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/prometheus/client_golang v1.18.0 // indirect

examples/txnkv/1pc_txn/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/opentracing/opentracing-go v1.2.0 // indirect
2222
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
2323
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
24-
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 // indirect
24+
github.com/pingcap/kvproto v0.0.0-20240924080114-4a3e17f5e62d // indirect
2525
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/prometheus/client_golang v1.18.0 // indirect

examples/txnkv/async_commit/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/opentracing/opentracing-go v1.2.0 // indirect
2222
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
2323
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
24-
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 // indirect
24+
github.com/pingcap/kvproto v0.0.0-20240924080114-4a3e17f5e62d // indirect
2525
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/prometheus/client_golang v1.18.0 // indirect

examples/txnkv/delete_range/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/opentracing/opentracing-go v1.2.0 // indirect
2222
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
2323
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
24-
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 // indirect
24+
github.com/pingcap/kvproto v0.0.0-20240924080114-4a3e17f5e62d // indirect
2525
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/prometheus/client_golang v1.18.0 // indirect

examples/txnkv/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/opentracing/opentracing-go v1.2.0 // indirect
2222
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
2323
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
24-
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 // indirect
24+
github.com/pingcap/kvproto v0.0.0-20240924080114-4a3e17f5e62d // indirect
2525
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/prometheus/client_golang v1.18.0 // indirect

examples/txnkv/pessimistic_txn/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/opentracing/opentracing-go v1.2.0 // indirect
2222
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
2323
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
24-
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 // indirect
24+
github.com/pingcap/kvproto v0.0.0-20240924080114-4a3e17f5e62d // indirect
2525
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/prometheus/client_golang v1.18.0 // indirect

examples/txnkv/unsafedestoryrange/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/opentracing/opentracing-go v1.2.0 // indirect
2222
github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect
2323
github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
24-
github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 // indirect
24+
github.com/pingcap/kvproto v0.0.0-20240924080114-4a3e17f5e62d // indirect
2525
github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/prometheus/client_golang v1.18.0 // indirect

internal/unionstore/art/art.go

+69-6
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
package art
1717

1818
import (
19+
"bytes"
1920
"fmt"
2021
"math"
22+
"sync/atomic"
2123

2224
tikverr "github.com/tikv/client-go/v2/error"
2325
"github.com/tikv/client-go/v2/internal/unionstore/arena"
@@ -44,6 +46,12 @@ type ART struct {
4446
bufferSizeLimit uint64
4547
len int
4648
size int
49+
50+
// The lastTraversedNode stores addr in uint64 of the last traversed node, include search and recursiveInsert.
51+
// Compare to atomic.Pointer, atomic.Uint64 can avoid heap allocation, so it's more efficient.
52+
lastTraversedNode atomic.Uint64
53+
hitCount atomic.Uint64
54+
missCount atomic.Uint64
4755
}
4856

4957
func New() *ART {
@@ -55,6 +63,7 @@ func New() *ART {
5563
t.allocator.nodeAllocator.freeNode4 = make([]arena.MemdbArenaAddr, 0, 1<<4)
5664
t.allocator.nodeAllocator.freeNode16 = make([]arena.MemdbArenaAddr, 0, 1<<3)
5765
t.allocator.nodeAllocator.freeNode48 = make([]arena.MemdbArenaAddr, 0, 1<<2)
66+
t.lastTraversedNode.Store(arena.NullU64Addr)
5867
return &t
5968
}
6069

@@ -102,10 +111,26 @@ func (t *ART) Set(key artKey, value []byte, ops ...kv.FlagsOp) error {
102111
return nil
103112
}
104113

105-
// search looks up the leaf with the given key.
114+
// search wraps searchImpl with cache.
115+
func (t *ART) search(key artKey) (arena.MemdbArenaAddr, *artLeaf) {
116+
// check cache
117+
addr, leaf, found := t.checkKeyInCache(key)
118+
if found {
119+
t.hitCount.Add(1)
120+
return addr, leaf
121+
}
122+
t.missCount.Add(1)
123+
addr, leaf = t.searchImpl(key)
124+
if !addr.IsNull() {
125+
t.updateLastTraversed(addr)
126+
}
127+
return addr, leaf
128+
}
129+
130+
// searchImpl looks up the leaf with the given key.
106131
// It returns the memory arena address and leaf itself it there is a match leaf,
107132
// returns arena.NullAddr and nil if the key is not found.
108-
func (t *ART) search(key artKey) (arena.MemdbArenaAddr, *artLeaf) {
133+
func (t *ART) searchImpl(key artKey) (arena.MemdbArenaAddr, *artLeaf) {
109134
current := t.root
110135
if current == nullArtNode {
111136
return arena.NullAddr, nil
@@ -154,9 +179,25 @@ func (t *ART) search(key artKey) (arena.MemdbArenaAddr, *artLeaf) {
154179
}
155180
}
156181

157-
// recursiveInsert returns the node address of the key.
158-
// It will insert the key if not exists, returns the newly inserted or existing leaf.
182+
// recursiveInsert wraps recursiveInsertImpl with cache.
159183
func (t *ART) recursiveInsert(key artKey) (arena.MemdbArenaAddr, *artLeaf) {
184+
addr, leaf, found := t.checkKeyInCache(key)
185+
if found {
186+
t.hitCount.Add(1)
187+
return addr, leaf
188+
}
189+
t.missCount.Add(1)
190+
addr, leaf = t.recursiveInsertImpl(key)
191+
if !addr.IsNull() {
192+
t.updateLastTraversed(addr)
193+
}
194+
return addr, leaf
195+
}
196+
197+
// recursiveInsertImpl returns the node address of the key.
198+
// It will insert the key if not exists, returns the newly inserted or existing leaf.
199+
func (t *ART) recursiveInsertImpl(key artKey) (arena.MemdbArenaAddr, *artLeaf) {
200+
160201
// lazy init root node and allocator.
161202
// this saves memory for read only txns.
162203
if t.root.addr.IsNull() {
@@ -501,6 +542,7 @@ func (t *ART) Reset() {
501542
t.len = 0
502543
t.allocator.nodeAllocator.Reset()
503544
t.allocator.vlogAllocator.Reset()
545+
t.lastTraversedNode.Store(arena.NullU64Addr)
504546
}
505547

506548
// DiscardValues releases the memory used by all values.
@@ -583,10 +625,31 @@ func (t *ART) RemoveFromBuffer(key []byte) {
583625
panic("unimplemented")
584626
}
585627

628+
// updateLastTraversed updates the last traversed node atomically
629+
// the addr must be a valid leaf address
630+
func (t *ART) updateLastTraversed(addr arena.MemdbArenaAddr) {
631+
t.lastTraversedNode.Store(addr.AsU64())
632+
}
633+
634+
// checkKeyInCache retrieves the last traversed node if the key matches
635+
func (t *ART) checkKeyInCache(key []byte) (arena.MemdbArenaAddr, *artLeaf, bool) {
636+
addrU64 := t.lastTraversedNode.Load()
637+
if addrU64 == arena.NullU64Addr {
638+
return arena.NullAddr, nil, false
639+
}
640+
641+
addr := arena.U64ToAddr(addrU64)
642+
leaf := t.allocator.getLeaf(addr)
643+
if !bytes.Equal(leaf.GetKey(), key) {
644+
return arena.NullAddr, nil, false
645+
}
646+
return addr, leaf, true
647+
}
648+
586649
func (t *ART) GetCacheHitCount() uint64 {
587-
return 0
650+
return t.hitCount.Load()
588651
}
589652

590653
func (t *ART) GetCacheMissCount() uint64 {
591-
return 0
654+
return t.missCount.Load()
592655
}

internal/unionstore/memdb_bench_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -229,3 +229,24 @@ func benchIterator(b *testing.B, buffer MemBuffer) {
229229
iter.Close()
230230
}
231231
}
232+
233+
func BenchmarkMemBufferCache(b *testing.B) {
234+
fn := func(b *testing.B, buffer MemBuffer) {
235+
buf := make([][keySize]byte, b.N)
236+
for i := range buf {
237+
binary.LittleEndian.PutUint32(buf[i][:], uint32(i))
238+
buffer.Set(buf[i][:], buf[i][:])
239+
}
240+
ctx := context.Background()
241+
b.ResetTimer()
242+
for i := range buf {
243+
buffer.Get(ctx, buf[i][:])
244+
for j := 0; j < 10; j++ {
245+
// the cache hit get will be fast
246+
buffer.Get(ctx, buf[i][:])
247+
}
248+
}
249+
}
250+
b.Run("RBT", func(b *testing.B) { fn(b, newRbtDBWithContext()) })
251+
b.Run("ART", func(b *testing.B) { fn(b, newArtDBWithContext()) })
252+
}

internal/unionstore/memdb_test.go

+66
Original file line numberDiff line numberDiff line change
@@ -1106,3 +1106,69 @@ func testIterNoResult(t *testing.T, buffer MemBuffer) {
11061106
// Test lower bound > upper bound
11071107
checkFn([]byte{1, 0, 1}, []byte{1, 0, 0})
11081108
}
1109+
1110+
func TestMemBufferCache(t *testing.T) {
1111+
testMemBufferCache(t, newRbtDBWithContext())
1112+
testMemBufferCache(t, newArtDBWithContext())
1113+
}
1114+
1115+
func testMemBufferCache(t *testing.T, buffer MemBuffer) {
1116+
assert := assert.New(t)
1117+
1118+
type CacheStats interface {
1119+
GetCacheHitCount() uint64
1120+
GetCacheMissCount() uint64
1121+
}
1122+
cacheCheck := func(hit bool, fn func()) {
1123+
beforeHit, beforeMiss := buffer.(CacheStats).GetCacheHitCount(), buffer.(CacheStats).GetCacheMissCount()
1124+
fn()
1125+
afterHit, afterMiss := buffer.(CacheStats).GetCacheHitCount(), buffer.(CacheStats).GetCacheMissCount()
1126+
hitCnt := afterHit - beforeHit
1127+
missCnt := afterMiss - beforeMiss
1128+
if hit {
1129+
assert.Equal(hitCnt, uint64(1))
1130+
assert.Equal(missCnt, uint64(0))
1131+
} else {
1132+
assert.Equal(hitCnt, uint64(0))
1133+
assert.Equal(missCnt, uint64(1))
1134+
}
1135+
}
1136+
1137+
cacheCheck(false, func() {
1138+
assert.Nil(buffer.Set([]byte{1}, []byte{0}))
1139+
})
1140+
cacheCheck(true, func() {
1141+
assert.Nil(buffer.Set([]byte{1}, []byte{1}))
1142+
})
1143+
cacheCheck(false, func() {
1144+
assert.Nil(buffer.Set([]byte{2}, []byte{2}))
1145+
})
1146+
cacheCheck(true, func() {
1147+
v, err := buffer.Get(context.Background(), []byte{2})
1148+
assert.Nil(err)
1149+
assert.Equal(v, []byte{2})
1150+
})
1151+
cacheCheck(false, func() {
1152+
v, err := buffer.Get(context.Background(), []byte{1})
1153+
assert.Nil(err)
1154+
assert.Equal(v, []byte{1})
1155+
})
1156+
cacheCheck(true, func() {
1157+
v, err := buffer.Get(context.Background(), []byte{1})
1158+
assert.Nil(err)
1159+
assert.Equal(v, []byte{1})
1160+
})
1161+
cacheCheck(false, func() {
1162+
v, err := buffer.Get(context.Background(), []byte{2})
1163+
assert.Nil(err)
1164+
assert.Equal(v, []byte{2})
1165+
})
1166+
cacheCheck(true, func() {
1167+
assert.Nil(buffer.Set([]byte{2}, []byte{2, 2}))
1168+
})
1169+
cacheCheck(true, func() {
1170+
v, err := buffer.Get(context.Background(), []byte{2})
1171+
assert.Nil(err)
1172+
assert.Equal(v, []byte{2, 2})
1173+
})
1174+
}

0 commit comments

Comments
 (0)