-
Notifications
You must be signed in to change notification settings - Fork 816
Added KV Store client that mirrors one backend to another #1749
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
Conversation
TODO: think about how to do this without killing consul/etcd with too many requests. If every component (distributor/ingester/querier) does the mirroring all the time, that's probably little too much. |
Cleaned up the code a little bit, and added:
|
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.
LGTM, I just have a few questions
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.
LGTM, just some nitpicky things 🙂
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.
Very good job @pstibrany! I left few minor non-blocking comments.
Tomorrow morning I will re-review the cancel fn logic in the multi client, cause I'm feeling tired and I may have miss something. Great job again!
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.
LGTM
Rebased on top of master, and squashed to single commit, to ease possible future rebases. |
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.
Changes look really good to me, great job!
Just a small change to do to fix an issue with limits changes.
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.
Good job @pstibrany! I left a couple of non blocking comments, so I'm approving it, given - to my 👀 - the changes look good.
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.
So my big comment would be around simplifying the entire thing to be: Write to both, read from primary. Also, forget about inflight ops, just do it on the primary client when we initiated the call.
|
||
// Runs supplied fn with current primary client. If primary client changes, fn is restarted. | ||
// When fn finishes (with or without error), this method returns given error value. | ||
func (m *MultiClient) runWithPrimaryClient(origCtx context.Context, fn func(newCtx context.Context, primary kvclient) error) 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.
Actually can we make the requirements laxer? Instead of the whole inProgress
and cancellation, can we just run the function against the primary client and forget about it changing midway?
I wonder what the worst case scenario is then.
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.
WatchKey
and WatchPrefix
operations are long-running, basically designed to never return (eg. see usage in pkg/ring/ring.go:155 or pkg/distributor/ha_tracker.go:144). So we need to either preserve that behaviour (what this PR is trying to do), or modify clients to restart if it returns early.
Get
and CAS
are short operations, and switching midway isn't necessary there.
pkg/ring/kv/multi.go
Outdated
} | ||
|
||
// watchChanges performs mirroring -- it watches changes in the primary client, and puts them into secondary. | ||
func (m *MultiClient) watchChanges(prefix 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.
Alternatively, what about writing to both clients but the reads go off only primary? Shouldn't that achieve the same?
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 think it would achieve the same, at the cost of making CAS slower for clients. But that is probably just fine. (There is question of what to do when CAS to primary succeeds, but CAS to secondary fails, but the only reasonable thing we can do is to ignore it... we should not call f
again)
I've changed functionality to follow this logic: writes via CAS function are now mirrored (if enabled) to secondary store, Get always goes to primary. WatchKey and WatchPrefix are left as they were (explanation why). I've removed mirroring goroutine and rate limiting, and added metrics. I've also introduced new config value, MirrorTimeout, used when forwarding write to secondary store. (I've observed very long delays when doing Consul -> Etcd mirroring and Etcd was down -- over minute, and ingester got to Unhealthy state.) |
Tons of changes have been made after and this requires another look.
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.
LGTM
The KV code was much clearer than the last time I reviewed this PR. The logic around the KV store looks good and has good associated metrics/logs.
I want to note I would prefer if the OverridesManager and associated code lived in it's own package. We don't have a strong style guide around these types of things as a project. However, since a significant part of Cortex is going to be consumed by downstream projects I think it is important to maintain well named, clearly defined packages throughout the code base.
Thanks @jtlisi for your review. "much cleaner" -- I think I mostly removed code from KV code since your last review. It's now not doing the "mirroring" as a background task, but updates values as part of CAS. re OverridesManager: I've now moved it and renamed to runtime_config.Manager. Originally I didn't want to do big changes to it, as this PR is primarily about something else, but I also think this is a good change. |
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.
Good job @pstibrany! I checked out the change set again - to catch up with last commits - and left few comments.
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.
Thanks @pstibrany for addressing my feedback. LGTM (once tests will be fixed)!
I've added two small changes:
|
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.
👍
Updated CHANGELOG.md and documented runtime config section and updated format of overrides/runtime config yaml file. |
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.
Thanks for all this @pstibrany! LGTM!
pkg/util/runtimeconfig/manager.go
Outdated
} | ||
go mgr.loop() | ||
} else { | ||
level.Info(util.Logger).Log("msg", "config disabled") |
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.
rutime config disabled.
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 "runtime config file not specified, reload disabled"
to explain "why" as well.
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.
or rather runtime config disabled: file not specified
This client is configured with multiple stores, one of them is designated as primary. All client operations are forwarded to the primary store. MultiClient also does "mirroring" of values from primary to secondary store. MultiClient can listen on changes in runtime configuration (via overrides mechanism), and switch primary store and enable/disable mirroring. Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Mirroring goroutine was removed, and replaced by forwarding writes done via CAS function to secondary client. Rate limits config was removed, but there is now timeout for secondary write, to avoid blocking CAS function for too long, if secondary write is slow (eg. etcd being down can cause very long writes). Only WatchKey and WatchPrefix functions now react on change of primary client. Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Without watch-and-mirror functionality, there is no need to check if value is already present in the secondary store. Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
…mes, removed forgotten log. Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Instead of spawning new goroutine for each config update, we now use channels to communicate config updates. Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Signed-off-by: Peter Štibraný <[email protected]>
Addressed other review feedback. Signed-off-by: Peter Štibraný <[email protected]>
Since we support multiple KV Store backends now (Consul, Etcd, memberlist-gossiping soon), we want to be able to migrate from one backend to a different one gracefully, without shutting down all components at once. (Issue #1525) This PR implements one way of doing that.
It adds yet another KV Store backend, called multiClient. This client is configured with several backends, one of them is designated as primary. Primary one is used for all client operations like Get, CAS, WatchKey, and WatchPrefix.
At the same time, multiClient reads all changes done in primary backend and copies them to secondary backend. (That is done using WatchPrefix, with "" prefix [update: now configurable])Updates via CAS are also written to secondary store, if mirroring is enabled.It is possible to switch the primary backend in the runtime by using existing overrides mechanism. This mechanism has been extended and refactored a bit. [update: it's now also possible to enable/disable mirroring in runtime via overrides]
Cortex has new "runtimeConfigValues" struct that is reloaded using OverridesManager (now
runtime_config.Manager
). runtimeConfigValues currently contains limit overrides and multi-client primary store.runtime_config.Manager
now lives in its ownruntime_config
package, and its job is to reload runtime YAML file (overrides.yaml
), and provide new value to clients and listeners.Components interested in using runtimeConfigValues setup themselves in main Cortex setup code (cortex.go, modules.go). At the moment, interested components are Overrides (using limits) and Ring (when using multi-client, for listening on primary store changes).
After all this introduction, how is this supposed to work? In scenario, where one wants to migrate from Consul to Etcd, here are the steps for migration:
All this can be done without any downtime.
Cost of this is higher load on consul/etcd when components do mirroring, but that can be allieviated by using rate limits on consul client. [update: mirroring itself now has rate limit][Watch+mirroring functionality has been replaced with simple update of values in secondary stores when performing CAS operation]