Skip to content
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

Merged
merged 14 commits into from
May 16, 2017
Merged

database/sinkdb: new package #1192

merged 14 commits into from
May 16, 2017

Conversation

jbowens
Copy link
Contributor

@jbowens jbowens commented May 15, 2017

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.

// 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)
Copy link
Contributor

Choose a reason for hiding this comment

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

✨ pretty!

@jbowens jbowens added the PTAL label May 15, 2017
confState raftpb.ConfState
done bool

// Current log position, accessed only from runUpdates goroutine
snapIndex uint64
}

type State interface {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, got it. Clever.

}
}

func (sv *Service) get(ctx context.Context, key string) ([]byte, error) {
func (sv *Service) linearizeRead(ctx context.Context) error {
Copy link
Contributor

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.

Copy link
Contributor

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

Copy link
Contributor

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.


// 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) {
Copy link
Contributor

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 🚲

Copy link
Contributor Author

@jbowens jbowens May 15, 2017

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. ¯\_(ツ)_/¯

Copy link
Contributor

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 {
Copy link
Contributor

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)

Copy link
Contributor Author

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").

Copy link
Contributor

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this is great

raftDB *raft.Service
raftPrefix string
sdb *sinkdb.DB
kvPrefix string
Copy link
Contributor

Choose a reason for hiding this comment

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

What about keyPrefix?

@tessr
Copy link
Contributor

tessr commented May 15, 2017

Just a few 🚲 -type comments, but this is starting to look really good!

if err != nil {
return nil, errors.Wrap(err)
}

grants := grantList.Grants
grants := grantList.GetGrants()
Copy link
Contributor

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.


// IfNotExists encodes a conditional to make an instruction
// successful only if the provided key does not exist.
func IfNotExists(key string) (op Op) {
Copy link
Contributor

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

// 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 {
Copy link
Contributor

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.

// 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
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 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.)

}
}

func (sv *Service) get(ctx context.Context, key string) ([]byte, error) {
func (sv *Service) linearizeRead(ctx context.Context) error {
Copy link
Contributor

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.

raft *raft.Service
}

type Op struct {
Copy link
Contributor

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.

return false, err
}
buf := db.state.get(key)
if len(buf) == 0 {
Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

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 {
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 delete this whole if stmt right? (b/c grantList.Grants will be empty)

for _, p := range core.Policies {
err = rDB.Delete(ctx, core.GrantPrefix+p)
err = sdb.Exec(ctx, sinkdb.Delete(core.GrantPrefix+p))
Copy link
Contributor

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.

Copy link
Contributor

@tessr tessr left a 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?

@kr
Copy link
Contributor

kr commented May 16, 2017

LGTM too.

jbowens and others added 14 commits May 16, 2017 17:24
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.
@iampogo iampogo force-pushed the rethink-kitchen-sink-db branch from f84756f to 3dd3b5a Compare May 16, 2017 17:24
@iampogo iampogo merged commit 28d3ee5 into main May 16, 2017
@iampogo iampogo deleted the rethink-kitchen-sink-db branch May 16, 2017 17:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants