Broker Config and Query Blocklist#19011
Conversation
gianm
left a comment
There was a problem hiding this comment.
This is a useful concept, although I think it should be done in such a way that the Coordinator handles communications with the metadata store. Other things that work this way include lookup definitions, users and roles for basic authentication and authorization, centralized schema, etc.
The rationale for why we've done it this way, in these other cases, is three-fold:
- Operationally, it's better if a smaller number of servers is communicating with the metadata store, to avoid overloading it and to make it easier to configure security for the metadata store.
- Syncing dynamic configurations from a central leader (Coordinator) allows updates to propagate more quickly.
- Someday we may want to make an option to use a builtin metadata store. This will be simplest to implement if metadata store operations go through central leaders. Those leaders would naturally become the place where
The way it can work is:
- Move the user-facing POST and GET APIs to the Coordinator.
- Brokers pull configs from the Coordinator on startup.
- When the Coordinator receives an update to the config, it should push it out to an internal POST API on the Brokers.
- Brokers also pull configs from the Coordinator periodically, in case they missed a push.
There are possible race conditions surrounding the push and pull: it's possible a new config is getting pushed out around the same time that the Broker is doing a scheduled pull. To solve these you can include a timestamp in the config object, and have the Brokers reject configs with older timestamps than the current one.
Please also consider what guarantees should exist around availability of the Broker config. Is it "required", i.e., if the Broker can't fetch it on startup then startup will fail? Or is it "best effort", i.e., if the Broker can't fetch it on startup, we proceed with a null config or default config for some time, until the next scheduled pull succeeds?
jtuglu1
left a comment
There was a problem hiding this comment.
Did a first pass – overall looking good!
|
Another thing I forgot to mention is docs + UI, namely: Docs:
UI: |
jtuglu1
left a comment
There was a problem hiding this comment.
Overall, LGTM. Left a few last comments.
jtuglu1
left a comment
There was a problem hiding this comment.
One last thing. Looks like there's some straggler ImmutableMap – I'll merge once those are fixed up.
|
@gianm any final thoughts here? Otherwise, I'm planning to merge this as-is. We can address any subsequent comments in a follow-up. |
There was a problem hiding this comment.
LGTM.
One thing Im wondering is if we should make a new API (on the coordinator) for this blocklist rules and store it separately from the Coordinator Dynamic configs. For example, in Basic Security, you can POST(/druid-ext/basic-security/authentication/db/{authenticatorName}/users/{userName}) on the Coordinator to create a new User. Basically we would have a new set of APIs for GET, POST, etc for blocklist resource. I think this is what @gianm is thinking of in #19011 (review). Although reusing the Coordinator Dynamic configs gives us for 'free' all the existing framework for syncing configs between Broker and Coordinator. Let's wait for @gianm
|
@maytasm oh interesting, just curious, what are the advantages of doing it that way? |
I'm not sure this is needed right now. 1) IMO we don't have a need programmatically (yet) for this that we cannot solve with a get/put of dynamic config API. 2) you can make the same argument for a good portion of the overlord dynamic config as well, yet that also is a single, dynamic config API. I think it keeps things simple for now, and if we want to expand it later, we can. You get all the built-in updating/propagation/auditing support for something that (hopefully) shouldn't be updated that often. However, if we were to extend this to datasource aliasing (which I don't think would fit in here), then I can imagine a higher write tput necessitating a separate API. The litmus test, IMO, for whether to extract something from dynamic config should be: how frequently is it read/written to (by clients), how "large" is the config (does it scale linearly with some sort of cluster resource – datasource, user, node, etc.), and how long do these configs tend to stay set to a specific value (e.g. is it truly "dynamic"). |
I agree on all of @jtuglu1 points above. I don't have strong opinion but just feel a little weird for this config to be in Coordinator Dynamic config as it has nothing to do with Coordination. All the other configs in Coordinator Dynamic config are related to Coordinator Duties like moving segments, loading segments, killing segments, replicating segments, etc. |
gianm
left a comment
There was a problem hiding this comment.
I had been imagining a separate config for this, not making it part of CoordinatorDynamicConfig. Something like BrokerDynamicConfig that is still managed on the Coordinator, but that is also synced to all the Brokers.
It should be straightforward to do, because it should be closely analogous to CoordinatorDynamicConfig. I recommend doing it. It would allow us to keep the Coordinator and Broker dynamic configs cleanly separated, and add more Broker configs over time without overly complicating the single object.
|
I think something from this PR is causing one of the embedded tests to consistently fail in master and on all recent PRs: |
|
Just downloaded the test-report artifacts from GitHub and I see Since the test uses an old Coordinator from 31.0.0, would some of the changes here be compatible with that? I’m wondering if the test might actually be uncovering a legitimate backwards compatibility issue with an old Coordinator + new Brokers. |
Yes, I saw that too – looking. My guess is brokers are calling coordinator config endpoint on boot (which doesn't exist on old coordinators). |
| public ListenableFuture<BrokerDynamicConfig> getBrokerDynamicConfig() | ||
| { | ||
| return FutureUtils.transform( | ||
| client.asyncRequest( | ||
| new RequestBuilder(HttpMethod.GET, "/druid/coordinator/v1/broker/config"), | ||
| new BytesFullResponseHandler() | ||
| ), | ||
| holder -> JacksonUtils.readValue( |
There was a problem hiding this comment.
Looks like this change is causing org.apache.druid.testing.embedded.docker.IngestionBackwardCompatibilityDockerTest to fail consistently (which seems like it would be a legit issue during rolling upgrades since the Coordinators are typically upgraded in the end).
I haven’t looked closely at the surrounding code yet, but either this code or the caller likely needs to account for the new /druid/coordinator/v1/broker/config endpoint not being available on Coordinators during rolling upgrades in a backward compatible manner.
There was a problem hiding this comment.
Yes, that must be the reason.
Probably just catching the not found exception and continuing with the Broker start up should suffice.
There was a problem hiding this comment.
Yeah – I have a test branch that I'll raise in a bit to fix this
There was a problem hiding this comment.
Will summarize the changes there
Fixes #18964
Description
Added a new BrokerDynamicConfig to enable dynamic, runtime configuration of Druid brokers without restarts and a query blocklist for dynamically blocking queries in the situation there is a rogue app/user spamming the cluster without relying on static configs/restarts.
QueryBlocklistRule
Enforced early in QueryLifecycle (after init) and throws DruidException when a query matches a rule i.e. if ALL specified criteria match (AND logic). Null or empty criteria act as wildcards (match everything):
dataSources: Matches if ANY datasource in the query intersects with the rule's datasourcesqueryTypes: Matches if the query type is in the rule's query typescontextMatches: Matches if ALL key-value pairs in the rule match the query context (exact string match)Query blocklist is managed via the existing coordinator config APIs:
Considerations
Creating a separate broker-level dynamic config was considered which can also be used for other future features such as datasource aliasing, routing rules or feature flags. However, Coordinator handles communications with the metadata store and syncs to brokers so that there are fewer servers communicating with the metadata store and future built-in metadata store operations go through central leaders
The query blocklist follows a "best effort" approach:
Rationale: The query blocklist is an operational safety feature, not a security control. Blocking all queries when config is unavailable would be more disruptive than the problem it's trying to solve. Operators can still use coordinator/historical unavailability to enforce hard blocks if needed.
Release note
Added query blocklist feature for dynamically blocking queries without restarts. Operators can block queries by datasource, query type, or query context using the new /druid/coordinator/v1/config/broker API. Rules use AND logic (all criteria must match) and are stored in the metadata database.
Key changed/added classes in this PR
This PR has: