|
22 | 22 | import org.elasticsearch.common.unit.ByteSizeUnit;
|
23 | 23 | import org.elasticsearch.common.unit.ByteSizeValue;
|
24 | 24 | import org.elasticsearch.common.unit.MemorySizeValue;
|
| 25 | +import org.elasticsearch.common.util.StringLiteralDeduplicator; |
25 | 26 | import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
26 | 27 | import org.elasticsearch.common.xcontent.XContentParserUtils;
|
27 | 28 | import org.elasticsearch.core.Booleans;
|
@@ -99,7 +100,27 @@ private static Settings of(Map<String, Object> settings, SecureSettings secureSe
|
99 | 100 |
|
100 | 101 | private Settings(Map<String, Object> settings, SecureSettings secureSettings) {
|
101 | 102 | // we use a sorted map for consistent serialization when using getAsMap()
|
102 |
| - this.settings = Collections.unmodifiableNavigableMap(new TreeMap<>(settings)); |
| 103 | + final TreeMap<String, Object> tree = new TreeMap<>(); |
| 104 | + for (Map.Entry<String, Object> settingEntry : settings.entrySet()) { |
| 105 | + final Object value = settingEntry.getValue(); |
| 106 | + final Object internedValue; |
| 107 | + if (value instanceof String) { |
| 108 | + internedValue = internKeyOrValue((String) value); |
| 109 | + } else if (value instanceof List) { |
| 110 | + @SuppressWarnings("unchecked") |
| 111 | + List<String> valueList = (List<String>) value; |
| 112 | + final int listSize = valueList.size(); |
| 113 | + final String[] internedArr = new String[listSize]; |
| 114 | + for (int i = 0; i < valueList.size(); i++) { |
| 115 | + internedArr[i] = internKeyOrValue(valueList.get(i)); |
| 116 | + } |
| 117 | + internedValue = List.of(internedArr); |
| 118 | + } else { |
| 119 | + internedValue = value; |
| 120 | + } |
| 121 | + tree.put(internKeyOrValue(settingEntry.getKey()), internedValue); |
| 122 | + } |
| 123 | + this.settings = Collections.unmodifiableNavigableMap(tree); |
103 | 124 | this.secureSettings = secureSettings;
|
104 | 125 | }
|
105 | 126 |
|
@@ -418,7 +439,7 @@ public List<String> getAsList(String key, List<String> defaultValue, Boolean com
|
418 | 439 | if (valueFromPrefix instanceof List) {
|
419 | 440 | @SuppressWarnings("unchecked")
|
420 | 441 | final List<String> valuesAsList = (List<String>) valueFromPrefix;
|
421 |
| - return Collections.unmodifiableList(valuesAsList); |
| 442 | + return valuesAsList; |
422 | 443 | } else if (commaDelimited) {
|
423 | 444 | String[] strings = Strings.splitStringByCommaToArray(get(key));
|
424 | 445 | if (strings.length > 0) {
|
@@ -1189,11 +1210,19 @@ public boolean shouldRemoveMissingPlaceholder(String placeholderName) {
|
1189 | 1210 | }
|
1190 | 1211 | if (entry.getValue() instanceof List) {
|
1191 | 1212 | @SuppressWarnings("unchecked")
|
1192 |
| - final ListIterator<String> li = ((List<String>) entry.getValue()).listIterator(); |
| 1213 | + final List<String> mutableList = new ArrayList<>((List<String>) entry.getValue()); |
| 1214 | + final ListIterator<String> li = mutableList.listIterator(); |
| 1215 | + boolean changed = false; |
1193 | 1216 | while (li.hasNext()) {
|
1194 | 1217 | final String settingValueRaw = li.next();
|
1195 | 1218 | final String settingValueResolved = propertyPlaceholder.replacePlaceholders(settingValueRaw, placeholderResolver);
|
1196 |
| - li.set(settingValueResolved); |
| 1219 | + if (settingValueResolved.equals(settingValueRaw) == false) { |
| 1220 | + li.set(settingValueResolved); |
| 1221 | + changed = true; |
| 1222 | + } |
| 1223 | + } |
| 1224 | + if (changed) { |
| 1225 | + entry.setValue(List.copyOf(mutableList)); |
1197 | 1226 | }
|
1198 | 1227 | continue;
|
1199 | 1228 | }
|
@@ -1445,4 +1474,18 @@ private static String toString(Object o) {
|
1445 | 1474 | return o == null ? null : o.toString();
|
1446 | 1475 | }
|
1447 | 1476 |
|
| 1477 | + private static final StringLiteralDeduplicator settingLiteralDeduplicator = new StringLiteralDeduplicator(); |
| 1478 | + |
| 1479 | + /** |
| 1480 | + * Interns the given string which should be either a setting key or value or part of a setting value list. This is used to reduce the |
| 1481 | + * memory footprint of similar setting instances like index settings that may contain mostly the same keys and values. Interning these |
| 1482 | + * strings at some runtime cost is considered a reasonable trade-off here since neither setting keys nor values change frequently |
| 1483 | + * while duplicate keys values may consume significant amounts of memory. |
| 1484 | + * |
| 1485 | + * @param s string to intern |
| 1486 | + * @return interned string |
| 1487 | + */ |
| 1488 | + static String internKeyOrValue(String s) { |
| 1489 | + return settingLiteralDeduplicator.deduplicate(s); |
| 1490 | + } |
1448 | 1491 | }
|
0 commit comments