Anshu Singh created NIFI-16480:
----------------------------------
Summary: Copy (copyComponents) maps the whole parent process group
and scans the full flow once per parameter, making copy take minutes on large
flows
Key: NIFI-16480
URL: https://issues.apache.org/jira/browse/NIFI-16480
Project: Apache NiFi
Issue Type: Improvement
Components: Core Framework
Affects Versions: 2.12.0, 2.11.0, 2.10.0, 2.9.0, 2.8.0, 2.7.0, 2.6.0
Environment: 3-node NiFi cluster (NiFi 2.6.0 lab, issue also observed
on NiFi 2.8.0), Java 21, 4 vCPU / 16 GB RAM per node, 8 GB heap, external
ZooKeeper, nifi.cluster.node.read.timeout=20 sec. Flow: ~3,000 process groups,
~15,000 processors, parameter contexts with inheritance (one context bound to
every process group, 1,645 parameters reachable).
Labels: performance cluster locking copy-paste
Reporter: Anshu Singh
h3. Summary
Even after the locking problem in NIFI-16479 is fixed, a single copy (Ctrl+C)
is very expensive on large flows. In our lab (3,005 process groups, ~14.7k
processors, 11 parameter contexts with 1,645 parameters reachable from every
group), copying ONE process group took 50-82 s on a 4 vCPU node. With a lighter
flow the same copy takes 2 s. The cost scales with (parameters reachable from
the copied groups) x (size of the whole flow), not with the size of the
selection.
h3. Where the time goes (StandardNiFiServiceFacade.copyComponents, 2.8.0; same
code in 2.12.0 and main)
# {{mapper.mapProcessGroup(processGroup, ...)}} maps the entire *parent* group
of the selection, including all descendants, and only afterwards filters to the
selected components. Copying one small group from the root canvas therefore
maps the whole flow.
# For each selected group,
{{parameterContexts.putAll(mapper.mapParameterContexts(pg, true, ...))}} is
called separately. Each call builds a new map, so a context shared by all
selected groups (common with inheritance) is mapped again for every selected
group.
# For every parameter, {{NiFiRegistryFlowMapper.mapParameter}} calls
{{{}StandardParameterReferenceManager.getReferencedControllerServiceData(context,
name){}}}. That method:
** walks the whole flow tree: {{{}rootGroup.findAllProcessGroups(group ->
group.referencesParameterContext(context)){}}};
** for every referencing group, calls {{getProperties()}} on every processor
and controller service, which resolves every property descriptor
({{{}AbstractComponentNode.getProperties{}}} -> {{{}getPropertyDescriptor{}}}).
So one copy is roughly O(parameters x process groups + parameters x components
x properties).
Thread dump of the copying node (NiFi 2.8.0) shows the time spent here:
{noformat}
at
org.apache.nifi.components.AbstractConfigurableComponent.getSupportedPropertyDescriptor(AbstractConfigurableComponent.java:80)
at
org.apache.nifi.controller.AbstractComponentNode.getProperties(AbstractComponentNode.java:659)
at
org.apache.nifi.parameter.StandardParameterReferenceManager.getReferencedControllerServiceData(StandardParameterReferenceManager.java:125)
at
org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper.mapParameter(NiFiRegistryFlowMapper.java:939)
at
org.apache.nifi.registry.flow.mapping.NiFiRegistryFlowMapper.mapParameterContexts(NiFiRegistryFlowMapper.java:876)
at
org.apache.nifi.web.StandardNiFiServiceFacade.copyComponents(StandardNiFiServiceFacade.java:5361)
{noformat}
h3. Measurements (lab, NiFi 2.6.0, write-lock hold time of copyComponents)
||Flow||Copy||Time||
|~16.5k processors, 1,600 contexts with ~3 parameters each|1 top-level
group|2.0 s|
|same + a shared context with 100 parameters inherited by all groups|1
top-level group|7.4 s (~49 ms per extra parameter)|
|same|all 13 top-level groups|71.2 s|
|3,005 groups, 11 contexts / 1,645 parameters bound to all groups|1 group|50-82
s|
h3. Possible improvements
* Map only the selected components (and their descendants), not the whole
parent group.
* Map each parameter context once per copy request (share one map across the
selected groups).
* In getReferencedControllerServiceData, compute the set of groups referencing
a context once per context instead of once per parameter, and only inspect
properties whose descriptor identifies a controller service. Alternatively,
cache per-context references for the duration of the mapping.
* The same mapping is used by Download Flow Definition and version control, so
they would benefit as well.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)