diff --git a/vendor/magento/module-catalog/Model/ResourceModel/Category/Collection.php b/vendor/magento/module-catalog/Model/ResourceModel/Category/Collection.php
index 259399ece827b..10b97385c858c 100644
--- a/vendor/magento/module-catalog/Model/ResourceModel/Category/Collection.php
+++ b/vendor/magento/module-catalog/Model/ResourceModel/Category/Collection.php
@@ -11,6 +11,8 @@
 use Magento\Framework\App\Config\ScopeConfigInterface;
 use Magento\Framework\DB\Select;
 use Magento\Store\Model\ScopeInterface;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Ddl\Table;
 
 /**
  * Category resource collection
@@ -21,6 +23,8 @@
  */
 class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection
 {
+    private const BULK_PROCESSING_LIMIT = 400;
+
     /**
      * Event prefix name
      *
@@ -282,6 +286,7 @@ protected function _loadProductCount()
      * @return $this
      * @SuppressWarnings(PHPMD.CyclomaticComplexity)
      * @SuppressWarnings(PHPMD.UnusedLocalVariable)
+     * @SuppressWarnings(PHPMD.NPathComplexity)
      * @throws \Magento\Framework\Exception\NoSuchEntityException
      */
     public function loadProductCount($items, $countRegular = true, $countAnchor = true)
@@ -337,16 +342,151 @@ public function loadProductCount($items, $countRegular = true, $countAnchor = tr
             $categoryIds = array_keys($anchor);
             $countSelect = $this->getProductsCountQuery($categoryIds, (bool)$websiteId);
             $categoryProductsCount = $this->_conn->fetchPairs($countSelect);
+            $countFromCategoryTable = [];
+            if (count($categoryIds) > self::BULK_PROCESSING_LIMIT) {
+                $countFromCategoryTable = $this->getCountFromCategoryTableBulk($categoryIds, (int)$websiteId);
+            }
+
             foreach ($anchor as $item) {
-                $productsCount = isset($categoryProductsCount[$item->getId()])
-                    ? (int)$categoryProductsCount[$item->getId()]
-                    : $this->getProductsCountFromCategoryTable($item, $websiteId);
+                $productsCount = 0;
+                if (count($categoryIds) > self::BULK_PROCESSING_LIMIT) {
+                    if (isset($categoryProductsCount[$item->getId()])) {
+                        $productsCount = (int)$categoryProductsCount[$item->getId()];
+                    } elseif (isset($countFromCategoryTable[$item->getId()])) {
+                        $productsCount = (int)$countFromCategoryTable[$item->getId()];
+                    }
+                } else {
+                    $productsCount = isset($categoryProductsCount[$item->getId()])
+                        ? (int)$categoryProductsCount[$item->getId()]
+                        : $this->getProductsCountFromCategoryTable($item, $websiteId);
+                }
                 $item->setProductCount($productsCount);
             }
         }
         return $this;
     }
 
