forked from TrueCloudLab/neoneo-go
191cc45032
MemoryStore is used in a MemCachedStore as a persistent layer in tests. Further commits suppose that persistent storage returns sorted values from Seek, so sort the result of MemoryStore.Seek. Benchmark results for 10000 matching items in MemoryStore compared to master: name old time/op new time/op delta MemorySeek-8 712µs ± 0% 3850µs ± 0% +440.52% (p=0.000 n=8+8) name old alloc/op new alloc/op delta MemorySeek-8 160kB ± 0% 2724kB ± 0% +1602.61% (p=0.000 n=10+8) name old allocs/op new allocs/op delta MemorySeek-8 10.0k ± 0% 10.0k ± 0% +0.24% (p=0.000 n=10+10) For details on implementation efficiency see the https://github.com/nspcc-dev/neo-go/pull/2193#discussion_r722993358.
168 lines
3.8 KiB
Go
168 lines
3.8 KiB
Go
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
|
)
|
|
|
|
// MemoryStore is an in-memory implementation of a Store, mainly
|
|
// used for testing. Do not use MemoryStore in production.
|
|
type MemoryStore struct {
|
|
mut sync.RWMutex
|
|
mem map[string][]byte
|
|
// A map, not a slice, to avoid duplicates.
|
|
del map[string]bool
|
|
}
|
|
|
|
// MemoryBatch is an in-memory batch compatible with MemoryStore.
|
|
type MemoryBatch struct {
|
|
MemoryStore
|
|
}
|
|
|
|
// Put implements the Batch interface.
|
|
func (b *MemoryBatch) Put(k, v []byte) {
|
|
b.MemoryStore.put(string(k), slice.Copy(v))
|
|
}
|
|
|
|
// Delete implements Batch interface.
|
|
func (b *MemoryBatch) Delete(k []byte) {
|
|
b.MemoryStore.drop(string(k))
|
|
}
|
|
|
|
// NewMemoryStore creates a new MemoryStore object.
|
|
func NewMemoryStore() *MemoryStore {
|
|
return &MemoryStore{
|
|
mem: make(map[string][]byte),
|
|
del: make(map[string]bool),
|
|
}
|
|
}
|
|
|
|
// Get implements the Store interface.
|
|
func (s *MemoryStore) Get(key []byte) ([]byte, error) {
|
|
s.mut.RLock()
|
|
defer s.mut.RUnlock()
|
|
if val, ok := s.mem[string(key)]; ok {
|
|
return val, nil
|
|
}
|
|
return nil, ErrKeyNotFound
|
|
}
|
|
|
|
// put puts a key-value pair into the store, it's supposed to be called
|
|
// with mutex locked.
|
|
func (s *MemoryStore) put(key string, value []byte) {
|
|
s.mem[key] = value
|
|
delete(s.del, key)
|
|
}
|
|
|
|
// Put implements the Store interface. Never returns an error.
|
|
func (s *MemoryStore) Put(key, value []byte) error {
|
|
newKey := string(key)
|
|
vcopy := slice.Copy(value)
|
|
s.mut.Lock()
|
|
s.put(newKey, vcopy)
|
|
s.mut.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// drop deletes a key-value pair from the store, it's supposed to be called
|
|
// with mutex locked.
|
|
func (s *MemoryStore) drop(key string) {
|
|
s.del[key] = true
|
|
delete(s.mem, key)
|
|
}
|
|
|
|
// Delete implements Store interface. Never returns an error.
|
|
func (s *MemoryStore) Delete(key []byte) error {
|
|
newKey := string(key)
|
|
s.mut.Lock()
|
|
s.drop(newKey)
|
|
s.mut.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// PutBatch implements the Store interface. Never returns an error.
|
|
func (s *MemoryStore) PutBatch(batch Batch) error {
|
|
b := batch.(*MemoryBatch)
|
|
return s.PutChangeSet(b.mem, b.del)
|
|
}
|
|
|
|
// PutChangeSet implements the Store interface. Never returns an error.
|
|
func (s *MemoryStore) PutChangeSet(puts map[string][]byte, dels map[string]bool) error {
|
|
s.mut.Lock()
|
|
for k := range puts {
|
|
s.put(k, puts[k])
|
|
}
|
|
for k := range dels {
|
|
s.drop(k)
|
|
}
|
|
s.mut.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// Seek implements the Store interface.
|
|
func (s *MemoryStore) Seek(key []byte, f func(k, v []byte)) {
|
|
s.mut.RLock()
|
|
s.seek(key, f)
|
|
s.mut.RUnlock()
|
|
}
|
|
|
|
// SeekAll is like seek but also iterates over deleted items.
|
|
func (s *MemoryStore) SeekAll(key []byte, f func(k, v []byte)) {
|
|
s.mut.RLock()
|
|
defer s.mut.RUnlock()
|
|
sk := string(key)
|
|
for k, v := range s.mem {
|
|
if strings.HasPrefix(k, sk) {
|
|
f([]byte(k), v)
|
|
}
|
|
}
|
|
for k := range s.del {
|
|
if strings.HasPrefix(k, sk) {
|
|
f([]byte(k), nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
// seek is an internal unlocked implementation of Seek.
|
|
func (s *MemoryStore) seek(key []byte, f func(k, v []byte)) {
|
|
sk := string(key)
|
|
var memList []KeyValue
|
|
for k, v := range s.mem {
|
|
if strings.HasPrefix(k, sk) {
|
|
memList = append(memList, KeyValue{
|
|
Key: []byte(k),
|
|
Value: v,
|
|
})
|
|
}
|
|
}
|
|
sort.Slice(memList, func(i, j int) bool {
|
|
return bytes.Compare(memList[i].Key, memList[j].Key) < 0
|
|
})
|
|
for _, kv := range memList {
|
|
f(kv.Key, kv.Value)
|
|
}
|
|
}
|
|
|
|
// Batch implements the Batch interface and returns a compatible Batch.
|
|
func (s *MemoryStore) Batch() Batch {
|
|
return newMemoryBatch()
|
|
}
|
|
|
|
// newMemoryBatch returns new memory batch.
|
|
func newMemoryBatch() *MemoryBatch {
|
|
return &MemoryBatch{MemoryStore: *NewMemoryStore()}
|
|
}
|
|
|
|
// Close implements Store interface and clears up memory. Never returns an
|
|
// error.
|
|
func (s *MemoryStore) Close() error {
|
|
s.mut.Lock()
|
|
s.del = nil
|
|
s.mem = nil
|
|
s.mut.Unlock()
|
|
return nil
|
|
}
|