-
Notifications
You must be signed in to change notification settings - Fork 362
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
database/sinkdb: new package #1192
Conversation
// storage, the config will be added to sinkdb. | ||
func Load(ctx context.Context, db pg.DB, sdb *sinkdb.DB) (*Config, error) { | ||
c := new(Config) | ||
found, err := sdb.Get(ctx, "/core/config", c) |
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.
✨ pretty!
confState raftpb.ConfState | ||
done bool | ||
|
||
// Current log position, accessed only from runUpdates goroutine | ||
snapIndex uint64 | ||
} | ||
|
||
type State interface { |
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 is this an interface? is there more than one implementation of this type?
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 circumvent a bidirectional dependency. I was hoping that database/raft would only be raft-specific logic and completely ignorant of its use in implementing a kv store, but it needs a way to access and modify the raft state. This interface keeps key-value logic out of the raft package but still lets it interact with the key-value state.
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.
Ah, got it. Clever.
database/raft/raft.go
Outdated
} | ||
} | ||
|
||
func (sv *Service) get(ctx context.Context, key string) ([]byte, error) { | ||
func (sv *Service) linearizeRead(ctx context.Context) 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.
I know this is wordier, but could we call this linearizableRead
instead? linearizeRead
sounds like we are taking a read and linearizing it, whatever that means.
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.
Oh, wait, I suppose that's actually what we're doing here. nevermind
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 also just be a lowercase version of the public method, so requestRead
.
database/sinkdb/sinkdb.go
Outdated
|
||
// GetInconsistent performs a non-linearizable read of the provided key. | ||
// The value may be stale. The read value is unmarshalled into v. | ||
func (db *DB) GetInconsistent(key string, v proto.Message) (found bool, 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.
🚲 I prefer the word "stale" to "inconsistent" but 🚲
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 co-opted the terminology from cockroach's consistency levels: cockroachdb/cockroach#343 (comment)
I get the point Spencer's making since in many cases, the value will not actually be stale, but I'm not sure if "inconsistent" is better. ¯\_(ツ)_/¯
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.
🚲 🚲 🚲 whatever you want to do is fine, ultimately 🚲 🚲 🚲 🚲 🚲
🚲
|
||
// Exec executes the provided operations. If all of the provided conditionals | ||
// are met, all of the provided effects are applied atomically. | ||
func (db *DB) Exec(ctx context.Context, ops ...Op) 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.
Does this function get us enough atomicity to do things like migrate multiple tables from pg? (e.g. right now we migrate config and access tokens separately, but that should be atomic)
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.
Oo, I think so. We should be able to pass in all the Set operations and a single sinkdb.IfNotExists("/core/config")
.
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.
Awesome! Do you want to add a TODO to the migration function?
|
||
// AddAllowedMember configures sinkdb to allow the provided address | ||
// to participate in Raft. | ||
func (db *DB) AddAllowedMember(ctx context.Context, addr string) 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 there a reason that isAllowedMember
is on the state and AddAllowedMember
is not? Is it just because AddAllowedMember
needs to be exported?
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.
Yeah, it needs to be exported and it needs to access raft. The state
struct doesn't have access to the *raft.Service
struct because the state
struct is created and passed in to raft.Start
|
||
return b | ||
func (s *state) EmptyWrite() (instruction []byte) { |
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.
this is great
net/http/authz/authorizer.go
Outdated
raftDB *raft.Service | ||
raftPrefix string | ||
sdb *sinkdb.DB | ||
kvPrefix string |
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 about keyPrefix
?
Just a few 🚲 -type comments, but this is starting to look really good! |
net/http/authz/grant.go
Outdated
if err != nil { | ||
return nil, errors.Wrap(err) | ||
} | ||
|
||
grants := grantList.Grants | ||
grants := grantList.GetGrants() |
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.
Nit: I think this can still be just .Grants
no? Because grantList is a struct not a pointer.
database/sinkdb/sinkdb.go
Outdated
|
||
// IfNotExists encodes a conditional to make an instruction | ||
// successful only if the provided key does not exist. | ||
func IfNotExists(key string) (op Op) { |
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.
Nit: maybe this should not have the final 's', like https://godoc.org/os#IsNotExist, idk
database/raft/raft.go
Outdated
// guaranteed not to read stale data.) | ||
// This can be slow; for faster but possibly stale reads, see Stale. | ||
func (sv *Service) Get(ctx context.Context, key string) ([]byte, error) { | ||
func (sv *Service) RequestRead(ctx context.Context) 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.
Nit: if you're happy with this name then cool, but if not then consider WaitRead
.
database/raft/raft.go
Outdated
// Set won't have changed the value again, but it is | ||
// RequestRead requests a linearizable read. Upon successful return, | ||
// the caller is guaranteed that a read from the Service's state will | ||
// be linearizable; this is, if a Set happens before a Get, the Get |
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.
We can be a lot more concrete with this description now that this method has such a good interface.
"RequestRead requests a linearizable read. Upon successful return, subsequent reads will observe all writes that happened before the call to RequestRead. (There is still …)"
(Or possibly "WaitRead waits for…" if you go with that name.)
database/raft/raft.go
Outdated
} | ||
} | ||
|
||
func (sv *Service) get(ctx context.Context, key string) ([]byte, error) { | ||
func (sv *Service) linearizeRead(ctx context.Context) 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.
It could also just be a lowercase version of the public method, so requestRead
.
database/sinkdb/sinkdb.go
Outdated
raft *raft.Service | ||
} | ||
|
||
type Op 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.
Maybe think about putting this type and its constructors in a separate file op.go. I have a feeling the list will grow in the future.
database/sinkdb/sinkdb.go
Outdated
return false, err | ||
} | ||
buf := db.state.get(key) | ||
if len(buf) == 0 { |
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.
Are we sure this is a safe way to check for "found"? A zero-value protobuf can serialize to nil, which we'll happily insert into the state map (and which will correctly unmarshal back to the zero-value struct). In many cases it prob won't matter, but I think it would be good if the found
value returned by this func exactly corresponds to the condition that's tested by IfNotExists
.
We were already doing it this way before, so maybe we don't need to change now. But could we at least add a todo?
I think the "right way" would be for get
to return a bool and have Get
just look at the bool, not the value, before unmarshaling.
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.
yeah, agreed it's wonky. i'll TODO it for now
core/grants.go
Outdated
if err != nil { | ||
var grantList authz.GrantList | ||
found, err := a.sdb.Get(ctx, GrantPrefix+x.Policy, &grantList) | ||
if err != nil || !found { |
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.
When found is false, this will return errors.Wrap(nil)
, which is nil
, which is the right thing to do, but maybe deserves a comment?
core/grants.go
Outdated
if err != nil { | ||
return nil, errors.Wrap(err) | ||
} | ||
if data == nil { | ||
if !ok { |
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.
We could also delete this whole if stmt right? (b/c grantList.Grants will be empty)
core/coreunsafe/reset.go
Outdated
for _, p := range core.Policies { | ||
err = rDB.Delete(ctx, core.GrantPrefix+p) | ||
err = sdb.Exec(ctx, sinkdb.Delete(core.GrantPrefix+p)) |
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.
Todo: all these deletes in a big batch.
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.
This LGTM, but maybe wait for @kr to sign off, too?
LGTM too. |
Split database/raft into two packages, one handling raft-specific logic, the other exposing a consistent kv store interface. database/raft manages the raft.Node and exposes http endpoints for communication between raft nodes. database/sinkdb implements a consistent kv store on top of database/raft. For now, kv data is stored in a simple protocol buffer persisted to the filesystem.
f84756f
to
3dd3b5a
Compare
Split database/raft into two packages, one handling raft-specific logic,
the other exposing a consistent kv store interface.
database/raft manages the raft.Node and exposes http endpoints for
communication between raft nodes.
database/sinkdb implements a consistent kv store on top of
database/raft. For now, committed kv data is stored in a simple protocol
buffer persisted to the filesystem.