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
10 changes: 6 additions & 4 deletions fe/fe-common/src/main/java/org/apache/doris/common/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -3268,10 +3268,12 @@ public class Config extends ConfigBase {
@ConfField(mutable = true)
public static boolean fix_tablet_partition_id_eq_0 = false;

@ConfField(mutable = true, masterOnly = true, description = {
"倒排索引默认存储格式",
"Default storage format of inverted index, the default value is V3."
})
@ConfField(mutable = true, masterOnly = true,
callback = InvertedIndexStorageFormatValidator.RuntimeConfigHandler.class,

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.

[P1] Keep a rejected V1 update from resetting persisted configs

For direct _set_config?inverted_index_storage_format=V1&persist=true, this callback rejects the only value, leaving setConfigs empty, but SetConfigAction still calls persistConfig(setConfigs, true). That reset path truncates fe_custom.conf, so a failed V1 update can erase every unrelated persisted override (and may expose a V1 value from fe.conf at the next startup). Please avoid reset persistence when validation produced errors, or make the request transactional, and add a test that the custom file is unchanged after this rejection.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Same upstream-parity situation: master Config.java declares this field with the identical callback = InvertedIndexStorageFormatValidator.RuntimeConfigHandler.class, so the _set_config persist reset-persistence interaction you describe exists on master after #64522 as well. The only local adaptation in this hunk was keeping branch-4.1 bilingual description array (plus re-indentation for checkstyle); the callback wiring is verbatim.

Fixing the SetConfigAction / persistConfig interaction is a genuine improvement but it belongs on master first so both branches behave the same. Leaving this thread open for maintainer visibility.

description = {
"倒排索引默认存储格式",
"Default storage format of inverted index, the default value is V3."
})
public static String inverted_index_storage_format = "V3";

@ConfField(mutable = true, masterOnly = true, description = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.common;

import java.lang.reflect.Field;

public final class InvertedIndexStorageFormatValidator {
private InvertedIndexStorageFormatValidator() {
}

public static void rejectRuntimeV1(String confVal) throws ConfigException {
String normalizedConfVal = confVal.trim();
if ("V1".equalsIgnoreCase(normalizedConfVal)) {
throw new ConfigException("Inverted index V1 is deprecated and no longer allowed"
+ " for new index creation. Please use inverted index V2.");
}
}

public static void rejectStartupV1(String confVal) throws ConfigException {
String normalizedConfVal = confVal.trim();
if ("V1".equalsIgnoreCase(normalizedConfVal)) {
throw new ConfigException(
"inverted_index_storage_format=V1 is no longer supported. "
+ "Please update fe.conf (or fe_custom.conf): set inverted_index_storage_format=V2.");
}
}

public static class RuntimeConfigHandler extends ConfigBase.DefaultConfHandler {
@Override
public void handle(Field field, String confVal) throws Exception {
rejectRuntimeV1(confVal);
super.handle(field, confVal.trim());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,24 @@ public void testSecurityPathConfigsAreNotRuntimeMutable() {
Assert.assertTrue(e.getMessage().contains("is not mutable"));
}
}

@Test
public void testRejectDeprecatedInvertedIndexV1WithWhitespace() throws Exception {
String originFormat = Config.inverted_index_storage_format;
try {
ConfigBase.setMutableConfig("inverted_index_storage_format", "V2");
ConfigException dynamicException = Assert.assertThrows(ConfigException.class,
() -> ConfigBase.setMutableConfig("inverted_index_storage_format", " V1 "));
Assert.assertTrue(dynamicException.getMessage().contains("Inverted index V1 is deprecated"));
Assert.assertEquals("V2", Config.inverted_index_storage_format);

Config.inverted_index_storage_format = "V2";
ConfigException startupException = Assert.assertThrows(ConfigException.class,
() -> InvertedIndexStorageFormatValidator.rejectStartupV1(" V1 "));
Assert.assertTrue(startupException.getMessage().contains("inverted_index_storage_format=V1"));
Assert.assertEquals("V2", Config.inverted_index_storage_format);
} finally {
Config.inverted_index_storage_format = originFormat;
}
}
}
5 changes: 5 additions & 0 deletions fe/fe-core/src/main/java/org/apache/doris/DorisFE.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.apache.doris.common.Config;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.FeMetaVersion;
import org.apache.doris.common.InvertedIndexStorageFormatValidator;
import org.apache.doris.common.LdapConfig;
import org.apache.doris.common.Log4jConfig;
import org.apache.doris.common.LogUtils;
Expand Down Expand Up @@ -144,6 +145,10 @@ public static void start(String dorisHomeDir, String pidDir, String[] args, Star
// Must init custom config after init config, separately.
// Because the path of custom config file is defined in fe.conf
config.initCustom(Config.custom_config_dir + "/fe_custom.conf");
// inverted_index_storage_format's runtime callback is not invoked while parsing
// fe.conf/fe_custom.conf, so validate the loaded value here after both files are loaded
// and merged, to reject a "V1" left over in the config files at startup.
InvertedIndexStorageFormatValidator.rejectStartupV1(Config.inverted_index_storage_format);

LdapConfig ldapConfig = new LdapConfig();
if (new File(dorisHomeDir + "/conf/ldap.conf").exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2816,6 +2816,13 @@ private boolean processAddIndex(CreateIndexClause alterClause, OlapTable olapTab
AnnIndexPropertiesChecker.checkProperties(indexDef.getProperties());
}

if (indexDef.getIndexType() == IndexType.INVERTED
&& olapTable.getInvertedIndexFileStorageFormat() == TInvertedIndexFileStorageFormat.V1) {

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.

[P1] Treat the legacy DEFAULT sentinel as V1

Tables created before this property existed deserialize its missing FE value as DEFAULT, while their physical tablet schema defaults to V1. This exact-enum check lets ADD INVERTED INDEX through, and ADD ANN also passes its own exact-V1 check. With the default light-index-change path, the format-free task makes BE copy that legacy schema and build against V1 (including ANN, which is unsupported there); with light change disabled, DEFAULT is instead mapped to V3 and silently upgrades the table. Please resolve/reject the effective legacy V1 value before both index types while leaving replay/restore/BUILD of committed indexes unchanged.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks — I checked this against master and the same code is there: SchemaChangeHandler on master carries the identical exact-enum guard (getInvertedIndexFileStorageFormat() == TInvertedIndexFileStorageFormat.V1), and this PR ports it verbatim (the only local adaptation in that hunk was keeping 4.1's indexDef.getColumns(), since getColumnNames() is master-only drift).

So the legacy DEFAULT sentinel behaviour you describe is not introduced by this backport — it exists identically on master after #64522. Changing it only on branch-4.1 would make the two branches diverge, which is exactly what a backport should avoid. Keeping this PR faithful to upstream; the DEFAULT-sentinel handling is worth a separate fix on master that can then be picked to 4.1 in the same shape.

throw new DdlException("Inverted index V1 is deprecated and no longer allowed for new index creation."
+ " Upgrading inverted_index_storage_format via ALTER TABLE is not supported;"
+ " recreate the table with inverted_index_storage_format = V2.");
}

for (String col : indexDef.getColumns()) {
Column column = olapTable.getColumn(col);
if (column != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1283,25 +1283,23 @@ public static TInvertedIndexFileStorageFormat analyzeInvertedIndexFileStorageFor
invertedIndexFileStorageFormat = properties.get(PROPERTIES_INVERTED_INDEX_STORAGE_FORMAT);
properties.remove(PROPERTIES_INVERTED_INDEX_STORAGE_FORMAT);
} else {
if (Config.inverted_index_storage_format.equalsIgnoreCase("V1")) {
return TInvertedIndexFileStorageFormat.V1;
} else if (Config.inverted_index_storage_format.equalsIgnoreCase("V2")) {
if (Config.inverted_index_storage_format.equalsIgnoreCase("V2")) {
return TInvertedIndexFileStorageFormat.V2;
} else {
return TInvertedIndexFileStorageFormat.V3;
}
}

if (invertedIndexFileStorageFormat.equalsIgnoreCase("v1")) {
return TInvertedIndexFileStorageFormat.V1;
throw new AnalysisException(
Comment thread
airborne12 marked this conversation as resolved.

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.

[P1] Preserve synchronized replay of existing V1 tables

Env.getSyncedDdlStmt serializes an existing table's persisted inverted_index_storage_format together with is_being_synced=true, but the destination invokes this analyzer before synchronization-specific handling can run. An existing V1 table therefore now fails CCR/binlog CREATE replay at this throw, and synchronized ADD INDEX hits the same compatibility break in processAddIndex's exact-V1 guard. Please route trusted sync/CCR execution through a compatibility path that preserves explicit V1 (the SQL property alone is user-settable and cannot be trusted), and cover replay of an explicit-V1 CREATE and ADD INDEX.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Verified against master and this is upstream-identical, not backport-specific: master PropertyAnalyzer has the exact same unconditional throw for v1 (lines 1240-1243 there vs 1293-1296 here, byte-identical text), and this PR ports that hunk verbatim.

So CCR/binlog replay of an existing explicit-V1 table hits the same rejection on master today. I am keeping the backport faithful rather than adding a 4.1-only sync/is_being_synced compatibility path, which would make the branches diverge on a user-visible behaviour.

That said, this is the most impactful of the findings for 4.1 users, since CCR replay of pre-existing V1 tables is a real upgrade scenario. Flagging it for the maintainers as a follow-up that should land on master first and then be picked here in the same shape. Leaving this thread open for that reason.

"Inverted index V1 is deprecated and no longer allowed for new index creation."
+ " Please use inverted index V2.");
} else if (invertedIndexFileStorageFormat.equalsIgnoreCase("v2")) {
return TInvertedIndexFileStorageFormat.V2;
} else if (invertedIndexFileStorageFormat.equalsIgnoreCase("v3")) {
return TInvertedIndexFileStorageFormat.V3;
} else if (invertedIndexFileStorageFormat.equalsIgnoreCase("default")) {
if (Config.inverted_index_storage_format.equalsIgnoreCase("V1")) {
return TInvertedIndexFileStorageFormat.V1;
} else if (Config.inverted_index_storage_format.equalsIgnoreCase("V2")) {
if (Config.inverted_index_storage_format.equalsIgnoreCase("V2")) {
return TInvertedIndexFileStorageFormat.V2;
} else {
return TInvertedIndexFileStorageFormat.V3;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,14 +334,17 @@ public void testAnalyzeInvertedIndexFileStorageFormat() throws AnalysisException
TInvertedIndexFileStorageFormat result = PropertyAnalyzer.analyzeInvertedIndexFileStorageFormat(null);
Assertions.assertEquals(TInvertedIndexFileStorageFormat.V3, result);

// Config=V1 with no explicit property: V1 config is ignored, falls through to V3
Config.inverted_index_storage_format = "V1";
result = PropertyAnalyzer.analyzeInvertedIndexFileStorageFormat(new HashMap<>());
Assertions.assertEquals(TInvertedIndexFileStorageFormat.V1, result);
Assertions.assertEquals(TInvertedIndexFileStorageFormat.V3, result);

Map<String, String> propertiesWithV1 = new HashMap<>();
propertiesWithV1.put(PropertyAnalyzer.PROPERTIES_INVERTED_INDEX_STORAGE_FORMAT, "v1");
result = PropertyAnalyzer.analyzeInvertedIndexFileStorageFormat(propertiesWithV1);
Assertions.assertEquals(TInvertedIndexFileStorageFormat.V1, result);
AnalysisException v1Ex = Assertions.assertThrows(AnalysisException.class,
() -> PropertyAnalyzer.analyzeInvertedIndexFileStorageFormat(propertiesWithV1));
Assertions.assertTrue(v1Ex.getMessage().contains(
"Inverted index V1 is deprecated and no longer allowed for new index creation."));

Map<String, String> propertiesWithV2 = new HashMap<>();
propertiesWithV2.put(PropertyAnalyzer.PROPERTIES_INVERTED_INDEX_STORAGE_FORMAT, "v2");
Expand All @@ -353,11 +356,12 @@ public void testAnalyzeInvertedIndexFileStorageFormat() throws AnalysisException
result = PropertyAnalyzer.analyzeInvertedIndexFileStorageFormat(propertiesWithV3);
Assertions.assertEquals(TInvertedIndexFileStorageFormat.V3, result);

// "default" + Config=V1: falls through to V3 (current default when config is not V2)
Config.inverted_index_storage_format = "V1";
Map<String, String> propertiesWithDefaultV1 = new HashMap<>();
propertiesWithDefaultV1.put(PropertyAnalyzer.PROPERTIES_INVERTED_INDEX_STORAGE_FORMAT, "default");
result = PropertyAnalyzer.analyzeInvertedIndexFileStorageFormat(propertiesWithDefaultV1);
Assertions.assertEquals(TInvertedIndexFileStorageFormat.V1, result);
Assertions.assertEquals(TInvertedIndexFileStorageFormat.V3, result);

Config.inverted_index_storage_format = "V2";
Map<String, String> propertiesWithDefaultV2 = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,51 +8,6 @@
-- !sql --
177

-- !sql --
177

-- !sql --
177

-- !sql --
177

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

-- !sql --
2

Expand Down Expand Up @@ -98,18 +53,6 @@
-- !sql --
0

-- !sql --
0

-- !sql --
0

-- !sql --
0

-- !sql --
4

-- !sql --
4

Expand All @@ -119,42 +62,6 @@
-- !sql --
4

-- !sql --
4

-- !sql --
4

-- !sql --
844

-- !sql --
152

-- !sql --
99

-- !sql --
13

-- !sql --
152

-- !sql --
844

-- !sql --
152

-- !sql --
99

-- !sql --
13

-- !sql --
152

-- !sql --
844

Expand Down
Loading
Loading