Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.io.File;

public class Config extends ConfigBase {

@ConfField(description = "The path of the user-defined configuration file, used to store fe_custom.conf. "
+ "Configurations in this file will override those in fe.conf")
public static String custom_config_dir = EnvUtils.getDorisHome() + "/conf";
Expand Down Expand Up @@ -541,6 +540,10 @@ public class Config extends ConfigBase {
+ "a load job.")
public static short min_load_replica_num = -1;

@ConfField(mutable = true, masterOnly = true, description = "Minimum number of successfully written replicas "
+ "required in each resource group for a load job.")
public static volatile String[] resource_group_succ_quorum = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

usage example?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how to configure it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

see fix be pr, it has 2 groovy case #66827


@ConfField(description = "The interval of the load job scheduler, in seconds.")
public static int load_checker_interval_second = 5;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public class Backend implements Writable {
// the locationTag is also saved in tagMap, use a single field here to avoid
// creating this everytime we get it.
@SerializedName(value = "locationTag", alternate = {"tag"})
private Tag locationTag = Tag.DEFAULT_BACKEND_TAG;
private volatile Tag locationTag = Tag.DEFAULT_BACKEND_TAG;

@SerializedName("nodeRole")
private Tag nodeRoleTag = Tag.DEFAULT_NODE_ROLE_TAG;
Expand Down Expand Up @@ -1138,4 +1138,3 @@ public static Backend fromThrift(TBackend backend) {
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.apache.doris.catalog.Partition.PartitionState;
import org.apache.doris.catalog.PartitionInfo;
import org.apache.doris.catalog.Replica;
import org.apache.doris.catalog.ReplicaAllocation;
import org.apache.doris.catalog.Table;
import org.apache.doris.catalog.TableIf;
import org.apache.doris.catalog.Tablet;
Expand Down Expand Up @@ -60,7 +61,9 @@
import org.apache.doris.persist.EditLog;
import org.apache.doris.persist.OperationType;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.resource.Tag;
import org.apache.doris.statistics.AnalysisManager;
import org.apache.doris.system.Backend;
import org.apache.doris.task.AgentBatchTask;
import org.apache.doris.task.AgentTaskExecutor;
import org.apache.doris.task.ClearTransactionTask;
Expand Down Expand Up @@ -120,6 +123,9 @@ private enum PublishResult {
// the max number of txn that can be remove per round.
// set it to avoid holding lock too long when removing too many txns per round.
private static final int MAX_REMOVE_TXN_PER_ROUND = 10000;
// ConfigBase replaces the array on every update, so its identity is the cache version.
private static volatile String[] cachedResourceGroupSuccQuorumConfig;
private static volatile Map<String, Integer> cachedResourceGroupSuccQuorum = Map.of();

private final long dbId;

Expand Down Expand Up @@ -494,6 +500,8 @@ private void checkCommitStatus(List<Table> tableList, TransactionState transacti
TabletInvertedIndex tabletInvertedIndex = env.getTabletInvertedIndex();
Map<Long, Set<Long>> tabletToBackends = new HashMap<>();
Map<Long, Table> idToTable = new HashMap<>();
Map<String, Integer> resourceGroupSuccQuorum = getResourceGroupSuccQuorum();
Map<Long, String> backendLocationTags = resourceGroupSuccQuorum.isEmpty() ? Map.of() : new HashMap<>();
for (int i = 0; i < tableList.size(); i++) {
idToTable.put(tableList.get(i).getId(), tableList.get(i));
}
Expand Down Expand Up @@ -610,6 +618,8 @@ private void checkCommitStatus(List<Table> tableList, TransactionState transacti

// (TODO): ignore the alter index if txn id is less than sc sched watermark
int loadRequiredReplicaNum = table.getLoadRequiredReplicaNum(partition.getId());
ReplicaAllocation replicaAllocation = resourceGroupSuccQuorum.isEmpty() ? null
: table.getPartitionInfo().getReplicaAllocation(partition.getId());
for (MaterializedIndex index : allIndices) {
for (Tablet tablet : index.getTablets()) {
tabletSuccReplicas.clear();
Expand All @@ -627,6 +637,12 @@ private void checkCommitStatus(List<Table> tableList, TransactionState transacti
throw new TransactionCommitFailedException("could not find replica for tablet ["
+ tabletId + "], backend [" + tabletBackend + "]");
}
if (!resourceGroupSuccQuorum.isEmpty()) {
backendLocationTags.computeIfAbsent(tabletBackend, backendId -> {
Backend backend = env.getCurrentSystemInfo().getBackend(backendId);
return backend == null ? "" : backend.getLocationTag().value;
Comment thread
deardeng marked this conversation as resolved.
});
}

// if the tablet have no replica's to commit or the tablet is a rolling up tablet,
// the commit backends maybe null
Expand Down Expand Up @@ -670,9 +686,71 @@ private void checkCommitStatus(List<Table> tableList, TransactionState transacti

throw new TabletQuorumFailedException(transactionId, errMsg);
}

for (Entry<String, Integer> entry : resourceGroupSuccQuorum.entrySet()) {
String resourceGroup = entry.getKey();
int replicaNumInResourceGroup = replicaAllocation.getReplicaNumByTag(
Tag.createNotCheck(Tag.TYPE_LOCATION, resourceGroup));
int requiredInResourceGroup = Math.min(entry.getValue(), replicaNumInResourceGroup);
if (requiredInResourceGroup == 0) {
continue;
}

int succInResourceGroup = 0;
for (Replica replica : tabletSuccReplicas) {
if (resourceGroup.equals(
backendLocationTags.get(replica.getBackendIdWithoutException()))) {
succInResourceGroup++;
}
}
if (succInResourceGroup < requiredInResourceGroup) {
String writeDetail = getTabletWriteDetail(tabletSuccReplicas,
tabletWriteFailedReplicas, tabletVersionFailedReplicas);
String errMsg = String.format("Failed to commit txn %s, cause tablet %s resource "
+ "group success quorum failed for %s: required %s successful "
+ "replicas, but only %s succeeded. table %s, partition: [ id=%s, "
+ "commit version %s, visible version %s ], this tablet detail: %s. "
+ "Please try again later.", transactionId, tablet.getId(),
resourceGroup, requiredInResourceGroup, succInResourceGroup, tableId,
partition.getId(), partition.getCommittedVersion(),
partition.getVisibleVersion(), writeDetail);
LOG.info(errMsg);
throw new TabletQuorumFailedException(transactionId, errMsg);
}
}
}
}
}
}
}

private static Map<String, Integer> getResourceGroupSuccQuorum() {
String[] config = Config.resource_group_succ_quorum;
if (config == cachedResourceGroupSuccQuorumConfig) {
return cachedResourceGroupSuccQuorum;
}
synchronized (DatabaseTransactionMgr.class) {
config = Config.resource_group_succ_quorum;
if (config == cachedResourceGroupSuccQuorumConfig) {
return cachedResourceGroupSuccQuorum;
}
Map<String, Integer> parsedConfig = new HashMap<>();
for (String item : config) {
String[] parts = item.split(":", -1);
try {
int configuredMin = Integer.parseInt(parts.length == 2 ? parts[1].trim() : "");
if (parts[0].trim().isEmpty() || configuredMin < 0) {
throw new NumberFormatException();
}
parsedConfig.put(parts[0].trim(), configuredMin);
} catch (NumberFormatException e) {
LOG.warn("Invalid resource_group_succ_quorum item '{}', ignored. Expected format "
+ "resource_group:min_success_replicas with a non-negative integer.", item);
}
}
cachedResourceGroupSuccQuorum = parsedConfig;
cachedResourceGroupSuccQuorumConfig = config;
return parsedConfig;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -98,6 +99,11 @@ public void getMethodTest() {
Assert.assertTrue(backend.isAlive());
}

@Test
public void testLocationTagIsSafelyPublished() throws NoSuchFieldException {
Assert.assertTrue(Modifier.isVolatile(Backend.class.getDeclaredField("locationTag").getModifiers()));
}

@Test
public void diskInfoTest() {
Map<String, TDisk> diskInfos = new HashMap<String, TDisk>();
Expand Down
Loading
Loading