-
Notifications
You must be signed in to change notification settings - Fork 110
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
feat(internal/database): badger v3 and memory implementations #3005
Conversation
e3a3f86
to
95a71f1
Compare
// Database is an in-memory database implementation. | ||
type Database struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of the in-memory database implementation package is to force the internal/database code to be flexible for additional implementations.
On top of that, I would suggest us to use this one instead of the in-memory badger database, so the badger database is only on disk. Never mind stupid idea, we should test using the badger code (if badger is the default prod db) to make sure it works with the badger underlying code. Added an InMemory
setting field for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of the in-memory database implementation package is to force the internal/database code to be flexible for additional implementations.
Does that mean it is not getting used anywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could if we add a config field for the database implementation later. Anyway for now both implementations are not used.
The memory implementation serves as a good example for another database implementation (as in it's a 'dumb' base just using Go std types).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if tests can use the badger in memory version, do we really need this in memory implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to keep it as a base for another future implementation. For example badger
uses the badger/v3 batch implementation, but some other implementations might need to implement their own batching, and it's already there in the (zero-dependency) memory implementation. I can create an issue to remove it later on once we have at least another implementation?
f6480a0
to
8069eee
Compare
4d2aa37
to
f57b5bf
Compare
d685c56
to
f886b62
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall.
Yet to look at test files.
// It defaults to the current directory if left unset. | ||
Path string | ||
// InMemory is whether to use an in-memory database. | ||
InMemory *bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesnt look like it has to be a pointer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the point of this is to detect when it's left unset, since false
is not 'unset'. That way we can set defaults as we want with a method.
More details on https://github.com/qdm12/gosettings#settings-struct I have been using this for about a year, and it has been quite scalable and nice to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need to check if it is set or unset?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be able to set defaults if it's left unset (and it extends beyond eventually, such as merging/overriding settings structs).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this is necessary. IMO InMemory
should default to false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be able to set defaults if it's left unset (and it extends beyond eventually, such as merging/overriding settings structs).
I don't think I understand this completely.
it extends beyond eventually, such as merging/overriding settings structs
May be elaborate this part. Do we need to merge or override this setting at some point?
What I am really not able to understand is that why can't we treat false as unset.
Say someone assigned a false value to inMemory, why do we care if it was set or it is there because it is default value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I am really not able to understand is that why can't we treat false as unset.
Because false
or true
are set values. The user can specify false
.
Say we want to read from two configuration sources such as flags and toml, and we want flags to take precedence.
The flag specifies --inmemory=false
and the toml config specifies inmemory = true
. If we consider false
as unset, then we would end up with a final value of true
whereas it should be false
due to precedence.
Same applies with merging/overriding settings.
It's perhaps out of scope and not used yet, but it's good code writing imo and there is no point neglecting this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we consider false as unset, then we would end up with a final value of true whereas it should be false due to precedence
If I want flag to take precedence, I would just take flag value and assign it to inMemory. It doesn't matter if it is set or not. So, if the flag says false, the value of inMemory will become false.
The way we deal with such setting is,
- initialise with default settings with, say
Default()
. - Override with say toml config (we don't need to know existing values in settings here)
- Override with say flag (we don't need existing values here as well)
At no point we need to know existing settings values.
I don't think we need inMemory to be pointer here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why we would want to support detecting an unset value for this attribute. I see no reason why the default value for InMemory
should not be false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read #3005 (comment) again. Empty value should be invalid (and for a boolean, false and true are both 'set values').
If I want flag to take precedence, I would just take flag value and assign it to inMemory. It doesn't matter if it is set or not. So, if the flag says false, the value of inMemory will become false.
Say the flag is --inmemory=false
, then your field is set to false. Then your next source (i.e. toml) sets inmemory = true
. How does it know if it should override this false
with its true
?? You can have ugly logic for every field to do this, but it's much more modular to just use a boolean pointer and if it's nil, then just set it to the value read from the settings source, otherwise leave it to its non-nil set value
EDIT: if it's such a blocker for this PR, I can switch it to a boolean but it's kinda the wrong approach for new code imo
// Database is an in-memory database implementation. | ||
type Database struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of the in-memory database implementation package is to force the internal/database code to be flexible for additional implementations.
Does that mean it is not getting used anywhere?
} | ||
|
||
// Close closes the database. | ||
func (db *Database) Close() (err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this getting used anywhere other than tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, it might make sense to use mutex here to make sure that we don't close it while someone is using the db.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this getting used anywhere other than tests?
Both implementations are not used anywhere in this PR. The badger implementation will be used in production code and in tests in the next PR. The in memory implementation could be used in tests, but really we should test using the in-memory badger implementation. So at the end of the day, that memory implementation is really just a base to serve for another implementation (zero dependency, just Go code with what should be expected in terms of implementation)
Also, it might make sense to use mutex here to make sure that we don't close it while someone is using the db.
Totally, added it 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fully replaced in #3088 now 😉 (based on this PR)
51226ca
to
0e4a4cc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the code looks good.
- Considering we have two database implementations, we should have the same test testing for both the implementations.
- It not nice that none of these functions are not getting used in this PR. Ideally, it will be nice if we have a db interface that is getting used. Then, In the same PR we can implement a db and plug it will rest of the codebase to use it to test if things are working fine. Let me know if that can't be done at the moment.
// It defaults to the current directory if left unset. | ||
Path string | ||
// InMemory is whether to use an in-memory database. | ||
InMemory *bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need to check if it is set or unset?
Yeah I get your point; using this newer interface/packages is blocked by #2831 which I'm working on now. Once this is merged, I'll rebase this branch on it and make a separate PR based on this one, which we can merge into this PR. Trying to keep this PR and the future interface-replacement PR separate to the amount of deltas in each (for reviewers), but we should merge it as a single commit in development indeed 👍 |
50fad42
to
3ea9abf
Compare
// It defaults to the current directory if left unset. | ||
Path string | ||
// InMemory is whether to use an in-memory database. | ||
InMemory *bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this is necessary. IMO InMemory
should default to false.
// Database is an in-memory database implementation. | ||
type Database struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if tests can use the badger in memory version, do we really need this in memory implementation?
I think we should wait to merge this into |
0f73b2d
to
be520e3
Compare
1b91d82
to
5875dc3
Compare
- Path cannot be the empty string, defaults to `.` - Deleting a non-existing key returns nil from badger - Remove `newTable` unneeded constructor - Add `makePrefixedKey` function, `append` is WRONG to use
Co-authored-by: Kishan Sagathiya <kishansagathiya@gmail.com>
- Add settings helper method `WithPath` - Add settings helper method `WithInMemory`
67c1c07
to
7d33f1a
Compare
5875dc3
to
1db53a5
Compare
Changes
ℹ️ This is only the standalone database implementation. I need the in-memory implementation for #2831 which in turn unblocks #2981 which will unblock the full replacement of chaindb. This doesn't affect the existing codebase for now.
internal/database
shared (return) interfaces and sentinel errorsinternal/database/memory
internal/database/badger
- based on badger/v3Tests
go test github.com/ChainSafe/gossamer/internal/database/...
Issues
Related #1973 / will create an issue for replacing the database implementation
Primary Reviewer
@timwu20