-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Cache the creation of parsers within DateProcessor
#92880
Conversation
Pinging @elastic/es-data-management (Team:Data Management) |
Hi @HiDAl, I've created a changelog YAML for you. |
DateProcessor
for the formattersDateProcessor
@elasticsearchmachine generate changelog |
f5c2fb2
to
ad030db
Compare
modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DateProcessor.java
Outdated
Show resolved
Hide resolved
ad030db
to
cbd4b9a
Compare
modules/ingest-common/src/main/java/org/elasticsearch/ingest/common/DateFormat.java
Outdated
Show resolved
Hide resolved
cbd4b9a
to
2537172
Compare
@HiDAl can you grab a heapdump during a run of the Logging benchmark? I'm thinking we've got ~20 date processors running there, each with a cache up to 16 of these things. So that's (up to) 320 of them. I'm guessing they're pretty small, but how small? That is, clearly we're making a time/space tradeoff here. The stats above give us a clear sense of the time we're buying (tons! awesome!), but I'm curious how much space we're giving up to buy that. A separate interesting question. What if we wanted to have zero cache evictions? Could you temporarily add some extra logging and run the Logging benchmark again? I'd be very curious what the normal usage pattern is here. For example, are some date processors just using one or two cached date-parsing-functions (embarrassingly cacheable! perfect!) and others are churning their cache constantly as they try to rotate through 512 different functions? An alternative implementation might be a static cache based around a I'm super in favor of getting a cache like this in, but I want to be very thoughtful about it. |
@joegallo well, I've taken the heap dump, and here's what I found out:
Cache lifecycleOn the other hand, adding some basic logging (89b9e0), we have found out:
../logs/server$ grep -i 'purging' default.log
../logs/server$
../logs/server$ grep -i 'cache size' default.log | cut -d ']' -f 6 | sort -n | uniq -c
7389 Cache size: 1
10660 Cache size: 2
6425 Cache size: 3
173 Cache size: 4
3235 Cache size: 6 About having a common static cacheRunning a new benchmark against having a big and shared cache (commit 86b35) we obtain the following results:
These results are slightly better than having a small & per-processor cache. Also, when checking the lifecycle of the cache (size and evictions), we get the following: ../logs/server$ grep -i 'cache size' default.log | cut -d ']' -f 6 | sort -n | uniq -c
512 Cache size: 1
558 Cache size: 3
1530 Cache size: 4
466 Cache size: 5
../logs/server$ grep -i 'purging' default.log
../logs/server$ As you can see, the cache has only 5 different values along the benchmark, compared with the 22 values of the non-shared cache (read footnote [3]). So, for this specific benchmark and probably for other scenarios where the cache key exists in a broader domain, the static cache is the best option, due to:
[1] This is interesting: why is Grok taking so much space? Let us pull a bit of the thread here later |
2537172
to
c900abc
Compare
the last push introduces the static cache with a default value of 256, configurable through a settings |
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.
+1, but I've been involved enough in the creation of this that I think a second reviewer would be a good thing.
|
||
element = supplier.get(); | ||
if (map.size() >= capacity) { | ||
map.clear(); |
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 is kind of surprising. Whenever the cache is full, we empty it and start over? Is this to avoid the complexity of a concurrent LRU cache? Or is the idea that we'll rarely see more than 256 formats in practice, so this is just for safety and will rarely be hit?
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.
Yes! this decision was made mainly because we want to avoid the complexity of LRU (or any eviction policy) within this code path. The decision to make the size 256 was primarily based on experiments with different benchmarks.
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.
I think it's worth adding a one line comment in the code above the clear to explain its purpose.
Oh, I missed that this version of things doesn't have the |
@joegallo you're right, my bad! just pushed the needed changes using |
cache potentially duped values in the DateProcessor, avoid the creation of disposable objects during the different executions of the processor
tests the Cache class
fcccf77
to
d3eb4cd
Compare
private static final Cache CACHE; | ||
|
||
static { | ||
var cacheSizeStr = System.getProperty(CACHE_CAPACITY_SETTING, "256"); |
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's the reason for doing this as a system property instead of a Settings thing (and related, are we documenting this setting)?
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.
We don't expect users will have to change this value unless they see a big performance effect on their pipelines. In the opposite case, we wanted to provide an easy way to change the value if we receive an SDH (with the obvious memory consumption trade-off). I inspired by this other PR#80902 by @joegallo
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.
+1, git grep 'System.getProperty("es' | grep -vi test/
shows a good number of examples of this as the pattern for non-customer-facing internal knobs that we don't expect to need to twiddle (but that we're making possible to twiddle as a favor to our future selves).
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.
OK, just wanted to make sure it was intentional.
add a javadoc comment
cache potentially duped values in the `DateProcessor`, avoiding the creation of disposable objects during the different executions
Related to #73918 |
This PR introduces a cache mechanism within the
DateProcessor
class. This cache helps to memoize the creation of expensive objects, which are created through calls toDateFormatter.forPattern(...)
method and other expensive methods.We have benchmarked the change, obtaining an increase in performance between 10% and 80% for some of the
logging
benchmarks (~80% for logs-postgresql.log-1.2.0
).Benchmark
elastic/logs
)BASELINE
CACHE OF SIZE 16
CACHE OF SIZE 64