diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php
index 571e4646f46a3d575eab73799a5be7c9dc3bd065..2002722684431cd63b392a7fb7480c30ff3188c3 100644
--- a/app/code/Magento/Catalog/Model/Category.php
+++ b/app/code/Magento/Catalog/Model/Category.php
@@ -943,8 +943,11 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements
      */
     public function getProductCount()
     {
-        $count = $this->_getResource()->getProductCount($this);
-        $this->setData(self::KEY_PRODUCT_COUNT, $count);
+        if (!$this->hasData(self::KEY_PRODUCT_COUNT)) {
+            $count = $this->_getResource()->getProductCount($this);
+            $this->setData(self::KEY_PRODUCT_COUNT, $count);
+        }
+
         return $this->getData(self::KEY_PRODUCT_COUNT);
     }
 
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php
index c7ddb14a7649de19dadd57c25e4f4111e1992585..78fe3042b5f71661f2c77b5bed52d598f2e50586 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php
@@ -8,9 +8,14 @@
 
 namespace Magento\Catalog\Model\Indexer\Category\Product;
 
-use Magento\Framework\DB\Query\Generator as QueryGenerator;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Product;
+use Magento\Framework\App\ObjectManager;
 use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Query\Generator as QueryGenerator;
+use Magento\Framework\DB\Select;
 use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Store\Model\Store;
 
 /**
  * Class AbstractAction
@@ -45,21 +50,21 @@ abstract class AbstractAction
     /**
      * Cached non anchor categories select by store id
      *
-     * @var \Magento\Framework\DB\Select[]
+     * @var Select[]
      */
     protected $nonAnchorSelects = [];
 
     /**
      * Cached anchor categories select by store id
      *
-     * @var \Magento\Framework\DB\Select[]
+     * @var Select[]
      */
     protected $anchorSelects = [];
 
     /**
      * Cached all product select by store id
      *
-     * @var \Magento\Framework\DB\Select[]
+     * @var Select[]
      */
     protected $productsSelects = [];
 
@@ -119,19 +124,21 @@ abstract class AbstractAction
      * @param \Magento\Store\Model\StoreManagerInterface $storeManager
      * @param \Magento\Catalog\Model\Config $config
      * @param QueryGenerator $queryGenerator
+     * @param MetadataPool|null $metadataPool
      */
     public function __construct(
         \Magento\Framework\App\ResourceConnection $resource,
         \Magento\Store\Model\StoreManagerInterface $storeManager,
         \Magento\Catalog\Model\Config $config,
-        QueryGenerator $queryGenerator = null
+        QueryGenerator $queryGenerator = null,
+        MetadataPool $metadataPool = null
     ) {
         $this->resource = $resource;
         $this->connection = $resource->getConnection();
         $this->storeManager = $storeManager;
         $this->config = $config;
-        $this->queryGenerator = $queryGenerator ?: \Magento\Framework\App\ObjectManager::getInstance()
-            ->get(QueryGenerator::class);
+        $this->queryGenerator = $queryGenerator ?: ObjectManager::getInstance()->get(QueryGenerator::class);
+        $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
     }
 
     /**
@@ -188,9 +195,9 @@ abstract class AbstractAction
      */
     protected function getMainTmpTable()
     {
-        return $this->useTempTable ? $this->getTable(
-            self::MAIN_INDEX_TABLE . self::TEMPORARY_TABLE_SUFFIX
-        ) : $this->getMainTable();
+        return $this->useTempTable
+            ? $this->getTable(self::MAIN_INDEX_TABLE . self::TEMPORARY_TABLE_SUFFIX)
+            : $this->getMainTable();
     }
 
     /**
@@ -218,24 +225,25 @@ abstract class AbstractAction
     /**
      * Retrieve select for reindex products of non anchor categories
      *
-     * @param \Magento\Store\Model\Store $store
-     * @return \Magento\Framework\DB\Select
+     * @param Store $store
+     * @return Select
+     * @throws \Exception when metadata not found for ProductInterface
      */
-    protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $store)
+    protected function getNonAnchorCategoriesSelect(Store $store)
     {
         if (!isset($this->nonAnchorSelects[$store->getId()])) {
             $statusAttributeId = $this->config->getAttribute(
-                \Magento\Catalog\Model\Product::ENTITY,
+                Product::ENTITY,
                 'status'
             )->getId();
             $visibilityAttributeId = $this->config->getAttribute(
-                \Magento\Catalog\Model\Product::ENTITY,
+                Product::ENTITY,
                 'visibility'
             )->getId();
 
             $rootPath = $this->getPathFromCategoryId($store->getRootCategoryId());
 
-            $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
+            $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
             $linkField = $metadata->getLinkField();
             $select = $this->connection->select()->from(
                 ['cc' => $this->getTable('catalog_category_entity')],
@@ -304,12 +312,65 @@ abstract class AbstractAction
                 ]
             );
 
+            $this->addFilteringByChildProductsToSelect($select, $store);
+
             $this->nonAnchorSelects[$store->getId()] = $select;
         }
 
         return $this->nonAnchorSelects[$store->getId()];
     }
 
