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

Allow empty tiered replicants map for load rules #14432

Merged
merged 20 commits into from
Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion docs/operations/rule-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,58 @@ In the web console you can use the up and down arrows on the right side of the i

Load rules define how Druid assigns segments to [historical process tiers](./mixed-workloads.md#historical-tiering), and how many replicas of a segment exist in each tier.

If you have a single tier, Druid automatically names the tier `_default` and loads all segments onto it. If you define an additional tier, you must define a load rule to specify which segments to load on that tier. Until you define a load rule, your new tier remains empty.
If you have a single tier, Druid automatically names the tier `_default`. If you define an additional tier, you must define a load rule to specify which segments to load on that tier. Until you define a load rule, your new tier remains empty.

`useDefaultTierForNull` is an optional parameter that can be passed to a Load Rule. This parameter determines the default value of `tieredReplicants` only has an effect if the field is not present. The default value of `useDefaultTierForNull` is true.

If this parameter is true, a missing `tieredReplicant` in the load rule is assumed to mean that the segment matching this rule should be loaded on historicals by default. This will initialize `tieredReplicants` with the default tier `"tieredReplicants": { "_default_tier": 2 }`.

If this parameter is false, a missing `tieredReplicants` in the load rule is assumed to mean that the segment matching this rule does not need to be loaded on any historical by default. This will mean that these segments are excluded from most queries as well. This will initialize `tieredReplicants` as an empty map.
adarshsanjeev marked this conversation as resolved.
Show resolved Hide resolved

Example:
adarshsanjeev marked this conversation as resolved.
Show resolved Hide resolved

With `useDefaultTierForNull` as true:

```json
{
"type": "loadForever",
"useDefaultTierForNull": true
}
```

is converted to

```json
{
"type": "loadForever",
"useDefaultTierForNull": true,
"tieredReplicants": {
"_default_tier": 2
}
}
```

With `useDefaultTierForNull` as false:

```json
{
"type": "loadByInterval",
"useDefaultTierForNull": false,
"interval": "2012-01-01/2013-01-01"
}
```

is converted to

```json
{
"type": "loadByInterval",
"useDefaultTierForNull": false,
"interval": "2012-01-01/2013-01-01",
"tieredReplicants": {}
}
```


### Forever load rule

Expand All @@ -130,6 +181,7 @@ The following example places one replica of each segment on a custom tier named
Set the following property:

- `tieredReplicants`: a map of tier names to the number of segment replicas for that tier.
- `useDefaultTierForNull`: This parameter determines the default value of `tieredReplicants` and only has an effect if the field is not present. The default value of `useDefaultTierForNull` is true.

### Period load rule

Expand Down Expand Up @@ -158,6 +210,7 @@ Set the following properties:

You can use this property to load segments with future start and end dates, where "future" is relative to the time when the Coordinator evaluates data against the rule. Defaults to `true`.
- `tieredReplicants`: a map of tier names to the number of segment replicas for that tier.
- `useDefaultTierForNull`: This parameter determines the default value of `tieredReplicants` and only has an effect if the field is not present. The default value of `useDefaultTierForNull` is true.

### Interval load rule

Expand All @@ -180,6 +233,7 @@ Set the following properties:

- `interval`: the load interval specified as an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) range encoded as a string.
- `tieredReplicants`: a map of tier names to the number of segment replicas for that tier.
- `useDefaultTierForNull`: This parameter determines the default value of `tieredReplicants` and only has an effect if the field is not present. The default value of `useDefaultTierForNull` is true.

## Drop rules

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ public Void withHandle(Handle handle) throws Exception
ImmutableMap.of(
DruidServer.DEFAULT_TIER,
DruidServer.DEFAULT_NUM_REPLICANTS
)
),
null
)
);
final String version = DateTimes.nowUtc().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,24 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableMap;
import org.apache.druid.client.DruidServer;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;

import javax.annotation.Nullable;
import java.util.Map;
import java.util.Objects;