+    /**
+     * Get products number for each category with bulk query
+     *
+     * @param array $categoryIds
+     * @param int $websiteId
+     * @return array
+     * @throws \Zend_Db_Exception
+     */
+    private function getCountFromCategoryTableBulk(
+        array $categoryIds,
+        int $websiteId
+    ) : array {
+        $connection = $this->_conn;
+        $tempTableName = 'temp_category_descendants_' . uniqid();
+        $tempTable = $connection->newTable($tempTableName)
+            ->addColumn(
+                'category_id',
+                Table::TYPE_INTEGER,
+                null,
+                ['unsigned' => true, 'nullable' => false],
+                'Category ID'
+            )
+            ->addColumn(
+                'descendant_id',
+                Table::TYPE_INTEGER,
+                null,
+                ['unsigned' => true, 'nullable' => false],
+                'Descendant ID'
+            )
+            ->addIndex(
+                $connection->getIndexName($tempTableName, ['category_id', 'descendant_id']),
+                ['category_id', 'descendant_id'],
+                ['type' => AdapterInterface::INDEX_TYPE_PRIMARY]
+            );
+        $connection->createTemporaryTable($tempTable);
+        $selectDescendants = $connection->select()
+            ->from(
+                ['ce' => $this->getTable('catalog_category_entity')],
+                ['category_id' => 'ce.entity_id', 'descendant_id' => 'ce2.entity_id']
+            )
+            ->joinInner(
+                ['ce2' => $this->getTable('catalog_category_entity')],
+                'ce2.path LIKE CONCAT(ce.path, \'/%\')',
+                []
+            )
+            ->where('ce.entity_id IN (?)', $categoryIds);
+
+        $connection->query(
+            $connection->insertFromSelect(
+                $selectDescendants,
+                $tempTableName,
+                ['category_id', 'descendant_id']
+            )
+        );
+        $select = $connection->select()
+            ->from(
+                ['t' => $tempTableName],
+                ['category_id' => 't.category_id']
+            )
+            ->joinLeft(
+                ['cp' => $this->getTable('catalog_category_product')],
+                'cp.category_id = t.descendant_id',
+                ['product_count' => 'COUNT(DISTINCT cp.product_id)']
+            );
+        if ($websiteId) {
+            $select->join(
+                ['w' => $this->getProductWebsiteTable()],
+                'cp.product_id = w.product_id',
+                []
+            )->where('w.website_id = ?', $websiteId);
+        }
+        $select->group('t.category_id');
+        $result = $connection->fetchPairs($select);
+        $connection->dropTemporaryTable($tempTableName);
+        $counts = array_fill_keys($categoryIds, 0);
+        foreach ($result as $categoryId => $count) {
+            $counts[$categoryId] = (int)$count;
+        }
+
+        return $counts;
+    }
+
+    /**
+     * Get products count using catalog_category_entity table
+     *
+     * @param Category $item
+     * @param string $websiteId
+     * @return int
+     */
+    private function getProductsCountFromCategoryTable(Category $item, string $websiteId): int
+    {
+        $productCount = 0;
+
+        if ($item->getAllChildren()) {
+            $bind = ['entity_id' => $item->getId(), 'c_path' => $item->getPath() . '/%'];
+            $select = $this->_conn->select();
+            $select->from(
+                ['main_table' => $this->getProductTable()],
+                new \Zend_Db_Expr('COUNT(DISTINCT main_table.product_id)')
+            )->joinInner(
+                ['e' => $this->getTable('catalog_category_entity')],
+                'main_table.category_id=e.entity_id',
+                []
+            )->where(
+                '(e.entity_id = :entity_id OR e.path LIKE :c_path)'
+            );
+            if ($websiteId) {
+                $select->join(
+                    ['w' => $this->getProductWebsiteTable()],
+                    'main_table.product_id = w.product_id',
+                    []
+                )->where(
+                    'w.website_id = ?',
+                    $websiteId
+                );
+            }
+            $productCount = (int)$this->_conn->fetchOne($select, $bind);
+        }
+        return $productCount;
+    }
+
     /**
      * Add category path filter
      *
@@ -519,45 +659,6 @@ public function getProductTable()
         return $this->_productTable;
     }
 
-    /**
-     * Get products count using catalog_category_entity table
-     *
-     * @param Category $item
-     * @param string $websiteId
-     * @return int
-     */
-    private function getProductsCountFromCategoryTable(Category $item, string $websiteId): int
-    {
-        $productCount = 0;
-
-        if ($item->getAllChildren()) {
-            $bind = ['entity_id' => $item->getId(), 'c_path' => $item->getPath() . '/%'];
-            $select = $this->_conn->select();
-            $select->from(
-                ['main_table' => $this->getProductTable()],
-                new \Zend_Db_Expr('COUNT(DISTINCT main_table.product_id)')
-            )->joinInner(
-                ['e' => $this->getTable('catalog_category_entity')],
-                'main_table.category_id=e.entity_id',
-                []
-            )->where(
-                '(e.entity_id = :entity_id OR e.path LIKE :c_path)'
-            );
-            if ($websiteId) {
-                $select->join(
-                    ['w' => $this->getProductWebsiteTable()],
-                    'main_table.product_id = w.product_id',
-                    []
-                )->where(
-                    'w.website_id = ?',
-                    $websiteId
-                );
-            }
-            $productCount = (int)$this->_conn->fetchOne($select, $bind);
-        }
-        return $productCount;
-    }
-
     /**
      * Get query for retrieve count of products per category
      *