+    /**
+     * Add filtering by child products to select
+     *
+     * It's used for correct handling of composite products.
+     * This method makes assumption that select already joins `catalog_product_entity` as `cpe`.
+     *
+     * @param Select $select
+     * @param Store $store
+     * @return void
+     * @throws \Exception when metadata not found for ProductInterface
+     */
+    private function addFilteringByChildProductsToSelect(Select $select, Store $store)
+    {
+        $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
+        $linkField = $metadata->getLinkField();
+
+        $statusAttributeId = $this->config->getAttribute(Product::ENTITY, 'status')->getId();
+
+        $select->joinLeft(
+            ['relation' => $this->getTable('catalog_product_relation')],
+            'cpe.' . $linkField . ' = relation.parent_id',
+            []
+        )->joinLeft(
+            ['relation_product_entity' => $this->getTable('catalog_product_entity')],
+            'relation.child_id = relation_product_entity.entity_id',
+            []
+        )->joinLeft(
+            ['child_cpsd' => $this->getTable('catalog_product_entity_int')],
+            'child_cpsd.' . $linkField . ' = '. 'relation_product_entity.' . $linkField
+            . ' AND child_cpsd.store_id = 0'
+            . ' AND child_cpsd.attribute_id = ' . $statusAttributeId,
+            []
+        )->joinLeft(
+            ['child_cpss' => $this->getTable('catalog_product_entity_int')],
+            'child_cpss.' . $linkField . ' = '. 'relation_product_entity.' . $linkField . ''
+            . ' AND child_cpss.attribute_id = child_cpsd.attribute_id'
+            . ' AND child_cpss.store_id = ' . $store->getId(),
+            []
+        )->where(
+            'relation.child_id IS NULL OR '
+            . $this->connection->getIfNullSql('child_cpss.value', 'child_cpsd.value') . ' = ?',
+            \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED
+        )->group(
+            [
+                'cc.entity_id',
+                'ccp.product_id',
+                'visibility'
+            ]
+        );
+    }
+
     /**
      * Check whether select ranging is needed
      *
@@ -323,16 +384,13 @@ abstract class AbstractAction
     /**
      * Return selects cut by min and max
      *
-     * @param \Magento\Framework\DB\Select $select
+     * @param Select $select
      * @param string $field
      * @param int $range
-     * @return \Magento\Framework\DB\Select[]
+     * @return Select[]
      */
