### Description

`TransportFieldCapabilitiesAction` (used for the the `_field_caps` endpoint) has got a nested loop in `merge` method when unmapped fields are included (https://github.com/elastic/elasticsearch/blob/main/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java#L245). This loops over indices -> fields -> indices, which could cause an exponential blowup in objects used when there's a large number of indices and unmapped fields.

We could be more efficient in creating & sharing IndexCap objects in these situations.
server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java
failuresByIndex.putIfAbsent(index, e);
        }
        void collectRemoteException(Exception ex, String clusterAlias, String[] remoteIndices) {
            for (String failedIndex : remoteIndices) {
                collect(RemoteClusterAware.buildRemoteIndexName(clusterAlias, failedIndex), ex);
            }
        }
        boolean isEmpty() {
            return failuresByIndex.isEmpty();
        }
    }
    private class NodeTransportHandler implements TransportRequestHandler<FieldCapabilitiesNodeRequest> {
        @Override
        public void messageReceived(FieldCapabilitiesNodeRequest request, TransportChannel channel, Task task) throws Exception {
            final ActionListener<FieldCapabilitiesNodeResponse> listener = new ChannelActionListener<>(channel, ACTION_NODE_NAME, request);
            ActionListener.completeWith(listener, () -> {
                final List<FieldCapabilitiesIndexResponse> allResponses = new ArrayList<>();
                final Map<ShardId, Exception> allFailures = new HashMap<>();
                final Set<ShardId> allUnmatchedShardIds = new HashSet<>();
                
                
                final Map<String, List<ShardId>> groupedShardIds = request.shardIds()
                    .stream()
                    .collect(Collectors.groupingBy(ShardId::getIndexName));
                final FieldCapabilitiesFetcher fetcher = new FieldCapabilitiesFetcher(indicesService);
                for (List<ShardId> shardIds : groupedShardIds.values()) {
                    final Map<ShardId, Exception> failures = new HashMap<>();
                    final Set<ShardId> unmatched = new HashSet<>();
                    for (ShardId shardId : shardIds) {
                        try {