-
Notifications
You must be signed in to change notification settings - Fork 3.8k
remove unnecessary lock in ForegroundCachePopulator leading to a lot of contention #8116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
251f047
5a9f0e4
eddc981
c159d74
93fa2d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,21 +22,24 @@ | |
| import com.fasterxml.jackson.core.JsonGenerator; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import com.google.common.base.Preconditions; | ||
| import org.apache.commons.lang.mutable.MutableBoolean; | ||
| import org.apache.druid.java.util.common.guava.Sequence; | ||
| import org.apache.druid.java.util.common.guava.SequenceWrapper; | ||
| import org.apache.druid.java.util.common.guava.Sequences; | ||
| import org.apache.druid.java.util.common.logger.Logger; | ||
|
|
||
| import java.io.ByteArrayOutputStream; | ||
| import java.io.IOException; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.function.Function; | ||
|
|
||
| /** | ||
| * {@link CachePopulator} implementation that populates a cache on the same thread that is processing the | ||
| * {@link Sequence}. Used if config "druid.*.cache.numBackgroundThreads" is 0 (the default). | ||
| */ | ||
| public class ForegroundCachePopulator implements CachePopulator | ||
| { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please provide concurrent access documentation for this class (or for In particular, this passage from the PR description:
is not obvious, but it's not reflected anywhere in the code itself.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there isn't so much concurrent access to document anymore since all concurrency primitives have been removed from |
||
| private static final Logger log = new Logger(ForegroundCachePopulator.class); | ||
|
|
||
| private final Object lock = new Object(); | ||
| private final ObjectMapper objectMapper; | ||
| private final CachePopulatorStats cachePopulatorStats; | ||
| private final long maxEntrySize; | ||
|
|
@@ -61,7 +64,7 @@ public <T, CacheType> Sequence<T> wrap( | |
| ) | ||
| { | ||
| final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); | ||
| final AtomicBoolean tooBig = new AtomicBoolean(false); | ||
| final MutableBoolean tooBig = new MutableBoolean(false); | ||
| final JsonGenerator jsonGenerator; | ||
|
|
||
| try { | ||
|
|
@@ -75,21 +78,19 @@ public <T, CacheType> Sequence<T> wrap( | |
| Sequences.map( | ||
| sequence, | ||
| input -> { | ||
| if (!tooBig.get()) { | ||
| synchronized (lock) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This lock removal is the core change happening, and it looks good to me. The lock seems to have been pointless, since the only thing worth protecting is the |
||
| try { | ||
| jsonGenerator.writeObject(cacheFn.apply(input)); | ||
| if (!tooBig.isTrue()) { | ||
| try { | ||
| jsonGenerator.writeObject(cacheFn.apply(input)); | ||
|
|
||
| // Not flushing jsonGenerator before checking this, but should be ok since Jackson buffers are | ||
| // typically just a few KB, and we don't want to waste cycles flushing. | ||
| if (maxEntrySize > 0 && bytes.size() > maxEntrySize) { | ||
| tooBig.set(true); | ||
| } | ||
| } | ||
| catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| // Not flushing jsonGenerator before checking this, but should be ok since Jackson buffers are | ||
| // typically just a few KB, and we don't want to waste cycles flushing. | ||
| if (maxEntrySize > 0 && bytes.size() > maxEntrySize) { | ||
| tooBig.setValue(true); | ||
| } | ||
| } | ||
| catch (IOException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| } | ||
|
|
||
| return input; | ||
|
|
@@ -100,24 +101,22 @@ public <T, CacheType> Sequence<T> wrap( | |
| @Override | ||
| public void after(final boolean isDone, final Throwable thrown) throws Exception | ||
| { | ||
| synchronized (lock) { | ||
| jsonGenerator.close(); | ||
| jsonGenerator.close(); | ||
|
|
||
| if (isDone) { | ||
| // Check tooBig, then check maxEntrySize one more time, after closing/flushing jsonGenerator. | ||
| if (tooBig.get() || (maxEntrySize > 0 && bytes.size() > maxEntrySize)) { | ||
| cachePopulatorStats.incrementOversized(); | ||
| return; | ||
| } | ||
| if (isDone) { | ||
| // Check tooBig, then check maxEntrySize one more time, after closing/flushing jsonGenerator. | ||
| if (tooBig.isTrue() || (maxEntrySize > 0 && bytes.size() > maxEntrySize)) { | ||
| cachePopulatorStats.incrementOversized(); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| cache.put(cacheKey, bytes.toByteArray()); | ||
| cachePopulatorStats.incrementOk(); | ||
| } | ||
| catch (Exception e) { | ||
| log.warn(e, "Unable to write to cache"); | ||
| cachePopulatorStats.incrementError(); | ||
| } | ||
| try { | ||
| cache.put(cacheKey, bytes.toByteArray()); | ||
| cachePopulatorStats.incrementOk(); | ||
| } | ||
| catch (Exception e) { | ||
| log.warn(e, "Unable to write to cache"); | ||
| cachePopulatorStats.incrementError(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This description (or the more general description on the common page) should explain what happens if the serialized form of a query result is bigger than this size. I suppose it is "the result is not recorded in the cache;
XXX/put/oversizedmetric is incremented" (I don't know what should go into XXX, this question should be researched.)Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens is "the result is not recorded in the cache; cache
*/put/oversizedmetrics are incremented."The
*/put/oversizedsyntax would match how they are described in metrics.md, so I think that'd be the right way to write it in user-facing docs. The*could bequery/cache/deltaorquery/cache/total, also mentioned in metrics.md.