Repository navigation
Extend GCS performance metrics and unify the GCS metric namespace - #40142
Conversation
|
r: @Abacn |
|
Stopping reviewer notifications for this pull request: review requested by someone other than the bot, ceding control. If you'd like to restart, comment |
| // -Dconfiguration=large or -Dconfiguration='{"preset":"f100_s16","totalBytes":"10GB"}'. | ||
| tasks.register( | ||
| "ParquetPerformanceTest", IoPerformanceTestUtilities.IoPerformanceTest, project, 'google-cloud-platform', 'ParquetIOLT', | ||
| ['configuration':'local','project':'apache-beam-testing', 'artifactBucket':'io-performance-temp'] |
There was a problem hiding this comment.
Right now, we only trigger the minimum configuration ("local") in our github postcommit workflow. There will be a separate PR to add a new workflow to sweep all the configurations weekly.
There was a problem hiding this comment.
There are workflows running large tests:
https://github.com/apache/beam/blob/master/.github/workflows/beam_StressTests_Java_BigQueryIO.yml
https://github.com/apache/beam/blob/master/.github/workflows/beam_StressTests_Java_KafkaIO.yml
and others.
Large test should have their own workflows as it runs for long
There was a problem hiding this comment.
Agreed! I will have a follow-up PR to set up those tests.
| * different thread than the one processing the bundle, concurrently with {@link | ||
| * #processElement}. | ||
| */ | ||
| private synchronized List<BlockMetaData> getRowGroups(ReadableFile file) throws Exception { |
There was a problem hiding this comment.
Just curious, is this an optimization and do we know how much performance gain from it?
There was a problem hiding this comment.
Yes, it is an optimization.
Previously we retrieve the row group information for every getSize() and newTracker() on every dynamic split attempt or progress probing. In one of the tests, this fix reduced the number of metadata queries by ~80%.
| // -Dconfiguration=large or -Dconfiguration='{"preset":"f100_s16","totalBytes":"10GB"}'. | ||
| tasks.register( | ||
| "ParquetPerformanceTest", IoPerformanceTestUtilities.IoPerformanceTest, project, 'google-cloud-platform', 'ParquetIOLT', | ||
| ['configuration':'local','project':'apache-beam-testing', 'artifactBucket':'io-performance-temp'] |
There was a problem hiding this comment.
There are workflows running large tests:
https://github.com/apache/beam/blob/master/.github/workflows/beam_StressTests_Java_BigQueryIO.yml
https://github.com/apache/beam/blob/master/.github/workflows/beam_StressTests_Java_KafkaIO.yml
and others.
Large test should have their own workflows as it runs for long
| * {@code "1024"} into a number of bytes. The units are binary, i.e. {@code 1KB == 1024}, and both | ||
| * the short and the long spelling are accepted ({@code M}, {@code MB}, {@code MiB}). | ||
| */ | ||
| static long parseSizeToBytes(String sizeStr) { |
There was a problem hiding this comment.
It's testing code and we don't need complicated parsers (regex) and even handling alias switch (unit) { ... }. Can we just use bytes or simple suffix?
There was a problem hiding this comment.
Reasonable. Already simplified accordingly.
| * @param jobId the id of the job to query | ||
| * @return a map of GCS metric name to value, empty if no GCS metric was reported | ||
| */ | ||
| protected Map<String, Double> getGcsMetrics(String jobId) { |
There was a problem hiding this comment.
getGcsMetrics and printGcsMetrics nearly identical but long codes. Consider consolidate them.
There was a problem hiding this comment.
Refactored metric handling and reporting in MetricReport class to keep GcsIOLoadTestBase light-weight and GCS-oriented. We also make such metric handling code reusable.
| } | ||
|
|
||
| /** Formats a number of bytes as e.g. {@code 9.31 GB}. */ | ||
| private static String formatBytes(long bytes) { |
There was a problem hiding this comment.
This duplicates the same helper in GcsIOLoadTestBase
There was a problem hiding this comment.
Moved it to a new utility class.
| * Utilities to read and print byte counts, so that a load test can take a size such as {@code | ||
| * "40G"} on its command line and report it back in a human readable form. | ||
| */ | ||
| public final class ByteSizeUtils { |
There was a problem hiding this comment.
If previous comments are addressed I'd expect the Pr would have a much smaller footprint. Yet it keep growing into larger changes. For example now we have a whole "ByteSizeUtil" and need to add test to it. This is opposite to my comments about simplify things
There was a problem hiding this comment.
The parsing logic is simplified as we don't have to use regex to support different annotations. And to facilitate future usage, I think extracting the functions to a utility class will improve its reusability. Even though we have more code, it is covered by necessary tests.
There was a problem hiding this comment.
This is just an example one specific point gets further complicated rather than simplified after iteration
For this specific point, a few lines of code is sufficient like
private static final ImmutableMap<String, Long> UNIT_MAP =
ImmutableMap.of(
"k", 1024L, "m", 1024L * 1024, "g", 1024L * 1024 * 1024, "t", 1024L * 1024 * 1024 * 1024);
private static long getBytesFromOption(String raw) {
int len = raw.length();
if (len >= 2 && UNIT_MAP.containsKey(raw.substring(len - 1).toLowerCase())) {
return Long.valueOf(raw.substring(0, len - 1))
* UNIT_MAP.get(raw.substring(len - 1).toLowerCase());
}
return Long.valueOf(raw);
}
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #40142 +/- ##
============================================
- Coverage 59.59% 57.25% -2.35%
+ Complexity 17598 3695 -13903
============================================
Files 3050 1201 -1849
Lines 293086 194280 -98806
Branches 13816 3890 -9926
============================================
- Hits 174679 111238 -63441
+ Misses 111287 79437 -31850
+ Partials 7120 3605 -3515
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Since this PR is getting bigger, let me split it into two or three for easier review. |
Abacn
left a comment
There was a problem hiding this comment.
If agent is used, it shows strong tendency of
-
unrelated change gets touched whenever agent thought it can "improve" things when loitering code paths
-
over-engineering trivial logics
89a3f1d to
fb7c3c9
Compare
|
I trimmed the original and only keep the changes of GCS metrics here. PTAL. @Abacn These new metrics are pre-requisites to the follow-up GCS load tests. |
| * <ul> | ||
| * <li>{@code request_count} counts every attempt, retries included, because the request | ||
| * interceptor runs once per attempt. | ||
| * <li>Every attempt ends up in exactly one of {@code status_2xx}, {@code status_3xx}, {@code |
There was a problem hiding this comment.
The Javadoc states that status_2xx + status_3xx + status_4xx + status_5xx + status_other + request_no_response == request_count. This isn't true whenever there is retry inside HttpResponseInterceptor:
However, in google-http-client's HttpRequest.java.execute(), HttpExecuteInterceptor.intercept() is called inside the do { ... } while (retryRequest) loop (once per attempt), whereas HttpResponseInterceptor.interceptResponse() is called outside/after the retry loop (only once for the final response).
We can simply drop inaccurate javadocs
There was a problem hiding this comment.
Fixed the javadocs.
| if (container != null) { | ||
| HttpRequestInitializer scopedInitializer = | ||
| Transport.withMetricsContainer(this.httpRequestInitializer, container, false); | ||
| gcpStorage = |
There was a problem hiding this comment.
This creates an unclosed GoogleCloudStorageImpl instance on every GcsUtilV1.open() when metrics are enabled, may have performance implications
One of the original suspected reason for OOM was gRPC cache size grew. It wasn't the case, but with this change it could surface.
There was a problem hiding this comment.
Done. Cached the storage instance by metric container and the resource will be removed when metric container is gc'ed.
There was a problem hiding this comment.
I'm not sure if we need cache here. metrics container isn't owned by either GcsUtil or Impl. In fact, it's a runtime static object
container = MetricsEnvironment.getCurrentContainer();
Can we simply pass a flag down to Transport, then it get metrics container in place? I suspect it's passing container is to acommodate unit tests, which isn't worth it at the cost of holding additional state or excessive client creations.
There was a problem hiding this comment.
MetricsEnvironment.getCurrentContainer() is backed by a ThreadLocal, which the runner only sets on the DoFn thread for the active step.
Calling MetricsEnvironment.getCurrentContainer() inside Transport when HTTP requests execute doesn't work because:
- gcsio executes HTTP requests on background thread pools, where Beam's
ThreadLocalis not propagated andgetCurrentContainer()returnsnull. By capturing container inopen()/create()on the DoFn thread, we can make sure background threads can increment the counters directly without relying on theThreadLocaland avoid metric loss. GoogleCloudStorageImplfixes itsHttpRequestInitializerat construction time. Since each step has its ownMetricsContainer, cachingGoogleCloudStorageperMetricsContainerensures we create at most one client per step rather than one per file.
Summary
Today
--gcsPerformanceMetricsonly emits four counters, covering copy and rename. That is not enough to capture the possible performance change between package versions.In this PR, we makes the following improvements:
gcs_op_*for filesystem operations,gcs_http_*for transport)._count/_msecpair.Adds transport-level counters (request counts by shape, response counts by HTTP status class, and wire bytes) by attaching interceptors to the GCS
HttpRequestInitializer, bound to theMetricsContainerof the step that opened the channel.--gcsPerformanceMetricsflag, which defaults tofalse, so the default behavior of a pipeline is unchanged apart from the metric renames.Metric changes
Operation level (
gcs_op_*, namespaceGcs)gcs_op_copy_count/_msecnum_copies/copy_time_msecgcs_op_rename_count/_msecnum_renames/rename_time_msecgcs_op_delete_count/_msecgcs_op_match_glob_count/_msecgcs_op_match_nonglob_count/_msecgcs_op_open_count/_msecgcs_op_create_count/_msecgcs_op_match_glob_pagesTransport level (
gcs_http_*, namespaceGcs)gcs_http_*_request_countgcs_http_*_request_count_rangedRangeheadergcs_http_*_request_count_unboundedRangeheader (includes metadata GETs)gcs_http_*_request_count_othergcs_http_*_status_2xxgcs_http_*_status_3xxgcs_http_*_status_4xxgcs_http_*_status_5xxgcs_http_*_status_othergcs_http_*_request_no_responsegcs_http_read_wire_bytes_receivedgcs_http_write_wire_bytes_sent