NIFI-9061: Eliminated the nifi.cluster.node.protocol.threads property in favor of nifi.cluster.node.protocol.max.threads property so that we can properly scale out the number of threads used for HTTP request replication. Implementing a caching mechanism for creating the DateTimeFormatter used by TimeAdapter in order to improve performance when parsing timestamps in web requests. Implementing caching logic for caching the number of characters that can rendered without needing an ellipsis for some components in the UI (#5316)

This closes #5316 
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index d63f259..c3b4a74 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -243,7 +243,6 @@
     public static final String CLUSTER_IS_NODE = "nifi.cluster.is.node";
     public static final String CLUSTER_NODE_ADDRESS = "nifi.cluster.node.address";
     public static final String CLUSTER_NODE_PROTOCOL_PORT = "nifi.cluster.node.protocol.port";
-    public static final String CLUSTER_NODE_PROTOCOL_THREADS = "nifi.cluster.node.protocol.threads";
     public static final String CLUSTER_NODE_PROTOCOL_MAX_THREADS = "nifi.cluster.node.protocol.max.threads";
     public static final String CLUSTER_NODE_CONNECTION_TIMEOUT = "nifi.cluster.node.connection.timeout";
     public static final String CLUSTER_NODE_READ_TIMEOUT = "nifi.cluster.node.read.timeout";
@@ -878,15 +877,7 @@
      */
     @Deprecated()
     public int getClusterNodeProtocolThreads() {
-        return getClusterNodeProtocolCorePoolSize();
-    }
-
-    public int getClusterNodeProtocolCorePoolSize() {
-        try {
-            return Integer.parseInt(getProperty(CLUSTER_NODE_PROTOCOL_THREADS));
-        } catch (NumberFormatException nfe) {
-            return DEFAULT_CLUSTER_NODE_PROTOCOL_THREADS;
-        }
+        return getClusterNodeProtocolMaxPoolSize();
     }
 
     public int getClusterNodeProtocolMaxPoolSize() {
diff --git a/nifi-docker/dockerhub/README.md b/nifi-docker/dockerhub/README.md
index 39bef82..f0b495b 100644
--- a/nifi-docker/dockerhub/README.md
+++ b/nifi-docker/dockerhub/README.md
@@ -167,7 +167,6 @@
 | nifi.cluster.is.node                      | NIFI_CLUSTER_IS_NODE                   |
 | nifi.cluster.node.address                 | NIFI_CLUSTER_ADDRESS                   |
 | nifi.cluster.node.protocol.port           | NIFI_CLUSTER_NODE_PROTOCOL_PORT        |
-| nifi.cluster.node.protocol.threads        | NIFI_CLUSTER_NODE_PROTOCOL_THREADS     |
 | nifi.cluster.node.protocol.max.threads    | NIFI_CLUSTER_NODE_PROTOCOL_MAX_THREADS |
 | nifi.zookeeper.connect.string             | NIFI_ZK_CONNECT_STRING                 |
 | nifi.zookeeper.root.node                  | NIFI_ZK_ROOT_NODE                      |
diff --git a/nifi-docker/dockerhub/sh/start.sh b/nifi-docker/dockerhub/sh/start.sh
index c67c6f1..4b37746 100755
--- a/nifi-docker/dockerhub/sh/start.sh
+++ b/nifi-docker/dockerhub/sh/start.sh
@@ -82,7 +82,6 @@
 prop_replace 'nifi.cluster.is.node'                         "${NIFI_CLUSTER_IS_NODE:-false}"
 prop_replace 'nifi.cluster.node.address'                    "${NIFI_CLUSTER_ADDRESS:-$HOSTNAME}"
 prop_replace 'nifi.cluster.node.protocol.port'              "${NIFI_CLUSTER_NODE_PROTOCOL_PORT:-}"
-prop_replace 'nifi.cluster.node.protocol.threads'           "${NIFI_CLUSTER_NODE_PROTOCOL_THREADS:-10}"
 prop_replace 'nifi.cluster.node.protocol.max.threads'       "${NIFI_CLUSTER_NODE_PROTOCOL_MAX_THREADS:-50}"
 prop_replace 'nifi.zookeeper.connect.string'                "${NIFI_ZK_CONNECT_STRING:-}"
 prop_replace 'nifi.zookeeper.root.node'                     "${NIFI_ZK_ROOT_NODE:-/nifi}"
diff --git a/nifi-docker/dockermaven/sh/start.sh b/nifi-docker/dockermaven/sh/start.sh
index 3b251b5..617a39d 100755
--- a/nifi-docker/dockermaven/sh/start.sh
+++ b/nifi-docker/dockermaven/sh/start.sh
@@ -82,7 +82,6 @@
 prop_replace 'nifi.cluster.is.node'                         "${NIFI_CLUSTER_IS_NODE:-false}"
 prop_replace 'nifi.cluster.node.address'                    "${NIFI_CLUSTER_ADDRESS:-$HOSTNAME}"
 prop_replace 'nifi.cluster.node.protocol.port'              "${NIFI_CLUSTER_NODE_PROTOCOL_PORT:-}"
-prop_replace 'nifi.cluster.node.protocol.threads'           "${NIFI_CLUSTER_NODE_PROTOCOL_THREADS:-10}"
 prop_replace 'nifi.cluster.node.protocol.max.threads'       "${NIFI_CLUSTER_NODE_PROTOCOL_MAX_THREADS:-50}"
 prop_replace 'nifi.zookeeper.connect.string'                "${NIFI_ZK_CONNECT_STRING:-}"
 prop_replace 'nifi.zookeeper.root.node'                     "${NIFI_ZK_ROOT_NODE:-/nifi}"
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 1120695..103fe65 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -2135,14 +2135,9 @@
 ** `nifi.cluster.is.node` - Set this to _true_.
 ** `nifi.cluster.node.address` - Set this to the fully qualified hostname of the node. If left blank, it defaults to `localhost`.
 ** `nifi.cluster.node.protocol.port` - Set this to an open port that is higher than 1024 (anything lower requires root).
-** `nifi.cluster.node.protocol.threads` - The number of threads that should be used to communicate with other nodes in the cluster. This property
-defaults to `10`. A thread pool is used for replicating requests to all nodes, and the
-thread pool will never have fewer than this number of threads. It will grow as needed up to the maximum value set by the `nifi.cluster.node.protocol.max.threads`
-property.
 ** `nifi.cluster.node.protocol.max.threads` - The maximum number of threads that should be used to communicate with other nodes in the cluster. This property
-defaults to `50`. A thread pool is used for replication requests to all nodes, and the thread pool will have a "core" size that is configured by the
-`nifi.cluster.node.protocol.threads` property. However, if necessary, the thread pool will increase the number of active threads to the limit
-set by this property.
+defaults to `50`. A thread pool is used for replicating requests to all nodes. The thread pool will increase the number of active threads to the limit
+set by this property. It is typically recommended that this property be set to 4-8 times the number of nodes in your cluster.
 ** `nifi.zookeeper.connect.string` - The Connect String that is needed to connect to Apache ZooKeeper. This is a comma-separated list
 of hostname:port pairs. For example, `localhost:2181,localhost:2182,localhost:2183`. This should contain a list of all ZooKeeper
 instances in the ZooKeeper quorum.
@@ -3878,8 +3873,6 @@
 |`nifi.cluster.is.node`|Set this to `true` if the instance is a node in a cluster. The default value is `false`.
 |`nifi.cluster.node.address`|The fully qualified address of the node. It is blank by default.
 |`nifi.cluster.node.protocol.port`|The node's protocol port. It is blank by default.
-|`nifi.cluster.node.protocol.threads`|The number of threads that should be used to communicate with other nodes
-in the cluster. This property defaults to `10`, but for large clusters, this value may need to be larger.
 |`nifi.cluster.node.protocol.max.threads`|The maximum number of threads that should be used to communicate with other nodes in the cluster. This property defaults to `50`.
 |`nifi.cluster.node.event.history.size`|When the state of a node in the cluster is changed, an event is generated
 and can be viewed in the Cluster page. This value indicates how many events to keep in memory for each node. The default value is `25`.
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/util/ParseDefaultingDateTimeFormatter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/util/ParseDefaultingDateTimeFormatter.java
new file mode 100644
index 0000000..81525a7
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/util/ParseDefaultingDateTimeFormatter.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.web.api.dto.util;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+/**
+ * Some adapters need to create a Date object from a String that contains only a portion of it, such as the time.
+ * This is handled by using a DateTimeFormatter with defaulted values for some fields. The defaults are derived from
+ * the current date/time. Because of this, we can't just create a DateTimeFormatter once and never change it, as doing
+ * so would result in the wrong date/time after midnight, when the date changes.
+ * But we don't want to create a new instance of DateTimeFormatter every time, either, because it uses lazy initialization,
+ * so the first call to parse() is far more expensive than subsequent calls.
+ * This class allows us to easily create a DateTimeFormatter and cache it, continually reusing it, until the date changes.
+ */
+public class ParseDefaultingDateTimeFormatter {
+    private final AtomicReference<Wrapper> wrapperReference = new AtomicReference<>();
+
+    private final Function<LocalDateTime, String> applicabilityTransform;
+    private final Function<LocalDateTime, DateTimeFormatter> formatFactory;
+
+    /**
+     * Default constructor
+     * @param applicabilityTransform a transform that creates a String that can be used identify whether or not previously created DateTimeFormatter exists. This may be created, for instance,
+     * by concatenating specific fields from the given LocalDateTime
+     * @param formatFactory a transform that will give us a DateTimeFormatter that is applicable for the given LocalDateTime
+     */
+    public ParseDefaultingDateTimeFormatter(final Function<LocalDateTime, String> applicabilityTransform, final Function<LocalDateTime, DateTimeFormatter> formatFactory) {
+        this.applicabilityTransform = applicabilityTransform;
+        this.formatFactory = formatFactory;
+    }
+
+    public DateTimeFormatter get() {
+        final LocalDateTime now = LocalDateTime.now();
+        final String applicabilityValue = applicabilityTransform.apply(now);
+
+        final Wrapper wrapper = wrapperReference.get();
+        if (wrapper != null && wrapper.getApplicabilityValue().equals(applicabilityValue)) {
+            return wrapper.getFormatter();
+        }
+
+        final DateTimeFormatter formatter = formatFactory.apply(now);
+        final Wrapper updatedWrapper = new Wrapper(formatter, applicabilityValue);
+        wrapperReference.compareAndSet(wrapper, updatedWrapper);
+        return formatter;
+    }
+
+    private static class Wrapper {
+        private final DateTimeFormatter formatter;
+        private final String applicabilityValue;
+
+        public Wrapper(final DateTimeFormatter formatter, final String applicabilityValue) {
+            this.formatter = formatter;
+            this.applicabilityValue = applicabilityValue;
+        }
+
+        public DateTimeFormatter getFormatter() {
+            return formatter;
+        }
+
+        public String getApplicabilityValue() {
+            return applicabilityValue;
+        }
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/util/TimeAdapter.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/util/TimeAdapter.java
index d044757..f33b5a5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/util/TimeAdapter.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/util/TimeAdapter.java
@@ -37,23 +37,29 @@
     private static final ZoneId ZONE_ID = TimeZone.getDefault().toZoneId();
 
     @Override
-    public String marshal(Date date) throws Exception {
+    public String marshal(Date date) {
         final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT, Locale.US);
         final ZonedDateTime localDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZONE_ID);
         return formatter.format(localDateTime);
     }
 
+
+    private final ParseDefaultingDateTimeFormatter formatter = new ParseDefaultingDateTimeFormatter(
+        timestamp -> String.format("%s%s%s", timestamp.getYear(), timestamp.getMonthValue(), timestamp.getDayOfMonth()),
+        timestamp -> new DateTimeFormatterBuilder().appendPattern(DEFAULT_TIME_FORMAT)
+            .parseDefaulting(ChronoField.YEAR, timestamp.getYear())
+            .parseDefaulting(ChronoField.MONTH_OF_YEAR, timestamp.getMonthValue())
+            .parseDefaulting(ChronoField.DAY_OF_MONTH, timestamp.getDayOfMonth())
+            .parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)
+            .toFormatter(Locale.US));
+
+
     @Override
-    public Date unmarshal(String date) throws Exception {
-        final LocalDateTime now = LocalDateTime.now();
-        final DateTimeFormatter parser = new DateTimeFormatterBuilder().appendPattern(DEFAULT_TIME_FORMAT)
-                .parseDefaulting(ChronoField.YEAR, now.getYear())
-                .parseDefaulting(ChronoField.MONTH_OF_YEAR, now.getMonthValue())
-                .parseDefaulting(ChronoField.DAY_OF_MONTH, now.getDayOfMonth())
-                .parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)
-                .toFormatter(Locale.US);
+    public Date unmarshal(String date) {
+        final DateTimeFormatter parser = formatter.get();
         final LocalDateTime parsedDateTime = LocalDateTime.parse(date, parser);
+
+        final LocalDateTime now = LocalDateTime.now();
         return Date.from(parsedDateTime.toInstant(ZONE_ID.getRules().getOffset(now)));
     }
-
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
index 4eddc8a..d5d9242 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
@@ -103,7 +103,6 @@
     /**
      * Creates an instance.
      *
-     * @param corePoolSize core size of the thread pool
      * @param maxPoolSize the max number of threads in the thread pool
      * @param maxConcurrentRequests maximum number of concurrent requests
      * @param client a client for making requests
@@ -112,12 +111,10 @@
      * @param eventReporter an EventReporter that can be used to notify users of interesting events. May be null.
      * @param nifiProperties properties
      */
-    public ThreadPoolRequestReplicator(final int corePoolSize, final int maxPoolSize, final int maxConcurrentRequests, final HttpReplicationClient client,
+    public ThreadPoolRequestReplicator(final int maxPoolSize, final int maxConcurrentRequests, final HttpReplicationClient client,
         final ClusterCoordinator clusterCoordinator, final RequestCompletionCallback callback, final EventReporter eventReporter, final NiFiProperties nifiProperties) {
-        if (corePoolSize <= 0) {
-            throw new IllegalArgumentException("The Core Pool Size must be greater than zero.");
-        } else if (maxPoolSize < corePoolSize) {
-            throw new IllegalArgumentException("Max Pool Size must be >= Core Pool Size.");
+        if (maxPoolSize < 2) {
+            throw new IllegalArgumentException("Max Pool Size must be >= 2");
         } else if (client == null) {
             throw new IllegalArgumentException("Client may not be null.");
         }
@@ -138,7 +135,8 @@
             return t;
         };
 
-        executorService = new ThreadPoolExecutor(corePoolSize, maxPoolSize, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory);
+        executorService = new ThreadPoolExecutor(maxPoolSize, maxPoolSize, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), threadFactory);
+        executorService.allowCoreThreadTimeOut(true);
 
         maintenanceExecutor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
             @Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/spring/ThreadPoolRequestReplicatorFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/spring/ThreadPoolRequestReplicatorFactoryBean.java
index 4d82b34..9cd206b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/spring/ThreadPoolRequestReplicatorFactoryBean.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/spring/ThreadPoolRequestReplicatorFactoryBean.java
@@ -42,13 +42,12 @@
             final ClusterCoordinator clusterCoordinator = applicationContext.getBean("clusterCoordinator", ClusterCoordinator.class);
             final RequestCompletionCallback requestCompletionCallback = applicationContext.getBean("clusterCoordinator", RequestCompletionCallback.class);
 
-            final int corePoolSize = nifiProperties.getClusterNodeProtocolCorePoolSize();
             final int maxPoolSize = nifiProperties.getClusterNodeProtocolMaxPoolSize();
             final int maxConcurrentRequests = nifiProperties.getClusterNodeMaxConcurrentRequests();
 
             final OkHttpReplicationClient replicationClient = new OkHttpReplicationClient(nifiProperties);
 
-            replicator = new ThreadPoolRequestReplicator(corePoolSize, maxPoolSize, maxConcurrentRequests, replicationClient, clusterCoordinator,
+            replicator = new ThreadPoolRequestReplicator(maxPoolSize, maxConcurrentRequests, replicationClient, clusterCoordinator,
                 requestCompletionCallback, eventReporter, nifiProperties);
         }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
index f4406f8..ba6636f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
@@ -268,7 +268,7 @@
         final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
         };
 
-        final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(2, 5, 100, client, coordinator, requestCompletionCallback, EventReporter.NO_OP, props) {
+        final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(5, 100, client, coordinator, requestCompletionCallback, EventReporter.NO_OP, props) {
             @Override
             protected NodeResponse replicateRequest(final PreparedRequest request, final NodeIdentifier nodeId,
                 final URI uri, final String requestId, final StandardAsyncClusterResponse response) {
@@ -345,7 +345,7 @@
         final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
         };
 
-        final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(2, 5, 100, client, coordinator, requestCompletionCallback, EventReporter.NO_OP, props) {
+        final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(5, 100, client, coordinator, requestCompletionCallback, EventReporter.NO_OP, props) {
             @Override
             public AsyncClusterResponse replicate(Set<NodeIdentifier> nodeIds, String method, URI uri, Object entity, Map<String, String> headers,
                                                   boolean indicateReplicated, boolean verify) {
@@ -408,7 +408,7 @@
         final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
         };
 
-        final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(2, 5, 100, client, coordinator, requestCompletionCallback, EventReporter.NO_OP, props) {
+        final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(5, 100, client, coordinator, requestCompletionCallback, EventReporter.NO_OP, props) {
             @Override
             protected NodeResponse replicateRequest(final PreparedRequest request, final NodeIdentifier nodeId,
                 final URI uri, final String requestId, final StandardAsyncClusterResponse response) {
@@ -628,7 +628,7 @@
         final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
         };
 
-        final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(2, 5, 100, client, coordinator, requestCompletionCallback, EventReporter.NO_OP, nifiProps) {
+        final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(5, 100, client, coordinator, requestCompletionCallback, EventReporter.NO_OP, nifiProps) {
             @Override
             protected NodeResponse replicateRequest(final PreparedRequest request, final NodeIdentifier nodeId, final URI uri, final String requestId,
                     final StandardAsyncClusterResponse response) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/diagnostics/bootstrap/tasks/NiFiPropertiesDiagnosticTask.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/diagnostics/bootstrap/tasks/NiFiPropertiesDiagnosticTask.java
index 22cbe44..1445566 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/diagnostics/bootstrap/tasks/NiFiPropertiesDiagnosticTask.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/diagnostics/bootstrap/tasks/NiFiPropertiesDiagnosticTask.java
@@ -34,7 +34,6 @@
         "nifi.zookeeper.session.timeout",
         "nifi.ui.autorefresh.interval",
         "nifi.cluster.node.protocol.max.threads",
-        "nifi.cluster.node.protocol.threads",
         "nifi.security.allow.anonymous.authentication",
         "nifi.security.user.login.identity.provider",
         "nifi.security.user.authorizer",
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index af92897..1182a41 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -210,7 +210,6 @@
         <nifi.cluster.is.node>false</nifi.cluster.is.node>
         <nifi.cluster.node.address />
         <nifi.cluster.node.protocol.port />
-        <nifi.cluster.node.protocol.threads>10</nifi.cluster.node.protocol.threads>
         <nifi.cluster.node.protocol.max.threads>50</nifi.cluster.node.protocol.max.threads>
         <nifi.cluster.node.event.history.size>25</nifi.cluster.node.event.history.size>
         <nifi.cluster.node.connection.timeout>5 sec</nifi.cluster.node.connection.timeout>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index c854b2a..c1c560f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -261,7 +261,6 @@
 nifi.cluster.is.node=${nifi.cluster.is.node}
 nifi.cluster.node.address=${nifi.cluster.node.address}
 nifi.cluster.node.protocol.port=${nifi.cluster.node.protocol.port}
-nifi.cluster.node.protocol.threads=${nifi.cluster.node.protocol.threads}
 nifi.cluster.node.protocol.max.threads=${nifi.cluster.node.protocol.max.threads}
 nifi.cluster.node.event.history.size=${nifi.cluster.node.event.history.size}
 nifi.cluster.node.connection.timeout=${nifi.cluster.node.connection.timeout}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
index 2a5d573..5588422 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
@@ -55,6 +55,7 @@
     var nfSnippet;
     var nfBirdseye;
     var nfGraph;
+    var trimLengthCaches = {};
 
     var restrictedUsage = d3.map();
     var requiredPermissions = d3.map();
@@ -708,12 +709,20 @@
         },
 
         /**
+        * Clears the cache used to avoid calculating whether or not ellipses are needed for a given text element
+        */
+        clearEllipsisCache: function () {
+            trimLengthCaches = {};
+        },
+
+        /**
          * Applies single line ellipsis to the component in the specified selection if necessary.
          *
          * @param {selection} selection
          * @param {string} text
+         * @param {cacheName} string
          */
-        ellipsis: function (selection, text) {
+        ellipsis: function (selection, text, cacheName) {
             text = text.trim();
             var width = parseInt(selection.attr('width'), 10);
             var node = selection.node();
@@ -721,27 +730,62 @@
             // set the element text
             selection.text(text);
 
-            // see if the field is too big for the field
-            if (text.length > 0 && node.getSubStringLength(0, text.length - 1) > width) {
-                // make some room for the ellipsis
-                width -= 5;
-
-                // determine the appropriate index
-                var i = binarySearch(text.length, function (x) {
-                    var length = node.getSubStringLength(0, x);
-                    if (length > width) {
-                        // length is too long, try the lower half
-                        return -1;
-                    } else if (length < width) {
-                        // length is too short, try the upper half
-                        return 1;
-                    }
-                    return 0;
-                });
-
-                // trim at the appropriate length and add ellipsis
-                selection.text(text.substring(0, i) + String.fromCharCode(8230));
+            // Never apply ellipses to text less than 5 characters and don't keep it in the cache
+            // because it could take up a lot of space unnecessarily.
+            var textLength = text.length;
+            if (textLength < 5) {
+                return;
             }
+
+            // Check our cache of text lengths to see if we already know how much to trim it to
+            var trimLengths = trimLengthCaches[cacheName];
+            if (trimLengths === undefined) {
+                trimLengths = {};
+                trimLengthCaches[cacheName] = trimLengths;
+            }
+
+            var cacheForText = trimLengths[text];
+            var trimLength = (cacheForText === undefined) ? undefined : cacheForText[width];
+            if (trimLength === undefined) {
+                // We haven't cached the length for this text yet. Determine whether we need
+                // to trim & add ellipses or not
+                if (node.getSubStringLength(0, text.length - 1) > width) {
+                    // make some room for the ellipsis
+                    width -= 5;
+
+                    // determine the appropriate index
+                    var i = binarySearch(text.length, function (x) {
+                        var length = node.getSubStringLength(0, x);
+                        if (length > width) {
+                            // length is too long, try the lower half
+                            return -1;
+                        } else if (length < width) {
+                            // length is too short, try the upper half
+                            return 1;
+                        }
+                        return 0;
+                    });
+
+                    trimLength = i;
+                } else {
+                    // trimLength of -1 indicates we do not need ellipses
+                    trimLength = -1;
+                }
+
+                // TODO: Can we clear this when process group changes?
+                // Store the trim length in our cache
+                if (trimLengths[text] === undefined) {
+                    trimLengths[text] = {};
+                }
+                trimLengths[text][width] = trimLength;
+            }
+
+            if (trimLength === -1) {
+                return;
+            }
+
+            // trim at the appropriate length and add ellipsis
+            selection.text(text.substring(0, trimLength) + String.fromCharCode(8230));
         },
 
         /**
@@ -751,8 +795,9 @@
          * @param {selection} selection
          * @param {integer} lineCount
          * @param {string} text
+         * @param {string} cacheName
          */
-        multilineEllipsis: function (selection, lineCount, text) {
+        multilineEllipsis: function (selection, lineCount, text, cacheName) {
             var i = 1;
             var words = text.split(/\s+/).reverse();
 
@@ -801,7 +846,7 @@
                         var remainder = [word].concat(words.reverse());
 
                         // apply ellipsis to the last line
-                        nfCanvasUtils.ellipsis(tspan, remainder.join(' '));
+                        nfCanvasUtils.ellipsis(tspan, remainder.join(' '), cacheName);
 
                         // we've reached the line count
                         break;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index dc5d146..4b280fa 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -231,6 +231,9 @@
         var currentProcessGroup = nfCanvas.getGroupId();
         var currentParameterContext = nfCanvas.getParameterContext();
 
+        // clear caches because what is in the cache may not be applicable and we don't want the caches to grow indefinitely.
+        nfCanvasUtils.clearEllipsisCache();
+
         // update process group id and attempt to reload
         nfCanvas.setGroupId(processGroupId);
         var processGroupXhr = reloadProcessGroup(options);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
index 7313b89..44f8724 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
@@ -931,7 +931,7 @@
                                     connectionFromLabel.text(null).selectAll('title').remove();
 
                                     // apply ellipsis to the label as necessary
-                                    nfCanvasUtils.ellipsis(connectionFromLabel, d.component.source.name);
+                                    nfCanvasUtils.ellipsis(connectionFromLabel, d.component.source.name, 'connection-from');
                                 }).append('title').text(function () {
                                 return d.component.source.name;
                             });
@@ -1040,7 +1040,7 @@
                                     connectionToLabel.text(null).selectAll('title').remove();
 
                                     // apply ellipsis to the label as necessary
-                                    nfCanvasUtils.ellipsis(connectionToLabel, d.component.destination.name);
+                                    nfCanvasUtils.ellipsis(connectionToLabel, d.component.destination.name, 'connection-to');
                                 }).append('title').text(function (d) {
                                 return d.component.destination.name;
                             });
@@ -1145,7 +1145,7 @@
                                     connectionToLabel.text(null).selectAll('title').remove();
 
                                     // apply ellipsis to the label as necessary
-                                    nfCanvasUtils.ellipsis(connectionToLabel, connectionNameValue);
+                                    nfCanvasUtils.ellipsis(connectionToLabel, connectionNameValue, 'connection-name');
                                 }).append('title').text(function () {
                                 return connectionNameValue;
                             });
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
index 86c6885..1f59f05 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
@@ -365,9 +365,9 @@
                             // handle based on the number of tokens in the port name
                             if (words.length === 1) {
                                 // apply ellipsis to the port name as necessary
-                                nfCanvasUtils.ellipsis(portName, name);
+                                nfCanvasUtils.ellipsis(portName, name, 'port-name');
                             } else {
-                                nfCanvasUtils.multilineEllipsis(portName, 2, name);
+                                nfCanvasUtils.multilineEllipsis(portName, 2, name, 'port-name');
                             }
                         }).attrs({
                             'y': offsetY(25)
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
index 656b247..463433a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
@@ -1239,7 +1239,7 @@
                             processGroupName.text(null).selectAll('title').remove();
 
                             // apply ellipsis to the process group name as necessary
-                            nfCanvasUtils.ellipsis(processGroupName, d.component.name);
+                            nfCanvasUtils.ellipsis(processGroupName, d.component.name, 'group-name');
                         })
                         .append('title')
                         .text(function (d) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
index 5667928..0567699 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
@@ -597,7 +597,7 @@
                             processorName.text(null).selectAll('title').remove();
 
                             // apply ellipsis to the processor name as necessary
-                            nfCanvasUtils.ellipsis(processorName, d.component.name);
+                            nfCanvasUtils.ellipsis(processorName, d.component.name, 'processor-name');
                         }).append('title').text(function (d) {
                             return d.component.name;
                         });
@@ -611,7 +611,7 @@
                             processorType.text(null).selectAll('title').remove();
 
                             // apply ellipsis to the processor type as necessary
-                            nfCanvasUtils.ellipsis(processorType, nfCommon.formatType(d.component));
+                            nfCanvasUtils.ellipsis(processorType, nfCommon.formatType(d.component), 'processor-type');
                         }).append('title').text(function (d) {
                             return nfCommon.formatType(d.component);
                         });
@@ -625,7 +625,7 @@
                             processorBundle.text(null).selectAll('title').remove();
 
                             // apply ellipsis to the processor type as necessary
-                            nfCanvasUtils.ellipsis(processorBundle, nfCommon.formatBundle(d.component.bundle));
+                            nfCanvasUtils.ellipsis(processorBundle, nfCommon.formatBundle(d.component.bundle), 'processor-bundle');
                         }).append('title').text(function (d) {
                             return nfCommon.formatBundle(d.component.bundle);
                         });
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
index 7d10947..61d2d4c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
@@ -505,7 +505,7 @@
                             remoteProcessGroupUri.text(null).selectAll('title').remove();
 
                             // apply ellipsis to the remote process group name as necessary
-                            nfCanvasUtils.ellipsis(remoteProcessGroupUri, d.component.targetUris);
+                            nfCanvasUtils.ellipsis(remoteProcessGroupUri, d.component.targetUris, 'rpg-uri');
                         }).append('title').text(function (d) {
                         return d.component.name;
                     });
@@ -604,7 +604,7 @@
                             remoteProcessGroupName.text(null).selectAll('title').remove();
 
                             // apply ellipsis to the remote process group name as necessary
-                            nfCanvasUtils.ellipsis(remoteProcessGroupName, d.component.name);
+                            nfCanvasUtils.ellipsis(remoteProcessGroupName, d.component.name, 'rpg-name');
                         }).append('title').text(function (d) {
                         return d.component.name;
                     });
@@ -794,7 +794,7 @@
 
             // -------------------
             // active thread count
-            // -------------------            
+            // -------------------
 
             nfCanvasUtils.activeThreadCount(remoteProcessGroup, d, function (off) {
                 offset = off;