/**
*/
public class ForeverLoadRule extends LoadRule
{
private final Map<String, Integer> tieredReplicants;

@JsonCreator
public ForeverLoadRule(
@JsonProperty("tieredReplicants") Map<String, Integer> tieredReplicants
@JsonProperty("tieredReplicants") Map<String, Integer> tieredReplicants,
@JsonProperty("useDefaultTierForNull") @Nullable Boolean useDefaultTierForNull
adarshsanjeev marked this conversation as resolved.
Show resolved Hide resolved
)
{
this.tieredReplicants = tieredReplicants == null
? ImmutableMap.of(DruidServer.DEFAULT_TIER, DruidServer.DEFAULT_NUM_REPLICANTS)
: tieredReplicants;
validateTieredReplicants(this.tieredReplicants);
super(tieredReplicants, useDefaultTierForNull);
}

@Override
Expand All @@ -54,20 +48,6 @@ public String getType()
return "loadForever";
}

@Override
@JsonProperty
public Map<String, Integer> getTieredReplicants()
{
return tieredReplicants;
}

@Override
public int getNumReplicants(String tier)
{
Integer retVal = tieredReplicants.get(tier);
return (retVal == null) ? 0 : retVal;
}

@Override
public boolean appliesTo(DataSegment segment, DateTime referenceTimestamp)
{
Expand All @@ -80,22 +60,4 @@ public boolean appliesTo(Interval interval, DateTime referenceTimestamp)
return true;
}

@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ForeverLoadRule that = (ForeverLoadRule) o;
return Objects.equals(tieredReplicants, that.tieredReplicants);
}

@Override
public int hashCode()
{
return Objects.hash(tieredReplicants);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableMap;
import org.apache.druid.client.DruidServer;
import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.timeline.DataSegment;
import org.joda.time.DateTime;
import org.joda.time.Interval;

import javax.annotation.Nullable;
import java.util.Map;
import java.util.Objects;

/**
*/
Expand All @@ -37,16 +37,15 @@ public class IntervalLoadRule extends LoadRule
private static final Logger log = new Logger(IntervalLoadRule.class);

private final Interval interval;
private final Map<String, Integer> tieredReplicants;

@JsonCreator
public IntervalLoadRule(
@JsonProperty("interval") Interval interval,
@JsonProperty("tieredReplicants") Map<String, Integer> tieredReplicants
@JsonProperty("tieredReplicants") Map<String, Integer> tieredReplicants,
@JsonProperty("useDefaultTierForNull") @Nullable Boolean useDefaultTierForNull
)
{
this.tieredReplicants = tieredReplicants == null ? ImmutableMap.of(DruidServer.DEFAULT_TIER, DruidServer.DEFAULT_NUM_REPLICANTS) : tieredReplicants;
validateTieredReplicants(this.tieredReplicants);
super(tieredReplicants, useDefaultTierForNull);
this.interval = interval;
}

Expand All @@ -57,20 +56,6 @@ public String getType()
return "loadByInterval";
}

@Override
@JsonProperty
public Map<String, Integer> getTieredReplicants()
{
return tieredReplicants;
}

@Override
public int getNumReplicants(String tier)
{
final Integer retVal = tieredReplicants.get(tier);
return retVal == null ? 0 : retVal;
}

