-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve performance for many new fields introduction in mapping
When we have many new fields keep being introduced, the immutable open map we used becomes more and more expensive because of its clone characteristics, and we use it in several places. The usage semantics of it allows us to also use a CHM if we want to, but it would be nice to still maintain the concurrency aspects of volatile immutable map when the number of fields is sane. Introduce a new map like data structure, that can switch internally to CHM when a certain threshold is met. Also add a benchmark class to exploit the many new field mappings use case, which shows significant gains by using this change, to a level where mapping introduction is no longer a significant bottleneck. closes #6707
- Loading branch information
Showing
14 changed files
with
595 additions
and
109 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
177 changes: 177 additions & 0 deletions
177
src/main/java/org/elasticsearch/common/collect/UpdateInPlaceMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
/* | ||
* Licensed to Elasticsearch under one or more contributor | ||
* license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright | ||
* ownership. Elasticsearch 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.elasticsearch.common.collect; | ||
|
||
import com.carrotsearch.hppc.cursors.ObjectObjectCursor; | ||
import com.google.common.collect.Iterables; | ||
import org.elasticsearch.ElasticsearchIllegalStateException; | ||
import org.elasticsearch.common.lease.Releasable; | ||
import org.elasticsearch.common.util.concurrent.ConcurrentCollections; | ||
|
||
import java.util.Iterator; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentMap; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
||
/** | ||
* A map that exposes only read only methods, and can be mutated using a {@link #mutator()}. It | ||
* allows for a cutoff switch between {@link ImmutableOpenMap} and {@link ConcurrentMap}, based on size, since as | ||
* the size grows bigger, cloning the immutable map cost gets bigger and bigger, and might as well move to CHM. | ||
* <p/> | ||
* Note, its important to understand the semantics of the class and its mutator, its not an update in place, when | ||
* CHM is used, changes to the mutator will be reflected in the existing maps!. This class should be used as if | ||
* its a regular, mutable concurrent map, mutation can affect the existing map. | ||
* <p/> | ||
* This class only allows for a single concurrent mutator to execute at the same time. | ||
*/ | ||
public final class UpdateInPlaceMap<K, V> { | ||
|
||
final int switchSize; | ||
final AtomicBoolean mutating = new AtomicBoolean(); | ||
volatile ImmutableOpenMap<K, V> immutableMap; | ||
volatile ConcurrentMap<K, V> concurrentMap; | ||
|
||
UpdateInPlaceMap(int switchSize) { | ||
this.switchSize = switchSize; | ||
if (switchSize == 0) { | ||
this.concurrentMap = ConcurrentCollections.newConcurrentMap(); | ||
this.immutableMap = null; | ||
} else { | ||
this.concurrentMap = null; | ||
this.immutableMap = ImmutableOpenMap.of(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns if the map is empty or not. | ||
*/ | ||
public boolean isEmpty() { | ||
final ImmutableOpenMap<K, V> immutableMap = this.immutableMap; | ||
final ConcurrentMap<K, V> concurrentMap = this.concurrentMap; | ||
return immutableMap != null ? immutableMap.isEmpty() : concurrentMap.isEmpty(); | ||
} | ||
|
||
/** | ||
* Returns the value matching a key, or null if not matched. | ||
*/ | ||
public V get(K key) { | ||
final ImmutableOpenMap<K, V> immutableMap = this.immutableMap; | ||
final ConcurrentMap<K, V> concurrentMap = this.concurrentMap; | ||
return immutableMap != null ? immutableMap.get(key) : concurrentMap.get(key); | ||
} | ||
|
||
/** | ||
* Returns all the values in the map, on going mutator changes might or might not be reflected | ||
* in the values. | ||
*/ | ||
public Iterable<V> values() { | ||
return new Iterable<V>() { | ||
@Override | ||
public Iterator<V> iterator() { | ||
final ImmutableOpenMap<K, V> immutableMap = UpdateInPlaceMap.this.immutableMap; | ||
final ConcurrentMap<K, V> concurrentMap = UpdateInPlaceMap.this.concurrentMap; | ||
if (immutableMap != null) { | ||
return immutableMap.valuesIt(); | ||
} else { | ||
return Iterables.unmodifiableIterable(concurrentMap.values()).iterator(); | ||
} | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Opens a mutator allowing to mutate this map. Note, only one mutator is allowed to execute. | ||
*/ | ||
public Mutator mutator() { | ||
if (!mutating.compareAndSet(false, true)) { | ||
throw new ElasticsearchIllegalStateException("map is already mutating, can't have another mutator on it"); | ||
} | ||
return new Mutator(); | ||
} | ||
|
||
public static <K, V> UpdateInPlaceMap<K, V> of(int switchSize) { | ||
return new UpdateInPlaceMap<>(switchSize); | ||
} | ||
|
||
public final class Mutator implements Releasable { | ||
|
||
private ImmutableOpenMap.Builder<K, V> immutableBuilder; | ||
|
||
private Mutator() { | ||
if (immutableMap != null) { | ||
immutableBuilder = ImmutableOpenMap.builder(immutableMap); | ||
} else { | ||
immutableBuilder = null; | ||
} | ||
} | ||
|
||
public V get(K key) { | ||
if (immutableBuilder != null) { | ||
return immutableBuilder.get(key); | ||
} | ||
return concurrentMap.get(key); | ||
} | ||
|
||
public V put(K key, V value) { | ||
if (immutableBuilder != null) { | ||
V v = immutableBuilder.put(key, value); | ||
switchIfNeeded(); | ||
return v; | ||
} else { | ||
return concurrentMap.put(key, value); | ||
} | ||
} | ||
|
||
public Mutator putAll(Map<K, V> map) { | ||
for (Map.Entry<K, V> entry : map.entrySet()) { | ||
put(entry.getKey(), entry.getValue()); | ||
} | ||
return this; | ||
} | ||
|
||
public V remove(K key) { | ||
return immutableBuilder != null ? immutableBuilder.remove(key) : concurrentMap.remove(key); | ||
} | ||
|
||
private void switchIfNeeded() { | ||
if (concurrentMap != null) { | ||
assert immutableBuilder == null; | ||
return; | ||
} | ||
if (immutableBuilder.size() <= switchSize) { | ||
return; | ||
} | ||
concurrentMap = ConcurrentCollections.newConcurrentMap(); | ||
for (ObjectObjectCursor<K, V> cursor : immutableBuilder) { | ||
concurrentMap.put(cursor.key, cursor.value); | ||
} | ||
immutableBuilder = null; | ||
immutableMap = null; | ||
} | ||
|
||
public void close() { | ||
if (immutableBuilder != null) { | ||
immutableMap = immutableBuilder.build(); | ||
} | ||
assert (immutableBuilder != null && concurrentMap == null) || (immutableBuilder == null && concurrentMap != null); | ||
mutating.set(false); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.