-    protected function prepareSelectsByRange(
-        \Magento\Framework\DB\Select $select,
-        $field,
-        $range = self::RANGE_CATEGORY_STEP
-    ) {
+    protected function prepareSelectsByRange(Select $select, $field, $range = self::RANGE_CATEGORY_STEP)
+    {
         if ($this->isRangingNeeded()) {
             $iterator = $this->queryGenerator->generate(
                 $field,
@@ -353,10 +411,10 @@ abstract class AbstractAction
     /**
      * Reindex products of non anchor categories
      *
-     * @param \Magento\Store\Model\Store $store
+     * @param Store $store
      * @return void
      */
-    protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store)
+    protected function reindexNonAnchorCategories(Store $store)
     {
         $selects = $this->prepareSelectsByRange($this->getNonAnchorCategoriesSelect($store), 'entity_id');
         foreach ($selects as $select) {
@@ -374,10 +432,10 @@ abstract class AbstractAction
     /**
      * Check if anchor select isset
      *
-     * @param \Magento\Store\Model\Store $store
+     * @param Store $store
      * @return bool
      */
-    protected function hasAnchorSelect(\Magento\Store\Model\Store $store)
+    protected function hasAnchorSelect(Store $store)
     {
         return isset($this->anchorSelects[$store->getId()]);
     }
@@ -385,19 +443,20 @@ abstract class AbstractAction
     /**
      * Create anchor select
      *
-     * @param \Magento\Store\Model\Store $store
-     * @return \Magento\Framework\DB\Select
+     * @param Store $store
+     * @return Select
+     * @throws \Exception when metadata not found for ProductInterface or CategoryInterface
      * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
      */
-    protected function createAnchorSelect(\Magento\Store\Model\Store $store)
+    protected function createAnchorSelect(Store $store)
     {
         $isAnchorAttributeId = $this->config->getAttribute(
             \Magento\Catalog\Model\Category::ENTITY,
             'is_anchor'
         )->getId();
-        $statusAttributeId = $this->config->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'status')->getId();
+        $statusAttributeId = $this->config->getAttribute(Product::ENTITY, 'status')->getId();
         $visibilityAttributeId = $this->config->getAttribute(
-            \Magento\Catalog\Model\Product::ENTITY,
+            Product::ENTITY,
             'visibility'
         )->getId();
         $rootCatIds = explode('/', $this->getPathFromCategoryId($store->getRootCategoryId()));
@@ -405,12 +464,12 @@ abstract class AbstractAction
 
         $temporaryTreeTable = $this->makeTempCategoryTreeIndex();
 
-        $productMetadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
-        $categoryMetadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\CategoryInterface::class);
+        $productMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
+        $categoryMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\CategoryInterface::class);
         $productLinkField = $productMetadata->getLinkField();
         $categoryLinkField = $categoryMetadata->getLinkField();
 
-        return $this->connection->select()->from(
+        $select = $this->connection->select()->from(
             ['cc' => $this->getTable('catalog_category_entity')],
             []
         )->joinInner(
@@ -492,6 +551,10 @@ abstract class AbstractAction
                 'visibility' => new \Zend_Db_Expr($this->connection->getIfNullSql('cpvs.value', 'cpvd.value')),
             ]
         );
+
+        $this->addFilteringByChildProductsToSelect($select, $store);
+
+        return $select;
     }
 
     /**
@@ -586,10 +649,10 @@ abstract class AbstractAction
     /**
      * Retrieve select for reindex products of non anchor categories
      *
-     * @param \Magento\Store\Model\Store $store
-     * @return \Magento\Framework\DB\Select
+     * @param Store $store
+     * @return Select
      */
-    protected function getAnchorCategoriesSelect(\Magento\Store\Model\Store $store)
+    protected function getAnchorCategoriesSelect(Store $store)
     {
         if (!$this->hasAnchorSelect($store)) {
             $this->anchorSelects[$store->getId()] = $this->createAnchorSelect($store);
@@ -600,10 +663,10 @@ abstract class AbstractAction
     /**
      * Reindex products of anchor categories
      *
-     * @param \Magento\Store\Model\Store $store
+     * @param Store $store
      * @return void
      */
-    protected function reindexAnchorCategories(\Magento\Store\Model\Store $store)
+    protected function reindexAnchorCategories(Store $store)
     {
         $selects = $this->prepareSelectsByRange($this->getAnchorCategoriesSelect($store), 'entity_id');
 
@@ -622,22 +685,23 @@ abstract class AbstractAction
     /**
      * Get select for all products
      *
-     * @param \Magento\Store\Model\Store $store
-     * @return \Magento\Framework\DB\Select
+     * @param Store $store
+     * @return Select
+     * @throws \Exception when metadata not found for ProductInterface
      */
-    protected function getAllProducts(\Magento\Store\Model\Store $store)
+    protected function getAllProducts(Store $store)
     {
         if (!isset($this->productsSelects[$store->getId()])) {
             $statusAttributeId = $this->config->getAttribute(
-                \Magento\Catalog\Model\Product::ENTITY,
+                Product::ENTITY,
                 'status'
             )->getId();
             $visibilityAttributeId = $this->config->getAttribute(
-                \Magento\Catalog\Model\Product::ENTITY,
+                Product::ENTITY,
                 'visibility'
             )->getId();
 
-            $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
+            $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
             $linkField = $metadata->getLinkField();
 
             $select = $this->connection->select()->from(
@@ -726,10 +790,10 @@ abstract class AbstractAction
     /**
      * Reindex all products to root category
      *
-     * @param \Magento\Store\Model\Store $store
+     * @param Store $store
      * @return void
      */
-    protected function reindexRootCategory(\Magento\Store\Model\Store $store)
+    protected function reindexRootCategory(Store $store)
     {
         if ($this->isIndexRootCategoryNeeded()) {
             $selects = $this->prepareSelectsByRange(
@@ -750,16 +814,4 @@ abstract class AbstractAction
             }
         }
     }
-
-    /**
-     * @return \Magento\Framework\EntityManager\MetadataPool
-     */
-    private function getMetadataPool()
-    {
-        if (null === $this->metadataPool) {
-            $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance()
-                ->get(\Magento\Framework\EntityManager\MetadataPool::class);
-        }
-        return $this->metadataPool;
-    }
 }
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
index 1b988534328e9971ecc38dec36d294a780a9b79d..99b087691ab09aa0b4b42a655d1efcc3890dfa1b 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
@@ -6,9 +6,19 @@
 namespace Magento\Catalog\Model\Indexer\Product\Category\Action;
 
 use Magento\Catalog\Model\Category;
+use Magento\Catalog\Model\Config;
 use Magento\Catalog\Model\Product;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Query\Generator as QueryGenerator;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Event\ManagerInterface as EventManagerInterface;
 use Magento\Framework\Indexer\CacheContext;
+use Magento\Store\Model\StoreManagerInterface;
 
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
 class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction
 {
     /**
@@ -19,32 +29,102 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
     protected $limitationByProducts;
 
     /**
-     * @var \Magento\Framework\Indexer\CacheContext
+     * @var CacheContext
      */
     private $cacheContext;
 
+    /**
+     * @var EventManagerInterface|null
+     */
+    private $eventManager;
+
+    /**
+     * @param ResourceConnection $resource
+     * @param StoreManagerInterface $storeManager
+     * @param Config $config
+     * @param QueryGenerator|null $queryGenerator
+     * @param MetadataPool|null $metadataPool
+     * @param CacheContext|null $cacheContext
+     * @param EventManagerInterface|null $eventManager
+     */
+    public function __construct(
+        ResourceConnection $resource,
+        StoreManagerInterface $storeManager,
+        Config $config,
+        QueryGenerator $queryGenerator = null,
+        MetadataPool $metadataPool = null,
+        CacheContext $cacheContext = null,
+        EventManagerInterface $eventManager = null
+    ) {
+        parent::__construct($resource, $storeManager, $config, $queryGenerator, $metadataPool);
+        $this->cacheContext = $cacheContext ?: ObjectManager::getInstance()->get(CacheContext::class);
+        $this->eventManager = $eventManager ?: ObjectManager::getInstance()->get(EventManagerInterface::class);
+    }
+
     /**
      * Refresh entities index
      *
      * @param int[] $entityIds
      * @param bool $useTempTable
      * @return $this
+     * @throws \Exception if metadataPool doesn't contain metadata for ProductInterface
+     * @throws \DomainException
      */
     public function execute(array $entityIds = [], $useTempTable = false)
     {
-        $this->limitationByProducts = $entityIds;
+        $idsToBeReIndexed = $this->getProductIdsWithParents($entityIds);
+
+        $this->limitationByProducts = $idsToBeReIndexed;
         $this->useTempTable = $useTempTable;
 
+        $affectedCategories = $this->getCategoryIdsFromIndex($idsToBeReIndexed);
+
         $this->removeEntries();
 
         $this->reindex();
 
-        $this->registerProducts($entityIds);
-        $this->registerCategories($entityIds);
+        $affectedCategories = array_merge($affectedCategories, $this->getCategoryIdsFromIndex($idsToBeReIndexed));
+
+        $this->registerProducts($idsToBeReIndexed);
+        $this->registerCategories($affectedCategories);
+        $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]);
 
         return $this;
     }
 
+    /**
+     * Get IDs of parent products by their child IDs.
+     *
+     * Returns identifiers of parent product from the catalog_product_relation.
+     * Please note that returned ids don't contain ids of passed child products.
+     *
+     * @param int[] $childProductIds
+     * @return int[]
+     * @throws \Exception if metadataPool doesn't contain metadata for ProductInterface
+     * @throws \DomainException
+     */
+    private function getProductIdsWithParents(array $childProductIds)
+    {
+        /** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */
+        $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
+        $fieldForParent = $metadata->getLinkField();
+
+        $select = $this->connection
+            ->select()
+            ->from(['relation' => $this->getTable('catalog_product_relation')], [])
+            ->distinct(true)
+            ->where('child_id IN (?)', $childProductIds)
+            ->join(
+                ['cpe' => $this->getTable('catalog_product_entity')],
+                'relation.parent_id = cpe.' . $fieldForParent,
+                ['cpe.entity_id']
+            );
+
+        $parentProductIds = $this->connection->fetchCol($select);
+
+        return array_unique(array_merge($childProductIds, $parentProductIds));
+    }
+
     /**
      * Register affected products
      *
@@ -53,26 +133,19 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
      */
     private function registerProducts($entityIds)
     {
-        $this->getCacheContext()->registerEntities(Product::CACHE_TAG, $entityIds);
+        $this->cacheContext->registerEntities(Product::CACHE_TAG, $entityIds);
     }
 
     /**
      * Register categories assigned to products
      *
-     * @param array $entityIds
+     * @param array $categoryIds
      * @return void
      */
-    private function registerCategories($entityIds)
+    private function registerCategories(array $categoryIds)
     {
-        $categories = $this->connection->fetchCol(
-            $this->connection->select()
-                ->from($this->getMainTable(), ['category_id'])
-                ->where('product_id IN (?)', $entityIds)
-                ->distinct()
-        );
-
-        if ($categories) {
-            $this->getCacheContext()->registerEntities(Category::CACHE_TAG, $categories);
+        if ($categoryIds) {
+            $this->cacheContext->registerEntities(Category::CACHE_TAG, $categoryIds);
         }
     }
 
@@ -98,7 +171,7 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
     protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $store)
     {
         $select = parent::getNonAnchorCategoriesSelect($store);
-        return $select->where('ccp.product_id IN (?)', $this->limitationByProducts);
+        return $select->where('ccp.product_id IN (?) OR relation.child_id IN (?)', $this->limitationByProducts);
     }
 
     /**
@@ -136,16 +209,25 @@ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
     }
 
     /**
-     * Get cache context
+     * Returns a list of category ids which are assigned to product ids in the index
      *
      * @return \Magento\Framework\Indexer\CacheContext
-     * @deprecated 101.0.0
      */
-    private function getCacheContext()
+    private function getCategoryIdsFromIndex(array $productIds)
     {
-        if ($this->cacheContext === null) {
-            $this->cacheContext = \Magento\Framework\App\ObjectManager::getInstance()->get(CacheContext::class);
+        $categoryIds = $this->connection->fetchCol(
+            $this->connection->select()
+                ->from($this->getMainTable(), ['category_id'])
+                ->where('product_id IN (?)', $productIds)
+                ->distinct()
+        );
+        $parentCategories = $categoryIds;
+        foreach ($categoryIds as $categoryId) {
+            $parentIds = explode('/', $this->getPathFromCategoryId($categoryId));
+            $parentCategories = array_merge($parentCategories, $parentIds);
         }
-        return $this->cacheContext;
+        $categoryIds = array_unique($parentCategories);
+
+        return $categoryIds;
     }
 }
diff --git a/app/code/Magento/Catalog/view/frontend/templates/navigation/left.phtml b/app/code/Magento/Catalog/view/frontend/templates/navigation/left.phtml
index fa70e1513557847339cf125e5d7f24a1b78a0e6f..01820361744e0aebc18ab15cf4545275e2c87804 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/navigation/left.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/navigation/left.phtml
@@ -28,6 +28,7 @@
                 <dt><?= /* @escapeNotVerified */ __('Category') ?></dt>
                 <dd>
                     <ol class="items">
+                        <?php /** @var \Magento\Catalog\Model\Category $_category */ ?>
                         <?php foreach ($_categories as $_category): ?>
                             <?php if ($_category->getIsActive()): ?>
                                 <li class="item">
diff --git a/app/code/Magento/CatalogInventory/Helper/Stock.php b/app/code/Magento/CatalogInventory/Helper/Stock.php
index 410e35096ee58c7ea1835ec48421f6999166ebf2..99a83753e43793f98ee2ba9cfbf465e3d845a2de 100644
--- a/app/code/Magento/CatalogInventory/Helper/Stock.php
+++ b/app/code/Magento/CatalogInventory/Helper/Stock.php
@@ -156,7 +156,7 @@ class Stock
             $resource = $this->getStockStatusResource();
             $resource->addStockDataToCollection(
                 $collection,
-                !$isShowOutOfStock || $collection->getFlag('require_stock_items')
+                !$isShowOutOfStock
             );
             $collection->setFlag($stockFlag, true);
         }
diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
index 9009a40c19dff3fc65772032b6480d92012d07a8..1aa3dba07fc78d8f8ec4cebea2969cc057fdb11b 100644
--- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
+++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
@@ -123,11 +123,12 @@ class Fulltext implements \Magento\Framework\Indexer\ActionInterface, \Magento\F
         $saveHandler = $this->indexerHandlerFactory->create([
             'data' => $this->data
         ]);
+
         foreach ($storeIds as $storeId) {
             $dimension = $this->dimensionFactory->create(['name' => 'scope', 'value' => $storeId]);
             $productIds = array_unique(array_merge($ids, $this->fulltextResource->getRelationsByChild($ids)));
             $saveHandler->deleteIndex([$dimension], new \ArrayObject($productIds));
-            $saveHandler->saveIndex([$dimension], $this->fullAction->rebuildStoreIndex($storeId, $ids));
+            $saveHandler->saveIndex([$dimension], $this->fullAction->rebuildStoreIndex($storeId, $productIds));
         }
     }
 
diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php
index 07ff47610f3306b95f57f0287f948cf1988a41cc..98fb2528593e7fd917aee4d5ea4664ccafd1b9d7 100644
--- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php
+++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php
@@ -377,9 +377,9 @@ class DataProvider
     public function getProductChildIds($productId, $typeId)
     {
         $typeInstance = $this->getProductTypeInstance($typeId);
-        $relation = $typeInstance->isComposite(
-            $this->getProductEmulator($typeId)
-        ) ? $typeInstance->getRelationInfo() : false;
+        $relation = $typeInstance->isComposite($this->getProductEmulator($typeId))
+            ? $typeInstance->getRelationInfo()
+            : false;
 
         if ($relation && $relation->getTable() && $relation->getParentFieldName() && $relation->getChildFieldName()) {
             $select = $this->connection->select()->from(
diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php
index 639c0e8ca66f0a044880fda98ae0e3d642f8c8c8..5abcd5e7592e1a64ca8747469a6d7e43fa123ab3 100644
--- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php
+++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php
@@ -5,6 +5,7 @@
  */
 namespace Magento\CatalogSearch\Model\Indexer\Fulltext\Action;
 
+use Magento\Catalog\Api\Data\ProductInterface;
 use Magento\CatalogSearch\Model\Indexer\Fulltext;
 use Magento\Framework\App\ObjectManager;
 use Magento\Framework\App\ResourceConnection;
@@ -297,27 +298,32 @@ class Full
     /**
      * Get parents IDs of product IDs to be re-indexed
      *
+     * @deprecated as it not used in the class anymore and duplicates another API method
+     * @see \Magento\CatalogSearch\Model\ResourceModel\Fulltext::getRelationsByChild()
+     *
      * @param int[] $entityIds
      * @return int[]
+     * @throws \Exception
      */
     protected function getProductIdsFromParents(array $entityIds)
     {
-        /** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */
-        $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
-        $fieldForParent = $metadata->getLinkField();
+        $connection = $this->connection;
+        $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
 
-        $select = $this->connection
+        $select = $connection
             ->select()
-            ->from(['relation' => $this->getTable('catalog_product_relation')], [])
+            ->from(
+                ['relation' => $this->getTable('catalog_product_relation')],
+                []
+            )
             ->distinct(true)
             ->where('child_id IN (?)', $entityIds)
-            ->where('parent_id NOT IN (?)', $entityIds)
             ->join(
                 ['cpe' => $this->getTable('catalog_product_entity')],
-                'relation.parent_id = cpe.' . $fieldForParent,
+                'relation.parent_id = cpe.' . $linkField,
                 ['cpe.entity_id']
             );
-        return $this->connection->fetchCol($select);
+        return $connection->fetchCol($select);
     }
 
     /**
@@ -335,7 +341,7 @@ class Full
     public function rebuildStoreIndex($storeId, $productIds = null)
     {
         if ($productIds !== null) {
-            $productIds = array_unique(array_merge($productIds, $this->getProductIdsFromParents($productIds)));
+            $productIds = array_unique($productIds);
         }
 
         // prepare searchable attributes
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext.php
index 15349e91c3fe9b9d1690d8b2eecf91c176def860..e9737d0aa059983d0b3daa1384800288e06e35ac 100644
--- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext.php
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext.php
@@ -82,17 +82,20 @@ class Fulltext extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
     {
         $connection = $this->getConnection();
         $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
-        $select = $connection->select()->from(
-            ['relation' => $this->getTable('catalog_product_relation')],
-            []
-        )->join(
-            ['cpe' => $this->getTable('catalog_product_entity')],
-            'cpe.' . $linkField . ' = relation.parent_id',
-            ['cpe.entity_id']
-        )->where(
-            'relation.child_id IN (?)',
-            $childIds
-        )->distinct(true);
+        $select = $connection
+            ->select()
+            ->from(
+                ['relation' => $this->getTable('catalog_product_relation')],
+                []
+            )->distinct(true)
+            ->join(
+                ['cpe' => $this->getTable('catalog_product_entity')],
+                'cpe.' . $linkField . ' = relation.parent_id',
+                ['cpe.entity_id']
+            )->where(
+                'relation.child_id IN (?)',
+                $childIds
+            );
 
         return $connection->fetchCol($select);
     }
diff --git a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertFilterProductList.php b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertFilterProductList.php
index ea80e5ac224808cb125054c8f69bb48455516ffd..19de61c698701525369af97ccadd01bb3e4ca3a7 100644
--- a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertFilterProductList.php
+++ b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertFilterProductList.php
@@ -3,7 +3,6 @@
  * Copyright © Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
-
 namespace Magento\LayeredNavigation\Test\Constraint;
 
 use Magento\Catalog\Test\Fixture\Category;
diff --git a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertProductsCount.php b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertProductsCount.php
new file mode 100644
index 0000000000000000000000000000000000000000..7c9a1f4faef1663af977b2fc3bbca99c012e13b7
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Constraint/AssertProductsCount.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\LayeredNavigation\Test\Constraint;
+
+use Magento\Catalog\Test\Fixture\Category;
+use Magento\Catalog\Test\Page\Category\CatalogCategoryView;
+use Magento\Mtf\Client\BrowserInterface;
+use Magento\Mtf\Constraint\AbstractConstraint;
+
+/**
+ * Assertion that category name and products qty are correct in category layered navigation
+ */
+class AssertProductsCount extends AbstractConstraint
+{
+    /**
+     * Browser instance.
+     *
+     * @var BrowserInterface
+     */
+    private $browser;
+
+    /**
+     * Catalog category view page.
+     *
+     * @var CatalogCategoryView $catalogCategoryView
+     */
+    private $catalogCategoryView;
+
+    /**
+     * Assert that category name and products cont in layered navigation are correct
+     *
+     * @param CatalogCategoryView $catalogCategoryView
+     * @param Category $category
+     * @param BrowserInterface $browser
+     * @param string $productsCount
+     * @return void
+     */
+    public function processAssert(
+        CatalogCategoryView $catalogCategoryView,
+        Category $category,
+        BrowserInterface $browser,
+        $productsCount
+    ) {
+        $this->browser = $browser;
+        $this->catalogCategoryView = $catalogCategoryView;
+        while ($category) {
+            $parentCategory = $category->getDataFieldConfig('parent_id')['source']->getParentCategory();
+            if ($parentCategory && $parentCategory->getData('is_anchor') == 'No') {
+                $this->openCategory($parentCategory);
+                \PHPUnit_Framework_Assert::assertTrue(
+                    $this->catalogCategoryView->getLayeredNavigationBlock()->isCategoryVisible(
+                        $category,
+                        $productsCount
+                    ),
+                    'Category ' . $category->getName() . ' is absent in Layered Navigation or products count is wrong'
+                );
+            }
+            $category = $parentCategory;
+        }
+    }
+
+    /**
+     * Open category.
+     *
+     * @param Category $category
+     * @return void
+     */
+    private function openCategory(Category $category)
+    {
+        $categoryUrlKey = [];
+
+        while ($category) {
+            $categoryUrlKey[] = $category->hasData('url_key')
+                ? strtolower($category->getUrlKey())
+                : trim(strtolower(preg_replace('#[^0-9a-z%]+#i', '-', $category->getName())), '-');
+
+            $category = $category->getDataFieldConfig('parent_id')['source']->getParentCategory();
+            if ($category !== null && 1 == $category->getParentId()) {
+                $category = null;
+            }
+        }
+        $categoryUrlKey = $_ENV['app_frontend_url'] . implode('/', array_reverse($categoryUrlKey)) . '.html';
+
+        $this->browser->open($categoryUrlKey);
+    }
+
+    /**
+     * Assert success message that category is present in layered navigation and product is visible in product grid.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'Category is present in layered navigation and product is visible in product grid.';
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Repository/Category.xml b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Repository/Category.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7799faf309ccfc3492feecf210cf2d981acdd568
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Repository/Category.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" ?>
+<!--
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/Magento/Mtf/Repository/etc/repository.xsd">
+    <repository class="Magento\Catalog\Test\Repository\Category">
+        <dataset name="default_non_anchored_subcategory">
+            <field name="name" xsi:type="string">DefaultSubcategory%isolation%</field>
+            <field name="url_key" xsi:type="string">default-subcategory-%isolation%</field>
+            <field name="parent_id" xsi:type="array">
+                <item name="dataset" xsi:type="string">default_category</item>
+            </field>
+            <field name="is_active" xsi:type="string">Yes</field>
+            <field name="is_anchor" xsi:type="string">No</field>
+            <field name="include_in_menu" xsi:type="string">Yes</field>
+        </dataset>
+
+        <dataset name="default_second_level_anchored_subcategory">
+            <field name="name" xsi:type="string">DefaultSubcategory%isolation%</field>
+            <field name="url_key" xsi:type="string">default-subcategory-%isolation%</field>
+            <field name="parent_id" xsi:type="array">
+                <item name="dataset" xsi:type="string">default_non_anchored_subcategory</item>
+            </field>
+            <field name="is_active" xsi:type="string">Yes</field>
+            <field name="is_anchor" xsi:type="string">Yes</field>
+            <field name="include_in_menu" xsi:type="string">Yes</field>
+        </dataset>
+
+        <dataset name="default_third_level_non_anchored_subcategory">
+            <field name="name" xsi:type="string">DefaultSubcategory%isolation%</field>
+            <field name="url_key" xsi:type="string">default-subcategory-%isolation%</field>
+            <field name="parent_id" xsi:type="array">
+                <item name="dataset" xsi:type="string">default_second_level_anchored_subcategory</item>
+            </field>
+            <field name="is_active" xsi:type="string">Yes</field>
+            <field name="is_anchor" xsi:type="string">No</field>
+            <field name="include_in_menu" xsi:type="string">Yes</field>
+        </dataset>
+
+        <dataset name="default_fourth_level_anchored_subcategory">
+            <field name="name" xsi:type="string">DefaultSubcategory%isolation%</field>
+            <field name="url_key" xsi:type="string">default-subcategory-%isolation%</field>
+            <field name="parent_id" xsi:type="array">
+                <item name="dataset" xsi:type="string">default_third_level_non_anchored_subcategory</item>
+            </field>
+            <field name="is_active" xsi:type="string">Yes</field>
+            <field name="is_anchor" xsi:type="string">Yes</field>
+            <field name="include_in_menu" xsi:type="string">Yes</field>
+        </dataset>
+    </repository>
+</config>
diff --git a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/TestCase/ProductsCountInLayeredNavigationTest.php b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/TestCase/ProductsCountInLayeredNavigationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..179f9ef2579093184c9f452e9272a01b45900e50
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/TestCase/ProductsCountInLayeredNavigationTest.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\LayeredNavigation\Test\TestCase;
+
+use Magento\Catalog\Test\Fixture\Category;
+use Magento\Mtf\TestCase\Injectable;
+use Magento\Catalog\Test\Page\Adminhtml\CatalogProductEdit;
+use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex;
+use Magento\Mtf\Fixture\FixtureFactory;
+
+/**
+ * Preconditions:
+ * 1. Create four categories assigned in ascending order (Default Category->first->second->third->fourth)
+ * first and third categories should not be anchored, second and fourth categories should be anchored
+ * 2. Create configurable product with two configurable options and assign it to category "fourth"
+ *
+ * Steps:
+ * 1. Disable configurable options via massaction or from edit product page
+ * 2. Open created non anchored categories on frontend
+ * 3. Perform assertions
+ *
+ * @group Layered_Navigation
+ * @ZephyrId MAGETWO-82891
+ */
+class ProductsCountInLayeredNavigationTest extends Injectable
+{
+    /**
+     * Product page with a grid
+     *
+     * @var CatalogProductIndex
+     */
+    protected $catalogProductIndex;
+
+    /**
+     * Page to update a product
+     *
+     * @var CatalogProductEdit
+     */
+    protected $editProductPage;
+
+    /**
+     * Fixture factory
+     *
+     * @var FixtureFactory
+     */
+    protected $fixtureFactory;
+
+    /**
+     * Injection data
+     *
+     * @param CatalogProductIndex $catalogProductIndex
+     * @param CatalogProductEdit $editProductPage
+     * @param FixtureFactory $fixtureFactory
+     * @return void
+     */
+    public function __inject(
+        CatalogProductIndex $catalogProductIndex,
+        CatalogProductEdit $editProductPage,
+        FixtureFactory $fixtureFactory
+    ) {
+        $this->catalogProductIndex = $catalogProductIndex;
+        $this->editProductPage = $editProductPage;
+        $this->fixtureFactory = $fixtureFactory;
+    }
+
+    /**
+     * Test category name and products count displaying in layered navigation after configurable options disabling
+     *
+     * @param Category $category
+     * @param boolean $disableFromProductsGreed
+     * @return array
+     */
+    public function test(
+        Category $category,
+        $disableFromProductsGreed = true
+    ) {
+        // Preconditions
+        $category->persist();
+        // Steps
+        $products = $category->getDataFieldConfig('category_products')['source']->getProducts();
+        $configurableOptions = [];
+        /** @var \Magento\ConfigurableProduct\Test\Fixture\ConfigurableProduct\ $product */
+        foreach ($products as $product) {
+            $configurableOptions = array_merge(
+                $configurableOptions,
+                $product->getConfigurableAttributesData()['matrix']
+            );
+        }
+        // Disable configurable options
+        if ($disableFromProductsGreed) {
+            $this->catalogProductIndex->open();
+            $this->catalogProductIndex->getProductGrid()->massaction(
+                array_map(
+                    function ($assignedProduct) {
+                        return ['sku' => $assignedProduct['sku']];
+                    },
+                    $configurableOptions
+                ),
+                ['Change status' => 'Disable']
+            );
+        } else {
+            $productToDisable = $this->fixtureFactory->createByCode(
+                'catalogProductSimple',
+                ['data' => ['status' => 'No']]
+            );
+            foreach ($configurableOptions as $configurableOption) {
+                $filter = ['sku' => $configurableOption['sku']];
+                $this->catalogProductIndex->open();
+                $this->catalogProductIndex->getProductGrid()->searchAndOpen($filter);
+                $this->editProductPage->getProductForm()->fill($productToDisable);
+                $this->editProductPage->getFormPageActions()->save();
+            }
+        }
+        return [
+            'products' => $configurableOptions
+        ];
+    }
+}
diff --git a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/TestCase/ProductsCountInLayeredNavigationTest.xml b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/TestCase/ProductsCountInLayeredNavigationTest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..41e18e64540970783c931e392c40c80c96c5c7f4
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/TestCase/ProductsCountInLayeredNavigationTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd">
+    <testCase name="Magento\LayeredNavigation\Test\TestCase\ProductsCountInLayeredNavigationTest" summary="Check configurable products count in child categories of non-anchor category" ticketId="MAGETWO-82891">
+        <variation name="ProductsCountInLayeredNavigationTestVariation1" summary="Check configurable products count with disabled by massaction products">
+            <data name="runReindex" xsi:type="boolean">true</data>
+            <data name="flushCache" xsi:type="boolean">true</data>
+            <data name="category/dataset" xsi:type="string">default_fourth_level_anchored_subcategory</data>
+            <data name="category/data/category_products/dataset" xsi:type="string">configurableProduct::product_with_size</data>
+            <data name="productsCount" xsi:type="string">0</data>
+            <data name="disableFromProductsGreed" xsi:type="boolean">true</data>
+            <constraint name="Magento\LayeredNavigation\Test\Constraint\AssertProductsCount" />
+        </variation>
+        <variation name="ProductsCountInLayeredNavigationTestVariation2" summary="Check configurable products count with disabled from edit page products">
+            <data name="runReindex" xsi:type="boolean">true</data>
+            <data name="flushCache" xsi:type="boolean">true</data>
+            <data name="category/dataset" xsi:type="string">default_fourth_level_anchored_subcategory</data>
+            <data name="category/data/category_products/dataset" xsi:type="string">configurableProduct::product_with_size</data>
+            <data name="productsCount" xsi:type="string">0</data>
+            <data name="disableFromProductsGreed" xsi:type="boolean">false</data>
+            <constraint name="Magento\LayeredNavigation\Test\Constraint\AssertProductsCount" />
+        </variation>
+    </testCase>
+</config>
diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/FulltextTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/FulltextTest.php
index 29d1547f2d872f0764fb0ad5dbd10f8efcf02f9e..da2fc4498718b6b07342e9e2c37eced0e88f24b5 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/FulltextTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/FulltextTest.php
@@ -6,12 +6,14 @@
 namespace Magento\CatalogSearch\Model\Indexer;
 
 use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Visibility;
 use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection;
 use Magento\TestFramework\Helper\Bootstrap;
 
 /**
  * @magentoDbIsolation disabled
  * @magentoDataFixture Magento/CatalogSearch/_files/indexer_fulltext.php
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class FulltextTest extends \PHPUnit\Framework\TestCase
 {
@@ -186,13 +188,42 @@ class FulltextTest extends \PHPUnit\Framework\TestCase
         $this->assertCount(4, $products);
     }
 
+    /**
+     * Test the case when the last child product of the configurable becomes disabled/out of stock.
+     *
+     * Such behavior should enforce parent product to be deleted from the index as its latest child become unavailable
+     * and the configurable cannot be sold anymore.
+     *
+     * @magentoAppArea adminhtml
+     * @magentoDataFixture Magento/CatalogSearch/_files/product_configurable_with_single_child.php
+     */
+    public function testReindexParentProductWhenChildBeingDisabled()
+    {
+        $this->indexer->reindexAll();
+
+        $visibilityFilter = [
+            Visibility::VISIBILITY_IN_SEARCH,
+            Visibility::VISIBILITY_IN_CATALOG,
+            Visibility::VISIBILITY_BOTH
+        ];
+        $products = $this->search('Configurable', $visibilityFilter);
+        $this->assertCount(1, $products);
+
+        $childProduct = $this->getProductBySku('configurable_option_single_child');
+        $childProduct->setStatus(Product\Attribute\Source\Status::STATUS_DISABLED)->save();
+
+        $products = $this->search('Configurable', $visibilityFilter);
+        $this->assertCount(0, $products);
+    }
+
     /**
      * Search the text and return result collection
      *
      * @param string $text
+     * @param null|array $visibilityFilter
      * @return Product[]
      */
-    protected function search($text)
+    protected function search($text, $visibilityFilter = null)
     {
         $this->resourceFulltext->resetSearchResults();
         $query = $this->queryFactory->get();
@@ -207,6 +238,10 @@ class FulltextTest extends \PHPUnit\Framework\TestCase
             ]
         );
         $collection->addSearchFilter($text);
+        if (null !== $visibilityFilter) {
+            $collection->setVisibility($visibilityFilter);
+        }
+
         foreach ($collection as $product) {
             $products[] = $product;
         }
diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_with_single_child.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_with_single_child.php
new file mode 100644
index 0000000000000000000000000000000000000000..5172ea94e53748ed708b6b268e8a79371b072be1
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_with_single_child.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Attribute\Source\Status;
+use Magento\Catalog\Model\Product\Type;
+use Magento\Catalog\Model\Product\Visibility;
+use Magento\Catalog\Setup\CategorySetup;
+use Magento\CatalogInventory\Api\Data\StockItemInterface;
+use Magento\ConfigurableProduct\Helper\Product\Options\Factory;
+use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
+use Magento\Eav\Api\Data\AttributeOptionInterface;
+use Magento\TestFramework\Helper\Bootstrap;
+
+Bootstrap::getInstance()->reinitialize();
+
+require __DIR__ . '/../../../Magento/ConfigurableProduct/_files/configurable_attribute.php';
+
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = Bootstrap::getObjectManager()
+    ->create(ProductRepositoryInterface::class);
+
+/** @var $installer CategorySetup */
+$installer = Bootstrap::getObjectManager()->create(CategorySetup::class);
+
+/* Create simple products per each option value*/
+/** @var AttributeOptionInterface[] $options */
+$options = $attribute->getOptions();
+
+$attributeValues = [];
+$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default');
+$productsSku = [1410];
+array_shift($options); //remove the first option which is empty
+
+$option = reset($options);
+
+/** @var $childProduct Product */
+$childProduct = Bootstrap::getObjectManager()->create(Product::class);
+$productSku = array_shift($productsSku);
+$childProduct->setTypeId(Type::TYPE_SIMPLE)
+    ->setAttributeSetId($attributeSetId)
+    ->setName('Configurable Product Option' . $option->getLabel())
+    ->setSku('configurable_option_single_child')
+    ->setPrice(11)
+    ->setTestConfigurable($option->getValue())
+    ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE)
+    ->setStatus(Status::STATUS_ENABLED);
+$childProduct = $productRepository->save($childProduct);
+
+/** @var StockItemInterface $stockItem */
+$stockItem = $childProduct->getExtensionAttributes()->getStockItem();
+$stockItem->setUseConfigManageStock(1)->setIsInStock(true)->setQty(100)->setIsQtyDecimal(0);
+
+$childProduct = $productRepository->save($childProduct);
+
+$attributeValues[] = [
+    'label' => 'test',
+    'attribute_id' => $attribute->getId(),
+    'value_index' => $option->getValue(),
+];
+
+/** @var $product Product */
+$configurableProduct = Bootstrap::getObjectManager()->create(Product::class);
+
+/** @var Factory $optionsFactory */
+$optionsFactory = Bootstrap::getObjectManager()->create(Factory::class);
+
+$configurableAttributesData = [
+    [
+        'attribute_id' => $attribute->getId(),
+        'code' => $attribute->getAttributeCode(),
+        'label' => $attribute->getStoreLabel(),
+        'position' => '0',
+        'values' => $attributeValues,
+    ],
+];
+
+$configurableOptions = $optionsFactory->create($configurableAttributesData);
+
+$extensionConfigurableAttributes = $configurableProduct->getExtensionAttributes();
+$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions);
+$extensionConfigurableAttributes->setConfigurableProductLinks([$childProduct->getId()]);
+
+$configurableProduct->setExtensionAttributes($extensionConfigurableAttributes);
+
+$configurableProduct->setTypeId(Configurable::TYPE_CODE)
+    ->setAttributeSetId($attributeSetId)
+    ->setName('Configurable Product with single child')
+    ->setSku('configurable_with_single_child')
+    ->setVisibility(Visibility::VISIBILITY_BOTH)
+    ->setStatus(Status::STATUS_ENABLED);
+$configurableProduct = $productRepository->save($configurableProduct);
+
+/** @var StockItemInterface $stockItem */
+$stockItem = $configurableProduct->getExtensionAttributes()->getStockItem();
+$stockItem->setUseConfigManageStock(1)->setIsInStock(1);
+
+$productRepository->save($configurableProduct);
diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_with_single_child_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_with_single_child_rollback.php
new file mode 100644
index 0000000000000000000000000000000000000000..85a72789e7fd3ae5941328fbe515ca17e64eb2b0
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/product_configurable_with_single_child_rollback.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\TestFramework\Helper\Bootstrap;
+
+$objectManager = Bootstrap::getObjectManager();
+
+/** @var \Magento\Framework\Registry $registry */
+$registry = $objectManager->get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = Bootstrap::getObjectManager()
+    ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+
+$productSkus = ['configurable_option_single_child', 'configurable_with_single_child'];
+/** @var SearchCriteriaBuilder $searchCriteriaBuilder */
+$searchCriteriaBuilder = Bootstrap::getObjectManager()->get(SearchCriteriaBuilder::class);
+$searchCriteriaBuilder->addFilter(ProductInterface::SKU, $productSkus, 'in');
+$result = $productRepository->getList($searchCriteriaBuilder->create());
+foreach ($result->getItems() as $product) {
+    $productRepository->delete($product);
+}
+
+require __DIR__ . '/../../../Magento/Framework/Search/_files/configurable_attribute_rollback.php';
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/lib/internal/Magento/Framework/Indexer/CacheContext.php b/lib/internal/Magento/Framework/Indexer/CacheContext.php
index df0bed1dc1dcbb4bd78a824311e1b03f1ca93d7f..4a6964477ebd5cdd5121e17a0e3edf4d2b4656b5 100644
--- a/lib/internal/Magento/Framework/Indexer/CacheContext.php
+++ b/lib/internal/Magento/Framework/Indexer/CacheContext.php
@@ -29,15 +29,14 @@ class CacheContext implements \Magento\Framework\DataObject\IdentityInterface
      */
     public function registerEntities($cacheTag, $ids)
     {
-        $this->entities[$cacheTag] =
-            array_merge($this->getRegisteredEntity($cacheTag), $ids);
+        $this->entities[$cacheTag] = array_merge($this->getRegisteredEntity($cacheTag), $ids);
         return $this;
     }
 
     /**
      * Register entity tags
      *
-     * @param string $cacheTag
+     * @param array $cacheTags
      * @return $this
      */
     public function registerTags($cacheTags)