@JsonProperty
public Interval getInterval()
{
Expand Down Expand Up @@ -98,24 +83,16 @@ public boolean equals(Object o)
if (o == null || getClass() != o.getClass()) {
return false;
}

IntervalLoadRule that = (IntervalLoadRule) o;

if (interval != null ? !interval.equals(that.interval) : that.interval != null) {
return false;
}
if (tieredReplicants != null ? !tieredReplicants.equals(that.tieredReplicants) : that.tieredReplicants != null) {
if (!super.equals(o)) {
return false;
}

return true;
IntervalLoadRule that = (IntervalLoadRule) o;
return Objects.equals(interval, that.interval);
}

@Override
public int hashCode()
{
int result = interval != null ? interval.hashCode() : 0;
result = 31 * result + (tieredReplicants != null ? tieredReplicants.hashCode() : 0);
return result;
return Objects.hash(super.hashCode(), interval);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,106 @@

package org.apache.druid.server.coordinator.rules;

import org.apache.druid.java.util.common.IAE;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableMap;
import org.apache.druid.client.DruidServer;
import org.apache.druid.common.config.Configs;
import org.apache.druid.error.InvalidInput;
import org.apache.druid.timeline.DataSegment;

import java.util.Map;
import java.util.Objects;

/**
* LoadRules indicate the number of replicants a segment should have in a given tier.
adarshsanjeev marked this conversation as resolved.
Show resolved Hide resolved
*/
public abstract class LoadRule implements Rule
{
private final Map<String, Integer> tieredReplicants;
/**
* Used to determing the default value if tieredReplicants is null in {@link #handleNullTieredReplicants}.
*/
private final boolean useDefaultTierForNull;

protected LoadRule(Map<String, Integer> tieredReplicants, Boolean useDefaultTierForNull)
{
this.useDefaultTierForNull = Configs.valueOrDefault(useDefaultTierForNull, true);
this.tieredReplicants = handleNullTieredReplicants(tieredReplicants, this.useDefaultTierForNull);
validateTieredReplicants(this.tieredReplicants);
}

@JsonProperty
public Map<String, Integer> getTieredReplicants()
{
return tieredReplicants;
}

@JsonProperty
public boolean useDefaultTierForNull()
{
return useDefaultTierForNull;
}

@Override
public void run(DataSegment segment, SegmentActionHandler handler)
{
handler.replicateSegment(segment, getTieredReplicants());
}

protected static void validateTieredReplicants(final Map<String, Integer> tieredReplicants)
/**
* Returns the given {@code tieredReplicants} map unchanged if it is non-null (including empty).
* Returns the following default values if the given map is null.
* <ul>
* <li>If {@code useDefaultTierForNull} is true, returns a singleton map from {@link DruidServer#DEFAULT_TIER} to {@link DruidServer#DEFAULT_NUM_REPLICANTS}.</li>
* <li>If {@code useDefaultTierForNull} is false, returns an empty map. This causes segments to have a replication factor of 0 and not get assigned to any historical.</li>
* </ul>
*/
private static Map<String, Integer> handleNullTieredReplicants(final Map<String, Integer> tieredReplicants, boolean useDefaultTierForNull)
{
if (tieredReplicants.size() == 0) {
throw new IAE("A rule with empty tiered replicants is invalid");
if (useDefaultTierForNull) {
return Configs.valueOrDefault(tieredReplicants, ImmutableMap.of(DruidServer.DEFAULT_TIER, DruidServer.DEFAULT_NUM_REPLICANTS));
} else {
return Configs.valueOrDefault(tieredReplicants, ImmutableMap.of());
}
}

private static void validateTieredReplicants(final Map<String, Integer> tieredReplicants)
{
for (Map.Entry<String, Integer> entry : tieredReplicants.entrySet()) {
if (entry.getValue() == null) {
adarshsanjeev marked this conversation as resolved.
Show resolved Hide resolved
throw new IAE("Replicant value cannot be empty");
throw InvalidInput.exception("Invalid number of replicas for tier [%s]. Value must not be null.", entry.getKey());
}
if (entry.getValue() < 0) {
throw new IAE("Replicant value [%d] is less than 0, which is not allowed", entry.getValue());
throw InvalidInput.exception("Invalid number of replicas for tier [%s]. Value [%d] must be positive.", entry.getKey(), entry.getValue());
}
}
}

public abstract Map<String, Integer> getTieredReplicants();
public int getNumReplicants(String tier)
{
Integer retVal = getTieredReplicants().get(tier);
return (retVal == null) ? 0 : retVal;
}

public abstract int getNumReplicants(String tier);
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LoadRule loadRule = (LoadRule) o;
return useDefaultTierForNull == loadRule.useDefaultTierForNull && Objects.equals(
tieredReplicants,
loadRule.tieredReplicants
);
}

@Override
public int hashCode()
{
return Objects.hash(tieredReplicants, useDefaultTierForNull);
}
}
Loading