Skip to content

Commit 4f1f1a5

Browse files
committed
Security: reduce garbage during index resolution (#30180)
The IndexAndAliasesResolver resolves the indices and aliases for each request and also handles local and remote indices. The current implementation uses the ResolvedIndices class to hold the resolved indices and aliases. While evaluating the indices and aliases against the user's permissions, the final value for ResolvedIndices is constructed. Prior to this change, this was done by creating a ResolvedIndices for the first set of indices and for each additional addition, a new ResolvedIndices object is created and merged with the existing one. With a small number of indices and aliases this does not pose a large problem; however as the number of indices/aliases grows more list allocations and array copies are needed resulting in a large amount of garbage and severely impacted performance. This change introduces a builder for ResolvedIndices that appends to mutable lists until the final value has been constructed, which will ultimately reduce the amount of garbage generated by this code.
1 parent d054aba commit 4f1f1a5

File tree

2 files changed

+76
-55
lines changed

2 files changed

+76
-55
lines changed

docs/CHANGELOG.asciidoc

+13
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,19 @@ Watcher::
794794
* Replaced group settings with affix key settings where filters are needed.
795795
For more information, see https://github.com/elastic/elasticsearch/pull/28338.
796796

797+
== Elasticsearch version 6.3.1
798+
799+
=== New Features
800+
801+
=== Enhancements
802+
803+
=== Bug Fixes
804+
805+
Reduce the number of object allocations made by {security} when resolving the indices and aliases for a request ({pull}30180[#30180])
806+
807+
=== Regressions
808+
809+
=== Known Issues
797810
//[float]
798811
//=== Regressions
799812

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java

+63-55
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@
4040
import java.util.concurrent.CopyOnWriteArraySet;
4141
import java.util.stream.Collectors;
4242

43+
import static org.elasticsearch.xpack.core.security.authz.IndicesAndAliasesResolverField.NO_INDEX_PLACEHOLDER;
44+
4345
public class IndicesAndAliasesResolver {
4446

45-
private static final ResolvedIndices NO_INDEX_PLACEHOLDER_RESOLVED =
46-
ResolvedIndices.local(IndicesAndAliasesResolverField.NO_INDEX_PLACEHOLDER);
4747
//`*,-*` what we replace indices with if we need Elasticsearch to return empty responses without throwing exception
4848
private static final String[] NO_INDICES_ARRAY = new String[] { "*", "-*" };
4949
static final List<String> NO_INDICES_LIST = Arrays.asList(NO_INDICES_ARRAY);
@@ -87,12 +87,14 @@ public IndicesAndAliasesResolver(Settings settings, ClusterService clusterServic
8787

8888
public ResolvedIndices resolve(TransportRequest request, MetaData metaData, AuthorizedIndices authorizedIndices) {
8989
if (request instanceof IndicesAliasesRequest) {
90-
ResolvedIndices indices = ResolvedIndices.empty();
90+
ResolvedIndices.Builder resolvedIndicesBuilder = new ResolvedIndices.Builder();
9191
IndicesAliasesRequest indicesAliasesRequest = (IndicesAliasesRequest) request;
9292
for (IndicesRequest indicesRequest : indicesAliasesRequest.getAliasActions()) {
93-
indices = ResolvedIndices.add(indices, resolveIndicesAndAliases(indicesRequest, metaData, authorizedIndices));
93+
final ResolvedIndices resolved = resolveIndicesAndAliases(indicesRequest, metaData, authorizedIndices);
94+
resolvedIndicesBuilder.addLocal(resolved.getLocal());
95+
resolvedIndicesBuilder.addRemote(resolved.getRemote());
9496
}
95-
return indices;
97+
return resolvedIndicesBuilder.build();
9698
}
9799

98100
// if for some reason we are missing an action... just for safety we'll reject
@@ -102,10 +104,10 @@ public ResolvedIndices resolve(TransportRequest request, MetaData metaData, Auth
102104
return resolveIndicesAndAliases((IndicesRequest) request, metaData, authorizedIndices);
103105
}
104106

105-
ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData metaData,
106-
AuthorizedIndices authorizedIndices) {
107+
108+
ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData metaData, AuthorizedIndices authorizedIndices) {
109+
final ResolvedIndices.Builder resolvedIndicesBuilder = new ResolvedIndices.Builder();
107110
boolean indicesReplacedWithNoIndices = false;
108-
final ResolvedIndices indices;
109111
if (indicesRequest instanceof PutMappingRequest && ((PutMappingRequest) indicesRequest).getConcreteIndex() != null) {
110112
/*
111113
* This is a special case since PutMappingRequests from dynamic mapping updates have a concrete index
@@ -114,7 +116,7 @@ ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData
114116
*/
115117
assert indicesRequest.indices() == null || indicesRequest.indices().length == 0
116118
: "indices are: " + Arrays.toString(indicesRequest.indices()); // Arrays.toString() can handle null values - all good
117-
return ResolvedIndices.local(((PutMappingRequest) indicesRequest).getConcreteIndex().getName());
119+
resolvedIndicesBuilder.addLocal(((PutMappingRequest) indicesRequest).getConcreteIndex().getName());
118120
} else if (indicesRequest instanceof IndicesRequest.Replaceable) {
119121
IndicesRequest.Replaceable replaceable = (IndicesRequest.Replaceable) indicesRequest;
120122
final boolean replaceWildcards = indicesRequest.indicesOptions().expandWildcardsOpen()
@@ -127,13 +129,12 @@ ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData
127129
indicesOptions.expandWildcardsOpen(), indicesOptions.expandWildcardsClosed());
128130
}
129131

130-
ResolvedIndices result = ResolvedIndices.empty();
131132
// check for all and return list of authorized indices
132133
if (IndexNameExpressionResolver.isAllIndices(indicesList(indicesRequest.indices()))) {
133134
if (replaceWildcards) {
134135
for (String authorizedIndex : authorizedIndices.get()) {
135136
if (isIndexVisible(authorizedIndex, indicesOptions, metaData)) {
136-
result = ResolvedIndices.add(result, ResolvedIndices.local(authorizedIndex));
137+
resolvedIndicesBuilder.addLocal(authorizedIndex);
137138
}
138139
}
139140
}
@@ -144,7 +145,7 @@ ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData
144145
if (allowsRemoteIndices(indicesRequest)) {
145146
split = remoteClusterResolver.splitLocalAndRemoteIndexNames(indicesRequest.indices());
146147
} else {
147-
split = ResolvedIndices.local(indicesRequest.indices());
148+
split = new ResolvedIndices(Arrays.asList(indicesRequest.indices()), Collections.emptyList());
148149
}
149150
List<String> replaced = replaceWildcardsWithAuthorizedIndices(split.getLocal(), indicesOptions, metaData,
150151
authorizedIndices.get(), replaceWildcards);
@@ -153,22 +154,23 @@ ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData
153154
//remove all the ones that the current user is not authorized for and ignore them
154155
replaced = replaced.stream().filter(authorizedIndices.get()::contains).collect(Collectors.toList());
155156
}
156-
result = new ResolvedIndices(new ArrayList<>(replaced), split.getRemote());
157+
resolvedIndicesBuilder.addLocal(replaced);
158+
resolvedIndicesBuilder.addRemote(split.getRemote());
157159
}
158-
if (result.isEmpty()) {
160+
161+
if (resolvedIndicesBuilder.isEmpty()) {
159162
if (indicesOptions.allowNoIndices()) {
160163
//this is how we tell es core to return an empty response, we can let the request through being sure
161164
//that the '-*' wildcard expression will be resolved to no indices. We can't let empty indices through
162165
//as that would be resolved to _all by es core.
163166
replaceable.indices(NO_INDICES_ARRAY);
164167
indicesReplacedWithNoIndices = true;
165-
indices = NO_INDEX_PLACEHOLDER_RESOLVED;
168+
resolvedIndicesBuilder.addLocal(NO_INDEX_PLACEHOLDER);
166169
} else {
167170
throw new IndexNotFoundException(Arrays.toString(indicesRequest.indices()));
168171
}
169172
} else {
170-
replaceable.indices(result.toArray());
171-
indices = result;
173+
replaceable.indices(resolvedIndicesBuilder.build().toArray());
172174
}
173175
} else {
174176
if (containsWildcards(indicesRequest)) {
@@ -182,11 +184,9 @@ ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData
182184
//That is fine though because they never contain wildcards, as they get replaced as part of the authorization of their
183185
//corresponding parent request on the coordinating node. Hence wildcards don't need to get replaced nor exploded for
184186
// shard level requests.
185-
List<String> resolvedNames = new ArrayList<>();
186187
for (String name : indicesRequest.indices()) {
187-
resolvedNames.add(nameExpressionResolver.resolveDateMathExpression(name));
188+
resolvedIndicesBuilder.addLocal(nameExpressionResolver.resolveDateMathExpression(name));
188189
}
189-
indices = new ResolvedIndices(resolvedNames, new ArrayList<>());
190190
}
191191

192192
if (indicesRequest instanceof AliasesRequest) {
@@ -207,10 +207,10 @@ ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData
207207
//if we replaced the indices with '-*' we shouldn't be adding the aliases to the list otherwise the request will
208208
//not get authorized. Leave only '-*' and ignore the rest, result will anyway be empty.
209209
} else {
210-
return ResolvedIndices.add(indices, ResolvedIndices.local(aliasesRequest.aliases()));
210+
resolvedIndicesBuilder.addLocal(aliasesRequest.aliases());
211211
}
212212
}
213-
return indices;
213+
return resolvedIndicesBuilder.build();
214214
}
215215

216216
public static boolean allowsRemoteIndices(IndicesRequest request) {
@@ -423,24 +423,8 @@ public static class ResolvedIndices {
423423
private final List<String> remote;
424424

425425
ResolvedIndices(List<String> local, List<String> remote) {
426-
this.local = local;
427-
this.remote = remote;
428-
}
429-
430-
/**
431-
* Constructs a new instance of this class where both the {@link #getLocal() local} and {@link #getRemote() remote} index lists
432-
* are empty.
433-
*/
434-
private static ResolvedIndices empty() {
435-
return new ResolvedIndices(Collections.emptyList(), Collections.emptyList());
436-
}
437-
438-
/**
439-
* Constructs a new instance of this class where both the {@link #getLocal() local} index list is populated with <code>names</code>
440-
* and the {@link #getRemote() remote} index list is empty.
441-
*/
442-
private static ResolvedIndices local(String... names) {
443-
return new ResolvedIndices(Arrays.asList(names), Collections.emptyList());
426+
this.local = Collections.unmodifiableList(local);
427+
this.remote = Collections.unmodifiableList(remote);
444428
}
445429

446430
/**
@@ -449,14 +433,14 @@ private static ResolvedIndices local(String... names) {
449433
* to <code>[ "-a1", "a*" ]</code>. As a consequence, this list <em>may contain duplicates</em>.
450434
*/
451435
public List<String> getLocal() {
452-
return Collections.unmodifiableList(local);
436+
return local;
453437
}
454438

455439
/**
456440
* Returns the collection of index names that have been stored as "remote" indices.
457441
*/
458442
public List<String> getRemote() {
459-
return Collections.unmodifiableList(remote);
443+
return remote;
460444
}
461445

462446
/**
@@ -471,7 +455,7 @@ public boolean isEmpty() {
471455
* {@link IndicesAndAliasesResolverField#NO_INDEX_PLACEHOLDER no-index-placeholder} and nothing else.
472456
*/
473457
public boolean isNoIndicesPlaceholder() {
474-
return remote.isEmpty() && local.size() == 1 && local.contains(IndicesAndAliasesResolverField.NO_INDEX_PLACEHOLDER);
458+
return remote.isEmpty() && local.size() == 1 && local.contains(NO_INDEX_PLACEHOLDER);
475459
}
476460

477461
private String[] toArray() {
@@ -487,19 +471,43 @@ private String[] toArray() {
487471
}
488472

489473
/**
490-
* Returns a new <code>ResolvedIndices</code> contains the {@link #getLocal() local} and {@link #getRemote() remote}
491-
* index lists from <code>b</code> appended to the corresponding lists in <code>a</code>.
474+
* Builder class for ResolvedIndices that allows for the building of a list of indices
475+
* without the need to construct new objects and merging them together
492476
*/
493-
private static ResolvedIndices add(ResolvedIndices a, ResolvedIndices b) {
494-
List<String> local = new ArrayList<>(a.local.size() + b.local.size());
495-
local.addAll(a.local);
496-
local.addAll(b.local);
497-
498-
List<String> remote = new ArrayList<>(a.remote.size() + b.remote.size());
499-
remote.addAll(a.remote);
500-
remote.addAll(b.remote);
501-
return new ResolvedIndices(local, remote);
502-
}
477+
private static class Builder {
478+
479+
private final List<String> local = new ArrayList<>();
480+
private final List<String> remote = new ArrayList<>();
503481

482+
/** add a local index name */
483+
private void addLocal(String index) {
484+
local.add(index);
485+
}
486+
487+
/** adds the array of local index names */
488+
private void addLocal(String[] indices) {
489+
local.addAll(Arrays.asList(indices));
490+
}
491+
492+
/** adds the list of local index names */
493+
private void addLocal(List<String> indices) {
494+
local.addAll(indices);
495+
}
496+
497+
/** adds the list of remote index names */
498+
private void addRemote(List<String> indices) {
499+
remote.addAll(indices);
500+
}
501+
502+
/** @return <code>true</code> if both the local and remote index lists are empty. */
503+
private boolean isEmpty() {
504+
return local.isEmpty() && remote.isEmpty();
505+
}
506+
507+
/** @return a immutable ResolvedIndices instance with the local and remote index lists */
508+
private ResolvedIndices build() {
509+
return new ResolvedIndices(local, remote);
510+
}
511+
}
504512
}
505513
}

0 commit comments

Comments
 (0)