Skip to content
This repository has been archived by the owner on Jul 15, 2018. It is now read-only.

CacheDB #67

Merged
merged 12 commits into from
Nov 9, 2017
Merged

CacheDB #67

merged 12 commits into from
Nov 9, 2017

Conversation

jaekwon
Copy link
Contributor

@jaekwon jaekwon commented Oct 24, 2017

This PR is to make the tendermint/tmlibs/db CacheWrap() compatible.
We could in the future use cosmos/cosmos-sdk/state/kvcache.go (or whatever it becomes), but it's not clear whether we want to make the Cosmos KVStore/IterKVStore interfaces (designed for Merkle stores) compatible with the Tendermint DB interface (designed for LevelDB etc). Until we can unify them lets keep them separate like this for now.

Also fixes a bug where MemDB keys weren't being ordered before Merkle-rooting.
Also fixes c-leveldb compatibility. You need to run brew install leveldb or equivalent to run the tests.

NOTE: We need c-leveldb support so we should keep them as part of the tests, and fix our CI environment as necessary, instead of crippling our tests.

Copy link
Contributor

@ebuchman ebuchman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Various exported types could use some comment docs.

We can probably avoid append in many cases for efficiency.

Otherwise cool

db/cache_db.go Outdated
// Not the best, but probably not a bottleneck depending.
keys := []string{}
for key, _ := range db.cache {
keys = append(keys, key)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the least we can do this without append by allocating up front

db/cache_db.go Outdated
db.mtx.Lock()
defer db.mtx.Unlock()

db.cache[string(key)] = cDBValue{nil, true}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not DeleteNoLock?

db/cache_db.go Outdated
db.mtx.Lock()
defer db.mtx.Unlock()

db.cache[string(key)] = cDBValue{nil, true}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

db/cache_db.go Outdated

fmt.Println("CacheDB\ncache:\n")
for key, value := range db.cache {
fmt.Printf("[%X]:\t[%X]\n", []byte(key), value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the []byte(key)? I think key suffices

db/mem_batch.go Outdated
defer mtx.Unlock()

for _, op := range mBatch.ops {
if op.opType == opTypeSet {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe nicer to use a switch

if strings.HasPrefix(key, string(prefix)) {
it.keys = append(it.keys, key)
}
it.keys = append(it.keys, key)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can allocate up front


func MakeSortedKVPairs(m map[string]interface{}) []Hashable {
kvPairs := []KVPair{}
for k, v := range m {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allocate up front

func (sm *SimpleMap) Hash() []byte {
sm.kvz.Sort()
kvPairsH := []Hashable{}
for _, kvp := range sm.kvz {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allocate up front

}
{
db := NewSimpleMap()
db.Set("key1", "value1")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate test case ?

@melekes
Copy link
Contributor

melekes commented Oct 25, 2017

make metalinter_test failed with:

db/cache_db.go:134:33:warning: unnecessary conversion (unconvert)
db/backend_test.go:8:9:warning: unused variable or constant could not import github.com/tendermint/go-common (cannot find package "github.com/tendermint/go-common" in any of: (varcheck)
db/backend_test.go:17:18:warning: unused variable or constant undeclared name: common (varcheck)
db/cache_db.go:89::error: Println call ends with newline (vet)
db/cache_db.go:91::error: arg value for printf verb %X of wrong type: db.cDBValue (vet)
db/cache_db.go:93::error: Println call ends with newline (vet)
merkle/simple_proof.go:60::error: unreachable code (vet)
db/backend_test.go:8:9:warning: could not import github.com/tendermint/go-common (cannot find package "github.com/tendermint/go-common" in any of: (megacheck)
db/backend_test.go:17:18:warning: undeclared name: common (megacheck)

common "github.com/tendermint/go-common"
)

func print(x []byte) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't see where it's being used...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't this repo die long ago? To be reborn as tmlibs

db/c_level_db.go Outdated
}

func (c cLevelDBIterator) Next() bool {
c.Next()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

infinite recursion? maybe c.(*levigo.Iterator).Next()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c.Iterator.Next() should work...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or perhaps let's make it explicit to

type cLevelDBIterator struct {
      iter *levigo.Iterator
}

func (c cLevelDBIterator) Next() bool {
      c.iter.Next()
}

to avoid such problems that we previously had with 0b22b27 where the problem slipped through the cracks?

@@ -3,15 +3,14 @@ package db
import . "github.com/tendermint/tmlibs/common"

type DB interface {
Get([]byte) []byte
Get([]byte) []byte // NOTE: returns nil iff never set or deleted.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iff means if and only if

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did not know, thanks!

@@ -155,10 +154,6 @@ func (db *GoLevelDB) Iterator() Iterator {
return &goLevelDBIterator{db.db.NewIterator(nil, nil)}
}

func (db *GoLevelDB) IteratorPrefix(prefix []byte) Iterator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why delete? nobody using it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using this in iavl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should take a look at levigo/goleveldb's iterators and what's supported there... first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So does Goleveldb: https://godoc.org/github.com/syndtr/goleveldb/leveldb/iterator#IteratorSeeker

Lets adopt these as tmlibs/db.Iterator, and make MemDB support it too...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll write an exposed module function called "IteratePrefix" that takes an iterator and calls Seek() accordingly to get the same effect.

func (sm *SimpleMap) Hash() []byte {
sm.kvz.Sort()
kvPairsH := []Hashable{}
for _, kvp := range sm.kvz {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and what if the value is not Hashable? I am asking because in KVPair#Hash there is an if if kvH, ok := kv.Value.(Hashable); ok {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The KVPair is a Hashable that knows how to deal with binary objects.

@adrianbrink
Copy link
Contributor

make metalinter_test fails. Either we have to fix up all the warnings once or we disable it for now but run it locally and fix a little bit everytime.

Copy link
Contributor

@cloudhead cloudhead left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We make heavy use of IteratorPrefix in iavl and it's all-around useful for iterating over ordered key-spaces, so we should keep it.

Set([]byte, []byte)
SetSync([]byte, []byte)
Delete([]byte)
DeleteSync([]byte)
Close()
NewBatch() Batch
Iterator() Iterator
IteratorPrefix([]byte) Iterator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this for iavl.

@@ -155,10 +154,6 @@ func (db *GoLevelDB) Iterator() Iterator {
return &goLevelDBIterator{db.db.NewIterator(nil, nil)}
}

func (db *GoLevelDB) IteratorPrefix(prefix []byte) Iterator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using this in iavl.

"testing"

"github.com/stretchr/testify/assert"
// "github.com/stretchr/testify/require"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can delete this.

Makefile Outdated
@@ -33,15 +33,15 @@ metalinter_test: ensure_tools
--enable=goconst \
--enable=gosimple \
--enable=ineffassign \
--enable=interfacer \
--enable=interfacer \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did all this spacing get weird?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mh, some had two spaces instead of tabs. Everything is now intended with 2 tabs.

db/c_level_db.go Outdated
@@ -49,6 +51,8 @@ func NewCLevelDB(name string, dir string) (*CLevelDB, error) {
return database, nil
}

// Get gets the value for the given key. It returns ErrNotFound if the DB does not contains the
Copy link
Contributor

@ebuchman ebuchman Oct 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't sound right. The function panics on err, and should return nil if the key is not there. Is that not what it does?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. That doesn't sound right. I think I might have thought about something else while typing this :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comments are not necessary, we should just comment the interface functions (for DB and Iterator)

db/c_level_db.go Outdated
@@ -19,13 +19,15 @@ func init() {
registerDBCreator(CLevelDBBackendStr, dbCreator, false)
}

// CLevelDB provides access to the C implementation of LevelDB
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a XXX comment that these methods panic on error from the underlying DB

db/cache_db.go Outdated
@@ -37,63 +41,77 @@ func (db *CacheDB) Get(key []byte) []byte {
return db.parent.Get(key)
}

// Set sets the value for the given key in the cache. It does not write the value to the
// underlying database. Set does not incur a performance penalty compared to SetNoLock.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's all this about performance penalty? These functions lock a mutex while XxxNoLock does not

db/cache_db.go Outdated
func (db *CacheDB) DeleteNoLock(key []byte) {
db.cache[string(key)] = cDBValue{nil, true}
}

// Close closes the underlying database by deallocating the handle. Any attempts to use the DB
// after close will result in a panic.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably isn't true, maybe we should clarify that the db should not be used after Close. But it won't panic in this case I don't thiknk ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. It depends on the backing database.

.gitignore Outdated
@@ -1,4 +1,5 @@
*.swp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also do

*.sw[op]

and it's awesome to know that you too @jaekwon use Vim!

@@ -11,7 +11,7 @@ all: test
NOVENDOR = go list github.com/tendermint/tmlibs/... | grep -v /vendor/

test:
go test `glide novendor`
go test -tags gcc `glide novendor`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am not too sure if we should always be including the gcc tag, since on my Mac this is going to trip out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come it trips on your Mac? It runs fine on macOS 10.13 for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me... go 1.8.1, 10.12.x

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha nice, my Mac has issues with gcc tags but I'll try again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was where I first tripped out tendermint/merkleeyes#39 (comment) 2 months ago.

db/cache_db.go Outdated
if ok {
return value.value
}
return db.parent.Get(key)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't see where we memoize the cache-missed value for this, perhaps let's do this.

value, ok := db.cache[string(key)]
if !ok {
   data := db.parent.Get(key)
   value = cDBValue{value: data}
   db.cache[string(key)] = value
}
return value.value

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also should we perhaps check if deleted { return nil }?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about that too. Maybe @jaekwon can clarify how this functionality is supposed to work. I got the understanding that you had to explicitly add values to the cache.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh, yeah.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if deleted, value.value will be nil.

db/cache_db.go Outdated
}

// SetNoLock sets the value for the given key in the database. It does not write the value to the
// underlying database. SetNoLock implements atomicSetDeleter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come we are referring to an atomicSetDeleter unexported interface?
Perhaps could we just do a compile type assertion and not mention it? e.g

var _ atomicSetDeleter = (*CacheDB)(nil)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

db/cache_db.go Outdated
// DeleteNoLock deletes the value for the given key. It does not delete the value from the
// underlying database. DeleteNoLock implements atomicSetDeleter.
func (db *CacheDB) DeleteNoLock(key []byte) {
db.cache[string(key)] = cDBValue{nil, true}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need mutex protection here :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the function is called DeleteNoLock

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahah, thanks for that catch @cloudhead I made that suggestion without noticing that.

db/cache_db.go Outdated

stats := make(map[string]string)
stats["cache.size"] = fmt.Sprintf("%d", len(db.cache))
mergeStats(db.parent.Stats(), stats, "parent.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


// Clear the cache
db.cache = make(map[string]cDBValue)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice routine!

db/mem_db.go Outdated
delete(db.db, string(key))
}

// Close is a noop.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the doc!

func (kvps KVPairs) Len() int { return len(kvps) }
func (kvps KVPairs) Less(i, j int) bool { return kvps[i].Key < kvps[j].Key }
func (kvps KVPairs) Swap(i, j int) { kvps[i], kvps[j] = kvps[j], kvps[i] }
func (kvps KVPairs) Sort() { sort.Sort(kvps) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tagged you on the internal thread about the support for Go1.8+ where we'll later on just use sort.Slice that is

sort.Slice(kvPairs, func(i, j int) bool {
   kvi, kvj := kvPairs[i], kvPairs[j]
   return kvi.Key < kvj.Key
})

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iavl only compiles with go 1.8+
I questioned this, and bucky, alexis, and adrian all spoke in favor of making this a requirement. It is commented in cosmos-sdk (due to iavl dependency), let's just update everything to 1.8

if computedHash == nil {
return false
}
if !bytes.Equal(computedHash, rootHash) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return computedHash != nil && bytes.Equal(computedHash, rootHash)

}
switch total {
case 0:
panic("Cannot call computeHashFromAunts() with 0 total")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And for a nerdy detour and if you get bored: this is one of my favorite compiler checks. We don't need the return nil below because panic is a terminating statement :) as per

Copy link
Contributor

@odeke-em odeke-em left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @jaekwon for the PR, I've added a few suggestions, PTAL.

db/c_level_db.go Outdated
}

func (c cLevelDBIterator) Close() {
c.Close()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c.itr.Close()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jesus. thanks.

db/c_level_db.go Outdated
}

func (c cLevelDBIterator) GetError() error {
return c.GetError()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c.itr.GetError()?

@@ -7,7 +7,7 @@ import (
"fmt"
"testing"

. "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/common"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the convention is cmn

db/cache_db.go Outdated

value, ok := db.cache[string(key)]
if !ok {
fmt.Println("1", string(key))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these debug statements?

"github.com/tendermint/tmlibs/common"
)

func checkValid(t *testing.T, itr Iterator, expected bool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ these wrappers

}

func checkNextPanics(t *testing.T, itr Iterator) {
defer func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://godoc.org/github.com/stretchr/testify/assert#Panics

assert.Panics(t, func(){ itr.Next() }, "Next expected panic but didn't")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay

}

func checkPrevPanics(t *testing.T, itr Iterator) {
defer func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

}

func newTempDB(t *testing.T, backend string) (db DB) {
dir, dirname := common.Tempdir(fmt.Sprintf("test_go_iterator"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for fmt.Sprintf

}
if !it.source.Valid() {
it.invalid = true
return false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it.invalid {
  return false
}
it.invalid = !it.source.Valid()
return !it.invalid

db/util.go Outdated
return pi.itr.Value()
}

func (pi *prefixIterator) Close() {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we delegate to pi.itr? pi.itr.Close()

@@ -7,7 +7,7 @@ import (
"fmt"
"testing"

. "github.com/tendermint/tmlibs/common"
cmn "github.com/tendermint/tmlibs/common"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

db/cache_db.go Outdated
defer db.mtx.Unlock()

value, ok := db.cache[string(key)]
if !ok {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other check we might need, I had mentioned in the previous review but in later comments: we perhaps want to check that value.deleted is not set lest that's a discarded value in the cache i.e

if !ok || value.deleted {
}

What do y'all think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not value.deleted... if value.deleted in the cache, we don't want to query the base store.

if value.deleted, value.value is nil btw.


func (db *CacheDB) Set(key []byte, value []byte) {
db.mtx.Lock()
defer db.mtx.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now here I am being a bit painful about this, but I believe we can squeeze even better perhaps by avoiding defers whenever we can, as they are ~2X slower than non-defers, please see https://gist.github.com/odeke-em/f8fb3a8695b33dd06a700fc3473f3e62.
Could we please make all these:

db.mtx.Lock()
db.SetNoLock(key, value)
db.mtx.Unlock()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I insist that we keep the defers until we find that it's a bottleneck... it's defensive programming that IMO is well worth it.


func (db *CacheDB) SetSync(key []byte, value []byte) {
db.mtx.Lock()
defer db.mtx.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto about the defers


func (db *CacheDB) Delete(key []byte) {
db.mtx.Lock()
defer db.mtx.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto about the defers


func (db *CacheDB) DeleteSync(key []byte) {
db.mtx.Lock()
defer db.mtx.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto about the defers


func (db *CacheDB) Close() {
db.mtx.Lock()
defer db.mtx.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto about the defers

db.parent.Close()
}

func (db *CacheDB) Print() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So while Print is useful from an external perspective, it is not that easy to test. IMHO we could do something like

func (db *CacheDB) Fprint(w io.Writer) {
   db.mtx.Lock()
   defer db.mtx.Unlock()

   fmt.Fprintln(w, "CacheDB\ncache:")
   for key, value := range db.cache {
         fmt.Fprintf(w, "[%X]:\t[%v\n", key, value)
   }
   fmt.Fprintln(w, "\nparent:")
   db.parent.Print()
}

func (db *CacheDB) Print() { db.Fprint(os.Stdout) }

and then properly test the order and expected out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't want to test Print functions TBH.

@@ -24,17 +25,59 @@ type Batch interface {
Write()
}

/*
Usage:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Me likey!

@@ -6,7 +6,7 @@ import (
"fmt"
"testing"

. "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/common"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit, the convention seemingly used is cmn instead of common ie

cmn "github.com/tendermint/tmlibs/common"

db/mem_batch.go Outdated
type opType int

const (
opTypeSet = 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We missed setting the opType here so

const (
   opTypeSet opType = 1
   opTypeDelete opType = 2
)

}

func (mBatch *memBatch) Write() {
mtx := mBatch.db.Mutex()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

mBatch.ops = append(mBatch.ops, operation{opTypeDelete, key, nil})
}

func (mBatch *memBatch) Write() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write? Flush perhaps? Am just bikeshedding on the name, it doesn't matter much :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean you're yak-shaving the lingo?

db/mem_db.go Outdated
@@ -28,15 +29,30 @@ func (db *MemDB) Get(key []byte) []byte {
return db.db[string(key)]
}

func (db *MemDB) GetOK(key []byte) ([]byte, bool) {
db.mtx.Lock()
defer db.mtx.Unlock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here about an unnecessary defer.

db/mem_db.go Outdated
}

func (db *MemDB) SetSync(key []byte, value []byte) {
db.mtx.Lock()
defer db.mtx.Unlock()
db.SetNoLock(key, value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here about the unnecessary defer.

Copy link
Contributor

@odeke-em odeke-em left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks @jaekwon for the round of review addressing. I just have one last set of requests then am gucci to approve, most especially about some defers that we could shed, PTAL.

@jaekwon jaekwon dismissed ebuchman’s stale review October 31, 2017 02:46

I undid adrian's pushes.

Copy link
Contributor

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, it looks quite nice, and I like the design.

Two points about CacheWrap, returning something more useful than interface{} and separating read and write caches. Otherwise, I like it.


// Seek moves the iterator the position of the key given or, if the key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So seek is like the replacement for IteratorPrefix?
Can you add in the example a check to show how to iterate in a range? Cuz if we use seek to find the beginning, there is probably an end we also want to see.

But yeah, Seek() with adding Prev() as well as Next() seems like a nice generalization that allows a lot more flexibility to whoever uses this. Just show those example usages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the replacement for IteratorPrefix is db.IteratePrefix(db DB, prefix []byte) Iterator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides using db.IteratePrefix, You'll just have to manually stop iteration when you reach the end that you desire

Stats() map[string]string

// CacheWrap wraps the DB w/ a CacheDB.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this return interface{}? It provides no guarantees.

In cachedb_test.go, I see this line often:
cdb := mem.CacheWrap().(*CacheDB)

If the only way to use it is to cast it to a particular type, then just force that type.
If you want people to be able to use the CacheWrap without forcing a given type, then return a usable interface,that exposes some subset of the DB interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it seems *CacheDB implements the entire DB interface, why not return it there? Or remove a method or two that you don't want to require from Caches...

type Cache interface {
.....
}

type DB interface {
  Cache
  NewBatch() Batch
  Iterate() Iterator
}

then we could just use cdb := mem.CacheWrap()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. But CacheWrap also exists in the SDK for the merkle stores, and those don't conform to tmlibs/db.DB...

require.Panics(t, func() { cdb2.Write() })
}

func TestCacheDBNested(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good tests. Maybe add a few more.

What if we make two caches A, and B from one parent, and then try to write them both?
Is this allowed? Does it return an error? If it is allowed, what is supposed to happen?

Test cases are a great way to document and enforce this case.

db/cache_db.go Outdated
}

// CacheDB wraps an in-memory cache around an underlying DB.
type CacheDB struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really should have two caches, a read cache and a write cache... Otherwise, if we query a value, we write that to the db again. Not only is this wasteful use of IO, but with the version tree, this actually changes the hash.

https://github.com/cosmos/cosmos-sdk/blob/develop/state/kvcache.go#L6-L11

Issue:
cosmos/gaia#41
cosmos/cosmos-sdk#271

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixing so that we have a "dirty" flag.

@jaekwon jaekwon changed the base branch from develop to sdk-develop November 9, 2017 22:41
@jaekwon jaekwon merged commit 8481c49 into sdk-develop Nov 9, 2017
ebuchman pushed a commit that referenced this pull request Dec 31, 2017
* Add CacheDB & SimpleMap
* Generic memBatch; Fix cLevelDB tests
* CacheWrap() for CacheDB and MemDB
* Change Iterator to match LeviGo Iterator
* Fixes from review
* cacheWrapWriteMutex and some race fixes
* Use tmlibs/common
* NewCWWMutex is exposed.  DB can be CacheWrap'd
* Remove GetOK, not needed
* Fsdb (#72)
* Add FSDB
* Review fixes from Anton
* Review changes
* Fixes from review
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants