-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Clean up store interface #823
Comments
type ReadOnlyKVStore interface {
Iterate(start, end []byte, reverse bool) (Iterator, error)
} Iterator is lazy so the limit parameter is not necessary. You can I think another improvement is to return Third improvement I can think of is to add
|
Thanks @husio I agree with your first two points above. For the third one, let's think of what this means first, related to #822:
The first one adds run-time dependencies that we cannot check compile-time and I don't like it. |
I think we don't need the reverse parameter at all. When type ReadOnlyKVStore interface {
// Iterate returns an iterator over data set that key is from given range.
// If end value is smaller than start value than the iteration order is reversed.
Iterate(start, end []byte) (Iterator, error)
} regular := db.Iterate([]byte("alice"), []byte("charlie"))
reversed := db.Iterate([]byte("zorg"), []byte("bob")) |
I thought that too, but there is a problem. The ends can often be I guess How do I iterate reverse from end to alice, or alice to beginning? Also, which order should I tried this (remove reverse param) API way back in the tendermint days in early IAVL refactors and realized you couldn't do without. Also, it was nice for usability to always have (low, high) in that order. Adding one argument to remove a method does make sense to me though |
Good point. db.Iterate([]byte("alice"), store.MaxKey) \\ iterate from alice to the end and
db.Iterate(store.MinKey, []byte("alice")) \\ iterate from beginning to alice. Both variables (they cannot be
|
How about we simplify this by having something like
I don't really like the MinKey, MaxKey approach as it's not user-friendly and only makes sense when you have just talked about it. |
I agree with Roman here. Trying to remove an argument or two just obfuscates the API. I think both
as well as
are fine and equivalent. The version with two methods is a bit more work to implement, but much cleaner when reading and using ("what does that |
Agree, both are valid options. The first example is what we have already just different naming, so I like it slightly more. I was wondering if the |
IMHO it would be better to think about this as a command that uses a KV store rather than having this as an interface method. I would prefer I am not sure if moving the commands to the Off topic but CacheWrap could be used for atomic batches. |
This is a great improvement! |
Interesting idea. Does this make it simpler? More complex? I had an idea with options to configure the Iterator, but @husio thought that made it harder to use. I would say that Ascending vs. Descending choice needs to be made in the constructor. Ends, offsets, or limits can be added as wrappers. |
I actually did that (including renaming to Batch and removing the Batch interface), and when I was 80% with updating the code, I discovered why I did this.
I wanted to use custom batch wrapper. I threw away the code (maybe 40 minutes work), but I would first look for a clean design where all implementations of |
They are somehow atomic. But we crash in the midst of Also, if you look at the implementation: func (b *NonAtomicBatch) Write(ctx context.Context) error {
for _, Op := range b.ops {
err := Op.Apply(ctx, b.out)
if err != nil {
return err
}
}
b.ops = nil
return nil
} If I have 5 operations and the 3rd one errors, then the first two were executed on the underlying store and cannot be reverted. This is fine as used now, but since The only other approach would be that the backing store explicitly expose
We can gladly discuss this point deeper, but at least I explain the current design here. |
Back to do-able changes...
There are two implementations we use: func (b BTreeCacheable) CacheWrap() KVCacheWrap {
// TODO: reuse FreeList between multiple cache wraps....
// We create/destroy a lot per tx when processing a block
return NewBTreeCacheWrap(b.KVStore, b.NewBatch(), nil)
} func (b BTreeCacheWrap) CacheWrap() KVCacheWrap {
// TODO: reuse FreeList between multiple cache wraps....
// We create/destroy a lot per tx when processing a block
return NewBTreeCacheWrap(b, b.NewBatch(), b.free)
} They are basically the same algorithm, just different arguments, and we can definitely pull this out of the interface into a package-level helper function. Especially once we replace However... currently the interfaces are defined in weave and all implementation in I think this is no problem in practice, but the more we have packages importing sibling packages, the more risk we have of circular dependencies. (I am fine to do this as discussed, just want some reflection on the import impact first) |
Is your feature request related to a problem? Please describe.
There have been some comments that this could be done better. It was one of the first APIs in weave, and we have learned a lot since then. I'm sure we can improve it.
For example, we now have a much simpler Iterator
Let us consider the following interfaces here:
Describe the solution you'd like
Some thoughts on cleanup:
NewBatch()
fromKVStore
? What was my thinking there?CacheableKVStore
andKVCacheWrap
?Iterator
andReverseIterator
?Describe alternatives you've considered
One concrete suggestion is to make iterator take functional parameters
Additional context
Add any other context or screenshots about the feature request here.
The text was updated successfully, but these errors were encountered: