[
https://issues.apache.org/jira/browse/FLINK-40403?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Tim Steinbach updated FLINK-40403:
----------------------------------
Description:
*Problem*
MetricStore.TaskMetricStore#retainSubtasks and #isTransientMetric identify the
per-subtask key prefix (e.g. "0.numRecordsIn") with String#matches(String):
{{// retainSubtasks}}
{{{}return index.matches("\\{}}}{{{}d+") &&
!activeSubtasks.contains(Integer.parseInt(index));{}}}
{{// isTransientMetric}}
{{{}return name.matches("^\\d+\\{}}}{{{}..*") &&
super.isTransientMetric(name);{}}}
String#matches recompiles the regex Pattern on every call. Both methods run
once per metric key, and retainSubtasks is invoked once per vertex on every
MetricFetcher refresh from MetricStore#updateCurrentExecutionAttempts, which is
"public synchronized" — i.e. the O(#metric-keys) work, including a fresh
Pattern.compile per key, executes while the single global MetricStore monitor
is held.
*Impact*
On jobs with a large number of subtasks the per-refresh Pattern compilation
dominates the time the MetricStore monitor is held. Because the JobManager's
REST handler thread pool (rest.server.numThreads, default 4) is shared between
the MetricFetcher and the REST endpoints, the whole pool stalls on that monitor:
* GET /jobs/<jid> — which aggregates per-subtask IO metrics via
MutableIOMetrics#addIOMetrics and takes the same monitor once per subtask;
becomes very slow (observed 38-150 s, sometimes timing out)
* Endpoints that never touch MetricStore (GET /jobs/overview,
/jobs/<jid>/checkpoints) queue behind the same saturated pool. These are on the
Kubernetes Operator's observation path
Observed on a production job with ~2,233 subtasks (main operator chain at
parallelism 900). A thread dump taken while GET /jobs/<jid> was hanging shows
the entire REST pool contending on the MetricStore monitor: one thread RUNNABLE
inside java.util.regex.Pattern$...match / String#matches while holding the
monitor (MetricStore.TaskMetricStore#retainSubtasks -> keySet().removeIf), the
others BLOCKED on it. CPU is otherwise near-idle — this is lock-hold time, not
compute.
*Proposed fix*
Hoist the two regexes to static final Pattern fields on TaskMetricStore and
match via matcher(...).matches(). This is behaviour-preserving —
String#matches(regex) is specified as
Pattern.compile(regex).matcher(s).matches(), and the regex strings are
unchanged — and removes the per-key compilation from inside the lock. Existing
MetricStoreTest coverage (testTaskMetricStoreCleanup,
testSubtaskMetricStoreCleanup, testMalformedNameHandling) exercises both call
sites.
*Reproduction*
Run a job with many subtasks, open the Web UI job page (or poll GET
/jobs/<jid>), and observe REST latency plus a thread dump of the JobManager
showing REST threads BLOCKED on the MetricStore monitor with the holder in
String#matches / Pattern compilation under retainSubtasks.
was:
*Problem*
MetricStore.TaskMetricStore#retainSubtasks and #isTransientMetric identify the
per-subtask key prefix (e.g. "0.numRecordsIn") with String#matches(String):
{{// retainSubtasks}}
{{return index.matches("\\d+") &&
!activeSubtasks.contains(Integer.parseInt(index));}}
{{// isTransientMetric}}
{{return name.matches("^\\d+\\..*") && super.isTransientMetric(name);}}
String#matches recompiles the regex Pattern on every call. Both methods run
once per metric key, and retainSubtasks is invoked once per vertex on every
MetricFetcher refresh from MetricStore#updateCurrentExecutionAttempts, which is
"public synchronized" — i.e. the O(#metric-keys) work, including a fresh
Pattern.compile per key, executes while the single global MetricStore monitor
is held.
*Impact*
On jobs with a large number of subtasks the per-refresh Pattern compilation
dominates the time the MetricStore monitor is held. Because the JobManager's
REST handler thread pool (rest.server.numThreads, default 4) is shared between
the MetricFetcher and the REST endpoints, the whole pool stalls on that monitor:
* GET /jobs/<jid> — which aggregates per-subtask IO metrics via
MutableIOMetrics#addIOMetrics and takes the same monitor once per subtask;
becomes very slow (observed 38-150 s, sometimes timing out)
* Endpoints that never touch MetricStore (GET /jobs/overview,
/jobs/<jid>/checkpoints) queue behind the same saturated pool. These are on the
Kubernetes Operator's observation path
Observed on a production job with ~2,233 subtasks (main operator chain at
parallelism 900). A thread dump taken while GET /jobs/<jid> was hanging shows
the entire REST pool contending on the MetricStore monitor: one thread RUNNABLE
inside java.util.regex.Pattern$...match / String#matches while holding the
monitor (MetricStore.TaskMetricStore#retainSubtasks -> keySet().removeIf), the
others BLOCKED on it. CPU is otherwise near-idle — this is lock-hold time, not
compute.
*Proposed fix*
Hoist the two regexes to static final Pattern fields on TaskMetricStore and
match via matcher(...).matches(). This is behaviour-preserving —
String#matches(regex) is specified as
Pattern.compile(regex).matcher(s).matches(), and the regex strings are
unchanged — and removes the per-key compilation from inside the lock. Existing
MetricStoreTest coverage (testTaskMetricStoreCleanup,
testSubtaskMetricStoreCleanup, testMalformedNameHandling) exercises both call
sites.
*Reproduction*
Run a job with many subtasks, open the Web UI job page (or poll GET
/jobs/<jid>), and observe REST latency plus a thread dump of the JobManager
showing REST threads BLOCKED on the MetricStore monitor with the holder in
String#matches / Pattern compilation under retainSubtasks.
> MetricStore.TaskMetricStore recompiles subtask-index regexes on every metric
> fetch, starving the REST thread pool on high-parallelism jobs
> ------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: FLINK-40403
> URL: https://issues.apache.org/jira/browse/FLINK-40403
> Project: Flink
> Issue Type: Improvement
> Components: Runtime / REST
> Affects Versions: 2.3.0
> Reporter: Tim Steinbach
> Priority: Minor
>
> *Problem*
> MetricStore.TaskMetricStore#retainSubtasks and #isTransientMetric identify
> the per-subtask key prefix (e.g. "0.numRecordsIn") with
> String#matches(String):
> {{// retainSubtasks}}
> {{{}return index.matches("\\{}}}{{{}d+") &&
> !activeSubtasks.contains(Integer.parseInt(index));{}}}
> {{// isTransientMetric}}
> {{{}return name.matches("^\\d+\\{}}}{{{}..*") &&
> super.isTransientMetric(name);{}}}
> String#matches recompiles the regex Pattern on every call. Both methods run
> once per metric key, and retainSubtasks is invoked once per vertex on every
> MetricFetcher refresh from MetricStore#updateCurrentExecutionAttempts, which
> is "public synchronized" — i.e. the O(#metric-keys) work, including a fresh
> Pattern.compile per key, executes while the single global MetricStore monitor
> is held.
> *Impact*
> On jobs with a large number of subtasks the per-refresh Pattern compilation
> dominates the time the MetricStore monitor is held. Because the JobManager's
> REST handler thread pool (rest.server.numThreads, default 4) is shared
> between the MetricFetcher and the REST endpoints, the whole pool stalls on
> that monitor:
> * GET /jobs/<jid> — which aggregates per-subtask IO metrics via
> MutableIOMetrics#addIOMetrics and takes the same monitor once per subtask;
> becomes very slow (observed 38-150 s, sometimes timing out)
> * Endpoints that never touch MetricStore (GET /jobs/overview,
> /jobs/<jid>/checkpoints) queue behind the same saturated pool. These are on
> the Kubernetes Operator's observation path
>
> Observed on a production job with ~2,233 subtasks (main operator chain at
> parallelism 900). A thread dump taken while GET /jobs/<jid> was hanging shows
> the entire REST pool contending on the MetricStore monitor: one thread
> RUNNABLE inside java.util.regex.Pattern$...match / String#matches while
> holding the monitor (MetricStore.TaskMetricStore#retainSubtasks ->
> keySet().removeIf), the others BLOCKED on it. CPU is otherwise near-idle —
> this is lock-hold time, not compute.
> *Proposed fix*
> Hoist the two regexes to static final Pattern fields on TaskMetricStore and
> match via matcher(...).matches(). This is behaviour-preserving —
> String#matches(regex) is specified as
> Pattern.compile(regex).matcher(s).matches(), and the regex strings are
> unchanged — and removes the per-key compilation from inside the lock.
> Existing MetricStoreTest coverage (testTaskMetricStoreCleanup,
> testSubtaskMetricStoreCleanup, testMalformedNameHandling) exercises both call
> sites.
> *Reproduction*
> Run a job with many subtasks, open the Web UI job page (or poll GET
> /jobs/<jid>), and observe REST latency plus a thread dump of the JobManager
> showing REST threads BLOCKED on the MetricStore monitor with the holder in
> String#matches / Pattern compilation under retainSubtasks.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)